diff --git a/.gitignore b/.gitignore index 7d999646..71755637 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ __pycache__/ *.log -*.swp \ No newline at end of file +*.swp +fastapi-env/ \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example deleted file mode 100644 index 013ca14e..00000000 --- a/backend/.env.example +++ /dev/null @@ -1,4 +0,0 @@ -DB_URL=mongodb://localhost:27017/ -DB_HOST=mongodb -DB_PORT=27017 -DB_NAME=ess-backend-db \ No newline at end of file diff --git a/backend/Stores.json b/backend/Stores.json new file mode 100644 index 00000000..e790bbf6 --- /dev/null +++ b/backend/Stores.json @@ -0,0 +1 @@ +{"2": {"py/object": "src.db.store_database.Store", "nome": "1", "categoria": "4", "cnpj": "2", "email": "3", "senha": "5"}, "e": {"py/object": "src.db.store_database.Store", "nome": "27", "categoria": "e", "cnpj": "e", "email": "123", "senha": "e"}, "eewtw": {"py/object": "src.db.store_database.Store", "nome": "278", "categoria": "e2", "cnpj": "eewtw", "email": "1235", "senha": "e3"}, "eewt": {"py/object": "src.db.store_database.Store", "nome": "278", "categoria": "e2", "cnpj": "eewt", "email": "1235", "senha": "e3"}, "vitu": {"py/object": "src.db.store_database.Store", "nome": "27052004", "categoria": "Vitor", "cnpj": "vitu", "email": "000", "senha": "a"}, "vit": {"py/object": "src.db.store_database.Store", "nome": "2705200", "categoria": "Vito", "cnpj": "vit", "email": "00", "senha": "ai"}, "vi": {"py/object": "src.db.store_database.Store", "nome": "2705200", "categoria": "Vit", "cnpj": "vi", "email": "0", "senha": "aii"}, "101010": {"py/object": "src.db.store_database.Store", "nome": "vitinho", "categoria": "vitor", "cnpj": "101010", "email": "vitu", "senha": "123"}, "10101": {"py/object": "src.db.store_database.Store", "nome": "vitinho", "categoria": "vitor", "cnpj": "10101", "email": "vitu", "senha": "123"}, "vitorpaz": {"py/object": "src.db.store_database.Store", "nome": "a", "categoria": "a", "cnpj": "vitorpaz", "email": "gg", "senha": "123455"}, "string": {"py/object": "src.db.store_database.Store", "nome": "string", "categoria": "string", "cnpj": "string", "email": "string", "senha": "string"}, "111": {"py/object": "src.db.store_database.Store", "nome": "string", "categoria": "string", "cnpj": "111", "email": "string", "senha": "123"}, "1313": {"py/object": "src.db.store_database.Store", "nome": "string", "categoria": "string", "cnpj": "1313", "email": "string", "senha": "123"}} \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt index 2a638a27..ae7f3e6f 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -10,3 +10,6 @@ httpx py-bcrypt jsonpickle bidict +bcrypt +requirements +datetime \ No newline at end of file diff --git a/backend/src/api/cancel_orders.py b/backend/src/api/cancel_orders.py new file mode 100644 index 00000000..d5af805f --- /dev/null +++ b/backend/src/api/cancel_orders.py @@ -0,0 +1,39 @@ +from fastapi import APIRouter, status, HTTPException, Response +from src.schemas.response import HTTPResponses, HttpResponseModel +from src.schemas.provisory_schemas import Product, Order, User +from fastapi.responses import JSONResponse +from src.service.impl.orders_service import OrdersService + + +router = APIRouter() + +@router.put( + '/cancel/{id_product}', + response_model=HttpResponseModel, + status_code=status.HTTP_200_OK, + description="Order cancel by its ID", + ) +def cancel_order(id_product: int, user_CPF: str, cancel_reason:str, response:Response) -> HttpResponseModel: + request_response = OrdersService.cancel_order_service(id_product, user_CPF, cancel_reason) + response.status_code = request_response.status_code + + return request_response + +@router.get( + '/get_all', + response_model=HttpResponseModel, + status_code=status.HTTP_200_OK, + description="Get all cancel products", + ) +def get_all_canceled_orders(user_CPF: str) -> HttpResponseModel: + response = OrdersService.get_all_orders_service(user_CPF) + return response + +#@router.post( +# '/create/{id_product}', +# response_model=HttpResponseModel, +# status_code=status.HTTP_201_CREATED, +# description="Create a order from the product in the cart by its ID", +# ) +# def create_order(product_id: int, user_CPF: str) -> HttpResponseModel: +# pass \ No newline at end of file diff --git a/backend/src/api/carrinho.py b/backend/src/api/carrinho.py new file mode 100644 index 00000000..b2f7f310 --- /dev/null +++ b/backend/src/api/carrinho.py @@ -0,0 +1,253 @@ +from fastapi import APIRouter, status, HTTPException +from src.schemas.response import HttpResponseModel +from src.service.impl.carrinho_service import Carrinho_service +from src.service.impl.item_database_service import DadosItem +from src.service.impl.carrinho_service import DadosEndereço + +router = APIRouter() + +@router.get( + "/view/{CPF}", + response_model=HttpResponseModel, + status_code=status.HTTP_200_OK, + description="Visualização do carrinho", + responses={ + status.HTTP_200_OK: { + "model": HttpResponseModel, + "description": "Conteúdo do carrinho obtido com sucesso" + }, + status.HTTP_201_CREATED: { + "model":HttpResponseModel, + "description": "Carrinho não encontrado, carrrinho criado" + }, + status.HTTP_400_BAD_REQUEST: { + "model": HttpResponseModel, + "description": "Falha na obtenção do conteúdo do carrinho" + } + }, +) +def visualizar_carrinho(CPF: str) -> HttpResponseModel: + """ Se carrinho não for encontrado cria carrinho para o respectivo CPF """ + print("Entrou em visualizar carrinho") + resultado = Carrinho_service.get_cart(CPF=CPF) + return resultado + +@router.post( + "/adicionar", + response_model=HttpResponseModel, + status_code=status.HTTP_200_OK, + description="Adicionar item ao carrinho", + responses={ + status.HTTP_200_OK: { + "model": HttpResponseModel, + "description": "Item adicionado ao carrinho com sucesso" + }, + status.HTTP_400_BAD_REQUEST: { + "model": HttpResponseModel, + "description": "Falha na adição do item ao carrinho" + } + }, +) +def adicionar_item_ao_carrinho(dados: DadosItem, CPF: str) -> HttpResponseModel: + """ Tenta adicionar item ao carrinho """ + print("Entrou na função adicionar_item_ao_carrinho") + print(dados) + resultado = Carrinho_service.add_item_to_cart(item_data= dados, CPF= CPF) + print(resultado) + if resultado.status_code == status.HTTP_200_OK: + return resultado + else: + # Se a adição não foi bem-sucedida, lançar uma exceção HTTP que será tratada pelo FastAPI + raise HTTPException( + status_code=resultado.status_code, + detail=resultado.message + ) + +@router.delete( + "/remover", + response_model=HttpResponseModel, + status_code=status.HTTP_200_OK, + description="Remover item do carrinho", + responses={ + status.HTTP_200_OK: { + "model": HttpResponseModel, + "description": "Item removido do carrinho com sucesso" + }, + status.HTTP_400_BAD_REQUEST: { + "model": HttpResponseModel, + "description": "Falha na remoção do item do carrinho" + }, + status.HTTP_404_NOT_FOUND: { + "model": HttpResponseModel, + "description": "Item ou carrinho não encontrado" + } + }, +) +def remover_item_do_carrinho(item_id: str, CPF: str) -> HttpResponseModel: + """ Tenta remover item do carrinho """ + resultado = Carrinho_service.remove_item_from_cart(item_id=item_id, CPF=CPF) + if resultado.status_code == status.HTTP_200_OK: + return resultado + else: + # Se a remoção não foi bem-sucedida, lançar uma exceção HTTP que será tratada pelo FastAPI + raise HTTPException( + status_code=resultado.status_code, + detail=resultado.message + ) + +@router.get( + "/view_all_carts", + response_model=HttpResponseModel, + status_code=status.HTTP_200_OK, + description="Visualização dos carrinhos", + responses={ + status.HTTP_200_OK: { + "model": HttpResponseModel, + "description": "Conteúdo dos carrinhos obtido com sucesso" + }, + status.HTTP_400_BAD_REQUEST: { + "model": HttpResponseModel, + "description": "Falha na obtenção do conteúdo dos carrinhos" + } + }, +) +def visualizar_carrinho() -> HttpResponseModel: + """ Se carrinho não for encontrado cria carrinho para o respectivo CPF """ + return Carrinho_service.get_all_carts() + +@router.delete( + "/clear_cart", + response_model=HttpResponseModel, + status_code=status.HTTP_200_OK, + description="Limpar carrinho", + responses={ + status.HTTP_200_OK: { + "model": HttpResponseModel, + "description": "Conteúdo do carrinho limpo com sucesso" + }, + status.HTTP_404_NOT_FOUND: { + "model": HttpResponseModel, + "description": "Carrinho não encontrado" + } + }, +) +def clear_cart(CPF: str) -> HttpResponseModel: + """ Tenta limpar o carrinho """ + print("Entrou em clear_cart") + resultado = Carrinho_service.clear_cart_by_CPF(CPF=CPF) + if resultado.status_code == status.HTTP_200_OK: + return resultado + else: + raise HTTPException( + status_code=resultado.status_code, + detail=resultado.message + ) + +@router.delete( + "/clear_carts", + response_model=HttpResponseModel, + status_code=status.HTTP_200_OK, + description="Limpar database de carrinhos", + responses={ + status.HTTP_200_OK: { + "model": HttpResponseModel, + "description": "Conteúdo da database limpo" + } + }, +) +def clear_carts() -> HttpResponseModel: + """ Tenta limpar a database de carrinhos """ + return Carrinho_service.clear_all_carts() + +@router.put( + "/incrementar_item", + response_model=HttpResponseModel, + status_code=status.HTTP_200_OK, + description="Incrementar quantidade de item no carrinho", + responses={ + status.HTTP_200_OK: { + "model": HttpResponseModel, + "description": "Item incrementado com sucesso" + }, + status.HTTP_400_BAD_REQUEST: { + "model": HttpResponseModel, + "description": "Falha no incremento da quantidade do item" + }, + status.HTTP_404_NOT_FOUND: { + "model": HttpResponseModel, + "description": "Carrinho ou item não encontrado" + } + }, +) +def incrementar_item (item_id: str, CPF: str) -> HttpResponseModel: + """ Tenta Incrementar quantidade de item no carrinho """ + resultado = Carrinho_service.increase_item_quantity(item_id=item_id, CPF=CPF) + if resultado.status_code == status.HTTP_200_OK: + return resultado + else: + # Se a adição não foi bem-sucedida, lançar uma exceção HTTP que será tratada pelo FastAPI + raise HTTPException( + status_code=resultado.status_code, + detail=resultado.message + ) + + +@router.put( + "/decrementar_item", + response_model=HttpResponseModel, + status_code=status.HTTP_200_OK, + description="Decrementar quantidade de item no carrinho", + responses={ + status.HTTP_200_OK: { + "model": HttpResponseModel, + "description": "Item decrementado com sucesso" + }, + status.HTTP_400_BAD_REQUEST: { + "model": HttpResponseModel, + "description": "Falha no decremento da quantidade do item" + }, + status.HTTP_404_NOT_FOUND: { + "model": HttpResponseModel, + "description": "Carrinho ou item não encontrado" + } + }, +) +def decrementar_item (item_id: str, CPF: str) -> HttpResponseModel: + """ Tenta decrementar quantidade de item no carrinho """ + resultado = Carrinho_service.decrease_item_quantity(item_id=item_id, CPF=CPF) + if resultado.status_code == status.HTTP_200_OK: + return resultado + else: + # Se a adição não foi bem-sucedida, lançar uma exceção HTTP que será tratada pelo FastAPI + raise HTTPException( + status_code=resultado.status_code, + detail=resultado.message + ) + +@router.put( + "/alterar_endereço", + response_model=HttpResponseModel, + status_code=status.HTTP_200_OK, + description="Alterar endereço do carrinho", + responses={ + status.HTTP_200_OK: { + "model": HttpResponseModel, + "description": "Item adicionado ao carrinho com sucesso" + }, + status.HTTP_400_BAD_REQUEST: { + "model": HttpResponseModel, + "description": "Falha na adição do item ao carrinho" + } + }, +) +def alterar_endereço(dados: DadosEndereço, CPF: str) -> HttpResponseModel: + """ Tenta adicionar item ao carrinho """ + resultado = Carrinho_service.add_adress(dados, CPF) + if resultado.status_code == status.HTTP_200_OK: + return resultado + else: + # Se a adição não foi bem-sucedida, lançar uma exceção HTTP que será tratada pelo FastAPI + raise HTTPException( + status_code=resultado.status_code, + detail=resultado.message + ) \ No newline at end of file diff --git a/backend/src/api/estimated_time_arrival.py b/backend/src/api/estimated_time_arrival.py new file mode 100644 index 00000000..ced91802 --- /dev/null +++ b/backend/src/api/estimated_time_arrival.py @@ -0,0 +1,18 @@ +from fastapi import APIRouter, status, HTTPException, Response +from src.schemas.response import HTTPResponses, HttpResponseModel +from src.schemas.provisory_schemas import Supplier, Product, Order, User +from fastapi.responses import JSONResponse +from src.service.impl.time_arrival_service import TimeArrivalService + + +router = APIRouter() + +@router.get( + '/get_estimated_time/{product_id}', + response_model=HttpResponseModel, + status_code=status.HTTP_200_OK, + description="Route to obtain estimated delivery date, requisition date and mode of transport", + ) +def get_time(product_id: int, user_CPF: str) -> HttpResponseModel: + response = TimeArrivalService.calculating_time_arrival(product_id, user_CPF) + return response \ No newline at end of file diff --git a/backend/src/api/items.py b/backend/src/api/items.py deleted file mode 100644 index 635e5f45..00000000 --- a/backend/src/api/items.py +++ /dev/null @@ -1,68 +0,0 @@ -from fastapi import APIRouter, status -from src.schemas.response import HttpResponseModel -from src.service.impl.item_service import ItemService - -router = APIRouter() - -@router.get( - "/{item_id}", - response_model=HttpResponseModel, - status_code=status.HTTP_200_OK, - description="Retrieve an item by its ID", - tags=["items"], - responses={ - status.HTTP_200_OK: { - "model": HttpResponseModel, - "description": "Successfully got item by id", - }, - status.HTTP_404_NOT_FOUND: { - "description": "Item not found", - } - }, -) -def get_item(item_id: str) -> HttpResponseModel: - """ - Get item by ID. - - Parameters: - - item_id: The ID of the item to retrieve. - - Returns: - - The item with the specified ID. - - Raises: - - HTTPException 404: If the item is not found. - - """ - item_get_response = ItemService.get_item(item_id) - return item_get_response - - -@router.get( - "/", - response_model=HttpResponseModel, - status_code=status.HTTP_200_OK, - description="Retrieve all items", - tags=["items"], - responses={ - status.HTTP_200_OK: { - "model": HttpResponseModel, - "description": "Successfully got all the items", - } - }, -) -def get_items() -> HttpResponseModel: - """ - Get all items. - - Returns: - - A list of all items. - - """ - - item_list_response = ItemService.get_items() - - return item_list_response - - -# TODO: Add POST, PUT, DELETE endpoints \ No newline at end of file diff --git a/backend/src/api/recuperation.py b/backend/src/api/recuperation.py new file mode 100644 index 00000000..b85ce6d0 --- /dev/null +++ b/backend/src/api/recuperation.py @@ -0,0 +1,34 @@ +from fastapi import APIRouter, status, Response +from src.schemas.response import HttpResponseModel +import src.schemas.user_schemas as schemas +from src.service.impl.recuperation_service import RecuperationService + +router = APIRouter() + +#Tela para digitar o email para receber o código +@router.get( + "", + status_code=status.HTTP_200_OK, + description="Esqueci a senha", +) +def esqueci_a_senha(): + return {"message":"Vc esta na tela de esqueci a senha"} + +#Rota para enviar o email digitado ao servidor +@router.post( + "/enviarcodigo", + status_code=status.HTTP_200_OK, + description="Enviar código de recuperação", +) +def enviar_codigo(email: str): + RecuperationService.enviar_email(email) + return {"message": f"Caso o email '{email}' esteja cadastrado enviaremos um código para o mesmo"} + +@router.post( + "/recuperarconta", + status_code=status.HTTP_200_OK, + description="Digitar código de recuperação", +) +def recuperar_conta(email:str , codigo: str, nova_senha: str, nova_senha_repetida: str): + message = RecuperationService.recuperar_conta(email, codigo, nova_senha, nova_senha_repetida) + return {message} \ No newline at end of file diff --git a/backend/src/api/router.py b/backend/src/api/router.py index a0cc6026..53d84570 100644 --- a/backend/src/api/router.py +++ b/backend/src/api/router.py @@ -1,7 +1,11 @@ from fastapi import APIRouter -from src.api import users +from src.api import carrinho, users, recuperation, stores, cancel_orders, estimated_time_arrival api_router = APIRouter() # api_router.include_router(items.router, prefix="/items", tags=["items"]) - -api_router.include_router(users.router, prefix="/auth/user", tags = ["user"]) \ No newline at end of file +api_router.include_router(carrinho.router, prefix="/carrinho", tags = ["carrinho"]) +api_router.include_router(users.router, prefix="/auth/user", tags = ["user"]) +api_router.include_router(recuperation.router, prefix="/esqueciasenha", tags = ["recuperation"]) +api_router.include_router(stores.router, prefix="/stores", tags = ["Stores"]) +api_router.include_router(estimated_time_arrival.router, prefix="/estimated_time_arrival", tags=["Estimated time arrival"]) +api_router.include_router(cancel_orders.router, prefix="/orders", tags=["Orders"]) diff --git a/backend/src/api/stores.py b/backend/src/api/stores.py new file mode 100644 index 00000000..a500e734 --- /dev/null +++ b/backend/src/api/stores.py @@ -0,0 +1,113 @@ +from fastapi import APIRouter, status, HTTPException +from src.schemas.response import HttpResponseModel +from src.service.impl.store_service import Store_service, DadosLoja, DadosLoginLoja, DadosRetrieveLoja, DadosChangeLoja + +router = APIRouter() + +@router.post( + "/signup", + response_model=HttpResponseModel, + status_code=status.HTTP_200_OK, + description="Cadastro do usuario", + responses={ + status.HTTP_200_OK: { + "model": HttpResponseModel, + "description": "Cadastro realizado de forma de bem sucedida" + }, + status.HTTP_400_BAD_REQUEST: { + "model": HttpResponseModel, + "description": "Cadastro não realizado" + } + }, +) +def store_signup(dados: DadosLoja): + """Se a loja ainda não estiver cadastrada, adiciona ela na db""" + resultado = Store_service.signup_store(dados) + if resultado.status_code == status.HTTP_200_OK: + return resultado + else: + raise HTTPException(status_code=resultado.status_code, detail=resultado.message) + + + +@router.post( + "/login", + response_model=HttpResponseModel, + status_code=status.HTTP_200_OK, + description="Login do usuario", + responses={ + status.HTTP_200_OK: { + "model": HttpResponseModel, + "description": "Login realizado de forma de bem sucedida" + }, + status.HTTP_400_BAD_REQUEST: { + "model": HttpResponseModel, + "description": "Login não realizado" + }, + status.HTTP_401_UNAUTHORIZED: { + "model": HttpResponseModel, + "description": "CNPJ ou Senha incorretos" + }, + }, +) +def store_login(dados: DadosLoginLoja): + """Se os dados de entrada corresponderem com a db redireciona usuario para homepage""" + resultado = Store_service.login_store(dados) + if resultado.status_code == status.HTTP_200_OK: + return resultado + else: + raise HTTPException(status_code=resultado.status_code, detail=resultado.message) + + + +@router.post( + "/login/retrieve_password", + response_model=HttpResponseModel, + status_code=status.HTTP_200_OK, + description="Recuperação de Senha", + responses={ + status.HTTP_200_OK: { + "model": HttpResponseModel, + "description": "Senha redefinida com sucesso" + }, + status.HTTP_400_BAD_REQUEST: { + "model": HttpResponseModel, + "description": "Senha não pode ser redefinida" + }, + status.HTTP_401_UNAUTHORIZED: { + "model": HttpResponseModel, + "description": "CPNJ não registrado" + }, + }, +) +def store_retrieve_password(dados: DadosRetrieveLoja): + """Se CNPJ e Email estiverem no bd, redefine a senha do usuario""" + resultado = Store_service.retrieve_password(dados) + if resultado.status_code == status.HTTP_200_OK: + return resultado + else: + raise HTTPException(status_code=resultado.status_code, detail=resultado.message) + +@router.post( + "/store/change_store_data", + response_model=HttpResponseModel, + status_code=status.HTTP_200_OK, + description="Modifição de dados da loja", + responses={ + status.HTTP_200_OK: { + "model": HttpResponseModel, + "description": "Dados atualizados com sucesso" + }, + status.HTTP_400_BAD_REQUEST: { + "model": HttpResponseModel, + "description": "Dados não foram atualizados" + }, + }, +) +def change_store_data(dados:DadosChangeLoja): + """Se CNPJ e Email estiverem no bd, redefine a senha do usuario""" + resultado = Store_service.change_user_data(dados) + if resultado.status_code == status.HTTP_200_OK: + return resultado + else: + raise HTTPException(status_code=resultado.status_code, detail=resultado.message) \ No newline at end of file diff --git a/backend/src/config/__init__.py b/backend/src/config/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/src/config/config.py b/backend/src/config/config.py deleted file mode 100644 index adec8c55..00000000 --- a/backend/src/config/config.py +++ /dev/null @@ -1,16 +0,0 @@ -from dotenv import load_dotenv -from os import environ - -# Load environment variables from .env file -load_dotenv() - -class Environment(): - """ - Create an environment object to store environment variables - """ - DB_URL = environ.get('DB_URL') - DB_HOST = environ.get('DB_HOST') - DB_PORT = environ.get('DB_PORT') - DB_NAME = environ.get('DB_NAME') - -env = Environment() \ No newline at end of file diff --git a/backend/src/db/__init__.py b/backend/src/db/__init__.py index 6881c32d..80057fe3 100644 --- a/backend/src/db/__init__.py +++ b/backend/src/db/__init__.py @@ -1,10 +1,18 @@ -from .database import Database from .user_database import UserDatabase -from .config.create_collections import create_collections - -database = Database() +from .codigos_rec_database import RecuperacaoDatabase +from .store_database import StoreDatabase user_database = UserDatabase() user_database_example = UserDatabase("Usuários teste.json") user_database_example.clear_database() -# create_collections(database) \ No newline at end of file + +from .itens_database import ItemDatabase +from .carrinho_database import Carrinhos + +item_database = ItemDatabase() + +cart_database = Carrinhos() +store_database = StoreDatabase() +recuperacao_database = RecuperacaoDatabase() +recuperacao_database_test = RecuperacaoDatabase("Códigos teste.json") + diff --git a/backend/src/db/carrinho_database.py b/backend/src/db/carrinho_database.py new file mode 100644 index 00000000..e58a69b7 --- /dev/null +++ b/backend/src/db/carrinho_database.py @@ -0,0 +1,436 @@ +from typing import List, Dict +from uuid import uuid4 +from pymongo import MongoClient, errors +from pymongo.collection import Collection, IndexModel +#from src.config.config import env +from logging import INFO, WARNING, getLogger +from decimal import Decimal, ROUND_HALF_UP +import re +import os.path +import jsonpickle +from src.db.itens_database import Item +from src.db.schemas.adress_schema import Endereço +import sys + +# Faz os testes não interferirem com o funcionamento do programa em live +if "pytest" in sys.modules: + database_path = "Carrinhos teste.json" +else: + database_path = "Carrinhos.json" + +logger = getLogger('uvicorn') + +class Carrinho(): + """Classe que representa um carrinho de um usuário + + Criar com método new_cart() + + Returns: + (Cart, "SUCCESS"), ou (None, reason) caso o input não seja validado. + + reason será o nome do campo rejeitado pela validação + """ + CPF: str + items: dict[Item] + total: str # Total do preço dos produtos no carrinho + endereço: Endereço | None + + def __init__(self, CPF: str): + self.CPF = CPF + self.items = dict() + self.total = "0.00" + self.endereço = None + + def recalcular_total(self): + aux = Decimal("0.00") + for item in self.items.values(): + aux += Decimal(item.price) * item.quantidade # Valor inteiro + + dois_decimais = Decimal("0.00") # Arredonda para duas casas decimais + self.total = str(aux.quantize(dois_decimais, rounding=ROUND_HALF_UP)) + + def alterar_endereço(self, rua: str, numero: int, bairro: str, cidade: str, estado: str, cep: str, pais: str, complemento: str | None = None): + """ Tenta adicionar um endereço """ + # Fazer as verificações de validade do endereço (Se é real ou não) + reason = [] + # exemplo: if cep invalido -> reason.append("CEP invalido") return (False, reason) + self.endereço = Endereço(rua, numero, bairro, cidade, estado, cep, pais, complemento) + reason.append("Endereço registrado com sucesso") + return (True, reason) + + def get_adress(self) -> str: + """ Retorna o endereço do objeto em si na forma de string """ + if self.endereço is None: + return "Endereço não registrado" + return self.endereço.__str__() + + def get_all_items(self): + """Retorna todos os itens do carrinho""" + return list(self.items.values()) + + def add_item(self, item: Item): + """Adicionar um item ao carrinho + + Args: + item (Item): Item em questão + + Returns: + reason (list[str]): contém informações sobre se o item já existia ou não. + """ + reason = [] + obj = self.get_item_by_ID(item.id) + + if obj is None: + self.items[item.id] = item + reason.append("Novo item adicionado") + else: + self.items[item.id].quantidade += item.quantidade + reason.append("Quantidade do item existente aumentada") + + self.recalcular_total() + + return reason + + def remove_item_by_ID (self, item_id: str) -> Item | None: + """ Remover um item do carrinho + + Args: + item_id (int): ID do item em questão + + Returns: + item (Item | None): retorna item removido ou nada + """ + toreturn = self.items.pop(item_id, None) + self.recalcular_total() + return toreturn + + def get_item_by_ID (self, item_id: str) -> Item | None: + """ Acessar um item do carrinho + + Args: + item_id (int): ID do item em questão + + Returns: + Item (Item): Se o item for encontrado | None: Se o item não for encontrado. + """ + for key,val in self.items.items(): + if val.id == item_id: + return val + return None + + def modify_item_by_ID (self, item_id: str, new_item: Item): + """ Modificar um item da database + + Args: + item_id (int): ID do item em questão + new_item (Item): novos valores do item a ser modificado + + Returns: + success (bool): True para operação bem sucedida, False para mal sucedida + Item (Item | None): Se o item for encontrado. + """ + reason = [] + if self.get_item_by_ID(item_id): + reason.append("Item não encontrado") + return(False, reason) + + self.items[item_id] = new_item + self.recalcular_total() + return (True, ["SUCCESS"]) + + + def clear_database(self): + self.items = dict() + self.recalcular_total() + + +class Carrinhos(): + db: dict[Carrinho] + file_path:str + + def __init__(self, path: str = database_path): + self.db = dict() + self.file_path = path + self.try_read_from_file() + + def try_read_from_file(self): + # Ler itens do arquivo + if not os.path.exists(self.file_path): + self.write_to_file() + return None + + with open(self.file_path) as file: + carrinhos = file.read() + db = jsonpickle.decode(carrinhos) + if type(db) == dict: + self.db = db + + def write_to_file(self): + objetos = jsonpickle.encode(self.db) + with open(self.file_path, 'w+') as file: + file.write(objetos) + + def get_cart_list(self, update = True): + """Retorna uma lista de itens do tipo Carrinho""" + if update: + self.try_read_from_file() + return list(self.db.values()) + + def alterar_endereco_de_carrinho_por_CPF(self, CPF: str, rua: str, numero: int, bairro: str, cidade: str, estado: str, cep: str, pais: str, complemento: str | None = None, update: bool = True): + """ Recebe um CPF e altera o endereço do carrinho respectivo """ + if update: + self.try_read_from_file() + + reason = [] + carrinho = self.get_cart_by_CPF(CPF) + if carrinho is None: + reason.append("Carrinho não encontrado") + return (False, reason) + + # Chama o método alterar_endereço do carrinho + (success, reason) = carrinho.alterar_endereço(rua, numero, bairro, cidade, estado, cep, pais, complemento) + + if not success: + return (False, reason) + + # Se a alteração foi bem-sucedida + self.write_to_file() + reason.append("Endereço alterado com sucesso") + return (True, reason) + + + def add_new_cart(self, carrinho: Carrinho, update: bool = True): + """Adicionar um novo carrinho a database + + Args: + carrinho (Carrinho): Carrinho em questão + + Returns: + success (bool): True para operação bem sucedida, False para mal sucedida + reason (list[str]): contém "Carrinho com mesmo CPF já na base de dados" se for um CPF já existente. + ["SUCCESS"] caso tenha sido uma operação bem sucedida + """ + reason = [] + if update: + self.try_read_from_file() + if self.get_cart_by_CPF(carrinho.CPF, False): + print("Carrinho com mesmo CPF já na base de dados") + reason.append("Carrinho com mesmo CPF já na base de dados") + + if reason.__len__() > 0: + return (False, reason) + + self.db[carrinho.CPF] = carrinho + print("Carrinho criado e adicionado a base de dados") + self.write_to_file() + return (True, ["SUCCESS"]) + + def remove_cart_by_CPF (self, CPF: str, update: bool = True) -> Item | None: + """ Remover um carrinho da database + + Args: + CPF (str): CPF do carrinho em questão + + Returns: + carrinho (Carrinho | None): carrinho removido ou None. + """ + if update: + self.try_read_from_file() + toreturn = self.db.pop(CPF, None) + self.write_to_file() + return toreturn + + def get_cart_by_CPF (self, CPF: str, update: bool = True) -> Item | None: + """ Acessar um item da database + + Args: + CPF (str): CPF do carrinho em questão + + Returns: + Carrinho (Carrinho | None): Carrinho se for encontrado, None se não for encontrado + """ + if update: + self.try_read_from_file() + for key,val in self.db.items(): + if val.CPF == CPF: + return val + return None + + def modify_item_all_carts (self, item_id: str, new_item: Item, update: bool = True): + """ Modificação em um item da database (chamar para aplicar alteração em todos os carrinhos que apresentam o item) + + Args: + item_id (int): ID do item em questão + new_item (Item): novos valores do item a ser modificado + + Returns: + + """ + if update: + self.try_read_from_file() + + for CPF, cart in self.db: + for id, item in cart: + if id == item_id: + cart[id] = new_item + + self.write_to_file() + + + def remove_item_all_carts(self, item_id: str, update: bool = True): + """Remove um item especificado por item_id de todos os carrinhos na base de dados (chamar para aplicar alteração em todos os carrinhos que apresentam o item). + + Args: + item_id (int): ID do item a ser removido. + update (bool): Se True, atualiza a base de dados a partir do arquivo JSON antes da operação. + + """ + if update: + self.try_read_from_file() + + for cart_CPF, cart in self.db.items(): + if item_id in cart.items: + del cart.items[item_id] + + self.write_to_file() + + def add_item_to_cart(self, item: Item, CPF: str, update: bool = True): + """Adiciona um item no carrinho + + Args: + item (Item): item a ser adicionado + CPF (str): CPF do carrinho a ser modificado + update: ler do arquivo antes de operar + + Returns: + success (bool): True para operação bem sucedida, False para mal sucedida + reason (list[str]): contém "Carrinho não encontrado" se for o CPF não existe na base de dados. + ["SUCCESS"] caso tenha sido uma operação bem sucedida + """ + if update: + self.try_read_from_file() + + reason = [] + carrinho = self.get_cart_by_CPF(CPF) + + if carrinho is None: + reason.append("Carrinho de usuário não encontrado na base de dados") + return (False, reason) + + reason = self.db[CPF].add_item(item) + self.write_to_file() + + return (True, reason) + + def remove_item_from_cart(self, item_id: str, CPF: str, update: bool = True): + """Remove um item do carrinho + + Args: + item (Item): item a ser removido + CPF (str): CPF do carrinho a ser modificado + update: ler do arquivo antes de operar + + Returns: + success (bool): True para operação bem sucedida, False para mal sucedida + reason (list[str]): contém "Carrinho não encontrado" se for o CPF não existe na base de dados. + contém "Item não encontrado no carrinho" se não se encontrou o item selecionado. + ["SUCCESS"] caso tenha sido uma operação bem sucedida + """ + if update: + self.try_read_from_file() + + reason = [] + carrinho = self.get_cart_by_CPF(CPF) + + if carrinho is None: + reason.append("Carrinho de usuário não encontrado na base de dados") + return (False, reason) + + removed_item = self.db[CPF].remove_item_by_ID(item_id) + + if removed_item is None: + reason.append("Item não encontrado no carrinho") + return (False, reason) + + self.write_to_file() + reason.append("SUCCESS") + + return (True, reason) + + def decrease_item_quantity(self, item_id: str, CPF: str, update: bool = True): + """ Diminui em um a quantidade de um item, se o item tiver só 1 na quantidade, remove o item """ + if update: + self.try_read_from_file() + + reason = [] + carrinho = self.get_cart_by_CPF(CPF) + + if carrinho is None: + reason.append("Carrinho de usuário não encontrado na base de dados") + return (False, reason) + + if item_id not in carrinho.items: + reason.append("Item não encontrado no carrinho") + return (False, reason) + + if carrinho.items[item_id].quantidade > 1: + carrinho.items[item_id].quantidade -= 1 + reason.append("Quantidade do item alterada com sucesso") + else: + del carrinho.items[item_id] + reason.append("Quantidade do item alterada para 0, item removido do carrinho") + carrinho.recalcular_total() + self.write_to_file() + + return (True, reason) + + def increase_item_quantity(self, item_id: str, CPF: str, update: bool = True): + """Aumenta a quantidade de um item no carrinho. + + Args: + item_id (int): ID do item a ter sua quantidade aumentada. + CPF (str): CPF do usuário associado ao carrinho. + update (bool): Se True, atualiza a base de dados a partir do arquivo JSON. + + Returns: + (bool, list): Tupla contendo o sucesso da operação e uma lista de razões. + """ + if update: + self.try_read_from_file() + + reason = [] + carrinho = self.get_cart_by_CPF(CPF) + + if carrinho is None: + reason.append("Carrinho de usuário não encontrado na base de dados") + return (False, reason) + + if item_id not in carrinho.items: + reason.append("Item não encontrado no carrinho") + return (False, reason) + + carrinho.items[item_id].quantidade += 1 + carrinho.recalcular_total() + + self.write_to_file() + reason.append("SUCCESS") + + return (True, reason) + + def clear_cart_database(self): + self.db = dict() + self.write_to_file() + + def clear_cart_by_CPF(self, CPF: str, update: bool = True): + """ Tenta limpar o conteúdo do carrinho em questão. Retorna bool (success)""" + if update: + self.try_read_from_file() + + carrinho = self.get_cart_by_CPF(CPF=CPF) + if carrinho is None: + return False + else: + carrinho.clear_database() + + self.write_to_file() + return True diff --git a/backend/src/db/codigos_rec_database.py b/backend/src/db/codigos_rec_database.py new file mode 100644 index 00000000..96ebfebd --- /dev/null +++ b/backend/src/db/codigos_rec_database.py @@ -0,0 +1,55 @@ +import datetime +import jsonpickle + +class Recuperacao(object): + email:str + codigo:str + date: datetime.date + + def __init__(self, email: str, codigo: str, date: datetime.date = datetime.datetime.now()) -> None: + self.email = email + self.codigo = codigo + self.date = date + + +class RecuperacaoDatabase(): + db: dict[Recuperacao] + file_path: str + + def __init__(self, path = "Códigos.json"): + self.db = dict() + self.file_path = path + self.try_read_from_file() + + def try_read_from_file(self): + # Ler users de um arquivo + import os.path + if not os.path.exists(self.file_path): + self.write_to_file() + return None + + with open(self.file_path) as f: + objetos = f.read() + db = jsonpickle.decode(objetos) + if type(db) == dict: + self.db = db + + def write_to_file(self): + objetos = jsonpickle.encode(self.db) + with open(self.file_path, 'w+') as f: + f.write(objetos) + + def add_recuperacao(self, recuperacao: Recuperacao): + self.db[recuperacao.email] = recuperacao + self.write_to_file() + return True + + def get_rec_by_email(self, email: str): + try: + return self.db.get(email) + except: + return None + + def clear_database(self): + self.db = dict() + self.write_to_file() \ No newline at end of file diff --git a/backend/src/db/config/__init__.py b/backend/src/db/config/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/src/db/config/create_collections.py b/backend/src/db/config/create_collections.py deleted file mode 100644 index 7fb50a04..00000000 --- a/backend/src/db/config/create_collections.py +++ /dev/null @@ -1,25 +0,0 @@ -from pymongo import ASCENDING, IndexModel -from .item_collection_example import ITEM_COLLECTION_EXAMPLE -from src.db.schemas.item_schema import ItemSchema -from src.db.serializers.schema_serializer import schema_serializer - - -def create_collections(database): - """ - Create all collections and insert the example data. - - """ - - if 'items' not in database.db.list_collection_names(): - collections = ['items'] - - for collection in collections: - schema = ItemSchema() - database.create_collection( - collection, - indexes=[IndexModel([("id", ASCENDING)], unique=True)], - validation_schema=schema_serializer(schema.get()) - ) - - for item in ITEM_COLLECTION_EXAMPLE: - database.insert_item('items', item) diff --git a/backend/src/db/config/item_collection_example.py b/backend/src/db/config/item_collection_example.py deleted file mode 100644 index b95394ce..00000000 --- a/backend/src/db/config/item_collection_example.py +++ /dev/null @@ -1,32 +0,0 @@ -from datetime import datetime - -ITEM_COLLECTION_EXAMPLE = [ - { - "name": "RasenShuriken", - "created_at": '2023-07-10 10:00:00' - }, - { - "name": "Gomu Gomu no Pistol", - "created_at": '2023-07-11 15:30:00' - }, - { - "name": "KAMEEE-HAMEEEEEE-HAAAAA!", - "created_at": '2023-07-12 09:45:00' - }, - { - "name": "Eren Yaeger's Justice", - "created_at": '2023-07-13 14:20:00' - }, - { - "name": "Susanoo's Sword", - "created_at": '2023-07-14 13:50:00' - }, - { - "name": "Gomu Gomu no Mi", - "created_at": '2023-07-15 13:10:00' - }, - { - "name": "Migatte no Gokui", - "created_at": '2023-07-16 08:15:00' - } -] \ No newline at end of file diff --git a/backend/src/db/database.py b/backend/src/db/database.py deleted file mode 100644 index b1488324..00000000 --- a/backend/src/db/database.py +++ /dev/null @@ -1,211 +0,0 @@ -from typing import List, Dict -from uuid import uuid4 -from pymongo import MongoClient, errors -from pymongo.collection import Collection, IndexModel -from src.config.config import env -from logging import INFO, WARNING, getLogger - -logger = getLogger('uvicorn') - -class Database(): - - ID_LENGTH = 8 - - def __init__(self): - self.db = None - self.connect() - - - def connect(self): - try: - mongo_connection = MongoClient(env.DB_URL) - - logger.setLevel(INFO) - - self.db = mongo_connection[env.DB_NAME] - - print("--------------------") - logger.info("MongoDB connected!") - logger.info(f"Server Version: {mongo_connection.server_info()['version']}") - print("--------------------") - - - except errors.ServerSelectionTimeoutError as err: - - mongo_connection = None - logger.setLevel(WARNING) - logger.info(f"MongoDB connection error! {err}") - - def close_connection(self): - print("--------------------") - logger.info("MongoDB connection closed!") - print("--------------------") - self.db.client.close() - - def get_db(self): - return self.db - - - def create_collection( - self, - name: str, - indexes: List[IndexModel] = [], - validation_schema: Dict = {} - ) -> Collection: - """ - Create a collection - - Parameters - - name : str - The name of the collection to create - - indexes : List[IndexModel] - The indexes to create in the collection - - validation_schema : dict - The validation schema used to validate data inserted into the - collection. It should be a dictionary representing a JSON Schema - - Returns - - pymongo.collection.Collection - The created collection - - Raises - - TypeError: If indexes is not a list of pymongo.IndexModel - - """ - - collection_options = { "validator": { "$jsonSchema": validation_schema } } - - collection: Collection = self.db.create_collection( - name, - **collection_options - ) - - collection.create_indexes(indexes) - - logger.info(f"Collection {name} created!") - - return collection - - def drop_collection(self, name) -> bool: - """ - Drop a collection - - Parameters - - name : str - The name of the collection to drop - - Returns - - bool - True if the collection was dropped successfully, False otherwise - - """ - - if name in self.db.list_collection_names(): - self.db.drop_collection(name) - logger.info(f"Collection {name} dropped!") - return True - - return False - - def get_all_items(self, collection_name: str) -> list: - """ - Get all items from a collection - - Parameters: - - collection_name: str - The name of the collection - - Returns: - - list - A list of all items in the collection - - """ - - collection: Collection = self.db[collection_name] - - items = list(collection.find({}, {"_id": 0})) - - return items - - def get_item_by_id(self, collection_name: str, item_id: str) -> dict: - """ - Retrieve an item by its ID from a collection - - Parameters: - - collection_name: str - The name of the collection where the item will be stored - - item_id: str - The ID of the item to retrieve - - Returns: - - dict or None: - The item if found, None otherwise - - """ - collection: Collection = self.db[collection_name] - - item = collection.find_one({"id": str(item_id)}, {"_id": 0}) - return item - - def insert_item(self, collection_name: str, item: dict) -> dict: - """ - Insert an item into a collection - - Parameters: - - collection_name: str - The name of the collection where the item will be stored - - item: dict - The item to insert - - Returns: - - dict: - The inserted item - - """ - # TODO: test if this method works - - item["id"] = str(uuid4())[:self.ID_LENGTH] - - collection: Collection = self.db[collection_name] - - item_id = collection.insert_one(item).inserted_id - return { - "id": str(item_id), - **item - } - - # TODO: implement update_item method - # def update_item(self, collection_name: str, item_id: str, item: dict) -> dict: - """ - Update an item in a collection - - Parameters: - - collection_name: str - The name of the collection where the item is stored - - item_id: str - The ID of the item to update - - item: dict - New item data - - Returns: - - dict: - The updated item - - """ - - # TODO: implement delete_item method - # def delete_item(self, collection_name: str, item_id: str) -> list: - """ - Delete an item of a collection - - Parameters: - - collection_name: str - The name of the collection where the item is stored - - item_id: str - The ID of the item to delete - - Returns: - - list: - A list of all items in the collection. - - """ \ No newline at end of file diff --git a/backend/src/db/itens_database.py b/backend/src/db/itens_database.py new file mode 100644 index 00000000..6424f70f --- /dev/null +++ b/backend/src/db/itens_database.py @@ -0,0 +1,230 @@ +from typing import List, Dict +from uuid import uuid4 +from pymongo import MongoClient, errors +from pymongo.collection import Collection, IndexModel +#from src.config.config import env +from logging import INFO, WARNING, getLogger +from decimal import Decimal +import re +import os.path +import jsonpickle +from pydantic import BaseModel + +class DadosItem(BaseModel): + id: str # Acessos a database serão pelo ID (8 dígitos) + nome: str # Nome visível na interface + description: str + price: str + quantidade: int + img: str | None # Path para o arquivo + +logger = getLogger('uvicorn') + +class Item(): + """Classe que representa um item da database + + Criar com método new_item() + + Returns: + (Item, "SUCCESS"), ou (None, reason) caso o input não seja validado. + + reason será o nome do campo rejeitado pela validação + """ + id: str # Acessos a database serão pelo ID (8 dígitos) + nome: str # Nome visível na interface + description: str + price: str + quantidade: int + img: str | None # Path para o arquivo + ID_LENGTH = 8 + + def __init__(self, id: str, nome: str, description: str, price: str, quantidade: int, img: str | None = None): + self.id = id + self.nome = nome + self.description = description + self.price = price + self.quantidade = quantidade + self.img = img + + def to_dados_item(self): + return DadosItem( + id=self.id, + nome=self.nome, + description=self.description, + price=self.price, + quantidade=self.quantidade, + img=self.img + ) + + + @staticmethod + def is_image_path(path): + # Função para verificar se o path é válido para evitar SQL injection + # Ela considera extensões comuns de imagem e é case-insensitive (flag re.IGNORECASE) + pattern = re.compile(r"^[^.\n]+\.(jpg|jpeg|png|gif|bmp|tiff)$", re.IGNORECASE) + return re.match(pattern, path) is not None + + @staticmethod + def is_valid_price(price): + # Função para verificar se o preço do produto é válido + # Ela considera números no formato X.Y com X de no máximo 5 digitos e Y exatamente 2 + pattern = re.compile(r"^\d{1,5}\.\d{2}$", re.IGNORECASE) + return re.match(pattern, str(price)) is not None + + @staticmethod + def new_item(id: str, nome: str, description: str, price: str, quantidade: int, img: str | None = None): + """Cria novo item, validando-o de acordo com validade do path e tamanho do ID + + Args: + id: str + nome: str + description: str + price: str -> Conversão em decimal feita na hora de fazer cálculos + quantidade: int + img: str | None -> Path do arquivo + + Returns: + (Item, "SUCESS"), ou (None, reason) caso o input não seja validado. + + reason será a lista dos campos rejeitados pela validação. ["SUCCESS"] se o user for validado. + """ + + reason = [] + print("Entrou em new_item") + print(id) + # Verifica se imagem tem um formato sustentado + if img is not None and not Item.is_image_path(img): + reason.append("Caminho da imagem mal formulado") + + # Verifica se ID tem 8 dígitos + if str(id).__len__() != Item.ID_LENGTH: + reason.append("ID com tamanho inválido") + + if not Item.is_valid_price(price): + reason.append("Preço com formato inválido") + + obj = None + if reason.__len__() == 0: + reason.append("SUCCESS") + obj = Item(id, nome, description, price, quantidade, img) + + return (obj, reason) + +class ItemDatabase(): + db: dict[Item] + file_path:str + + def __init__(self, path: str = "Itens.json"): + self.db = dict() + self.file_path = path + self.try_read_from_file() + + def try_read_from_file(self): + # Ler itens do arquivo + if not os.path.exists(self.file_path): + self.write_to_file() + return None + + with open(self.file_path) as file: + itens = file.read() + db = jsonpickle.decode(itens) + if type(db) == dict: + self.db = db + + def write_to_file(self): + objetos = jsonpickle.encode(self.db) + with open(self.file_path, 'w+') as file: + file.write(objetos) + + def get_itens_list(self, update = True): + """Retorna todos os itens da database""" + if update: + self.try_read_from_file() + return list(self.db.values()) + + def add_new_item(self, item: Item, update: bool = True): + """Adicionar um novo item a database + + Args: + item (Item): Item em questão + + Returns: + success (bool): True para operação bem sucedida, False para mal sucedida + reason (list[str]): contém "Item com mesmo ID já na base de dados" se for um item já existente. + ["SUCCESS"] caso tenha sido uma operação bem sucedida + """ + reason = [] + if update: + self.try_read_from_file() + if ItemDatabase.get_item_by_ID(item.id, False): + reason.append("Item com mesmo ID já na base de dados") + + if reason.__len__() > 0: + return (False, reason) + + self.db[item.id] = item + self.write_to_file() + return (True, ["SUCCESS"]) + + def remove_item_by_ID (self, item_id: str, update: bool = True) -> Item | None: + """ Remover um item da database + + Args: + item_id (int): ID do item em questão + + Returns: + success (bool): True para operação bem sucedida, False para mal sucedida + reason (list[str]): contém "NOT_FOUND" se o item não foi encontrado + ["SUCCESS"] caso tenha sido uma operação bem sucedida + """ + if update: + self.try_read_from_file() + toreturn = self.db.pop(item_id, None) + self.write_to_file() + return toreturn + + @staticmethod + def get_item_by_ID (self, item_id: str, update: bool = True) -> Item | None: + """ Acessar um item da database + + Args: + item_id (int): ID do item em questão + + Returns: + item (Item | None): Item se existe, None se não + """ + if update: + self.try_read_from_file() + for key,val in self.db.items(): + if val.id == item_id: + return val + return None + + def modify_item_by_ID (self, item_id: str, new_item: Item, update: bool = True): + """ Modificar um item da database + + Args: + item_id (int): ID do item em questão + new_item (Item): novos valores do item a ser modificado + + Returns: + success (bool): True para operação bem sucedida, False para mal sucedida + Item (Item | None): Se o item for encontrado. + """ + reason = [] + if update: + self.try_read_from_file() + + if ItemDatabase.get_item_by_ID(item_id, False): + reason.append("Item não encontrado") + return(False, reason) + + self.db[item_id] = new_item + self.write_to_file() + return (True, ["SUCCESS"]) + + + def clear_database(self): + self.db = dict() + self.write_to_file() + \ No newline at end of file diff --git a/backend/src/db/orders.json b/backend/src/db/orders.json new file mode 100644 index 00000000..e1e4520e --- /dev/null +++ b/backend/src/db/orders.json @@ -0,0 +1,51 @@ +{ + "111.222.333-44": [ + { + "id": 1, + "name": "Produto A", + "supplier_name": "Fornecedor A", + "type": "Type A", + "img": "XXXX", + "quantity": 2, + "price": 10.5, + "request_date": "15-12-2023", + "delivery_date": "21-12-2023", + "delivery_model": "Express delivery", + "_status": "On the way", + "cancel_reason": null, + "payment_method": "Credit card" + }, + { + "id": 2, + "name": "Produto B", + "supplier_name": "Fornecedor B", + "type": "Type B", + "img": "YYYY", + "quantity": 50, + "price": 50, + "request_date": "15-12-2023", + "delivery_date": "20-12-2023", + "delivery_model": "Traditional delivery", + "_status": "Canceled", + "cancel_reason": "n\u00e3o gostei", + "payment_method": "Ticket" + } + ], + "222.333.444-55": [ + { + "id": 3, + "name": "Produto C", + "supplier_name": "Fornecedor C", + "type": "Type C", + "img": "ZZZZ", + "quantity": 30, + "price": 2000, + "request_date": "15-12-2023", + "delivery_date": "27-12-2023", + "delivery_model": "Air delivery", + "_status": "Delivered", + "cancel_reason": null, + "payment_method": "Credit card" + } + ] +} \ No newline at end of file diff --git a/backend/src/db/orders_db.py b/backend/src/db/orders_db.py new file mode 100644 index 00000000..1d38a864 --- /dev/null +++ b/backend/src/db/orders_db.py @@ -0,0 +1,60 @@ +from typing import List, Dict +from logging import INFO, WARNING, getLogger +from datetime import datetime, timedelta +import json +import os + +logger = getLogger('uvicorn') + +database_orders = {} + +def read_file(database, file): + file_path = os.path.join(os.path.dirname(__file__), file) + with open(file_path, "r") as f: + return json.load(f) + +def write_file(database, file): + file_path = os.path.join(os.path.dirname(__file__), file) + with open(file_path, "w") as f: + json.dump(database, f, indent=4) + +def cancel_order_db(product_id:int, user_CPF: str,cancel_reason:str) -> (bool, {}): + + orders_db = read_file(database_orders, "orders.json") + + if (cancel_reason == ""): + return (False,{"No cancel reason": True}) + if user_CPF not in orders_db: + return (False, {"CPF not found": True}) + + + for order in orders_db[user_CPF]: + if order["id"] == product_id: + if order["_status"] == "Canceled": + return (False, {'canceled': True}) + elif order["_status"] == "Delivered": + return (False, {'delivered': True}) + elif order["_status"] == "On the way": + order["_status"] = "Canceled" + order["cancel_reason"] = cancel_reason + + write_file(orders_db, "orders.json") + + return(True, {}) + + return (False, {"ID not found": True}) + +def get_all_orders_db(user_CPF: str) -> (bool, []): + + orders_db = read_file(database_orders, "orders.json") + + canceled_orders = [] + + for order in orders_db[user_CPF]: + if order["_status"] == "Canceled": + canceled_orders.append(order) + + if len(canceled_orders) == 0: + return (False, []) + else: + return (True, canceled_orders) \ No newline at end of file diff --git a/backend/src/db/products.json b/backend/src/db/products.json new file mode 100644 index 00000000..29341f62 --- /dev/null +++ b/backend/src/db/products.json @@ -0,0 +1,39 @@ +{ + "1": + { + "id": 1, + "name": "Produto A", + "supplier_corporate_name": "Empresa A LTDA", + "supplier_name": "Fornecedor A", + "cep": "53010120", + "_type": "Notebook IdeiaPad Enovo", + "img": "XXXX", + "stock": 5, + "price": 3000 + }, + "2": + { + "id": 2, + "name": "Produto B", + "supplier_corporate_name": "Empresa B LTDA", + "supplier_name": "Fornecedor B", + "cep": "57690-000", + "_type": "Bola de futebol Penâlti", + "img": "YYYY", + "stock": 20, + "price": 70.50 + } + , + "3": + { + "id": 3, + "name": "Produto C", + "supplier_corporate_name": "Empresa C LTDA", + "supplier_name": "Fornecedor C", + "cep": "04109-130", + "_type": "Teclado Kogostech", + "img": "ZZZZ", + "stock": 15, + "price": 100 + } +} \ No newline at end of file diff --git a/backend/src/db/schemas/adress_schema.py b/backend/src/db/schemas/adress_schema.py new file mode 100644 index 00000000..b3dd32f8 --- /dev/null +++ b/backend/src/db/schemas/adress_schema.py @@ -0,0 +1,26 @@ +class Endereço(): + rua: str + numero: int + bairro: str + cidade: str + estado: str + cep: str + pais: str + complemento: str | None + + def __init__(self, rua: str, numero: int, bairro: str, cidade: str, estado: str, cep: str, pais: str, complemento: str | None = None): + self.rua = rua + self.numero = numero + self.bairro = bairro + self.cidade = cidade + self.estado = estado + self.cep = cep + self.pais = pais + self.complemento = complemento + + def __str__(self): + endereco_completo = f"{self.rua}, {self.numero}" + if self.complemento: + endereco_completo += f", {self.complemento}" + endereco_completo += f"\n{self.bairro}, {self.cidade} - {self.estado}\nCEP: {self.cep}\n{self.pais}" + return endereco_completo diff --git a/backend/src/db/schemas/item_schema.py b/backend/src/db/schemas/item_schema.py deleted file mode 100644 index 0dc8583a..00000000 --- a/backend/src/db/schemas/item_schema.py +++ /dev/null @@ -1,28 +0,0 @@ - -from src.db.schemas.model_schema import ModelSchema - - -class ItemSchema(ModelSchema): - bson_type: str = "object" - required: list = ["id", "name"] - properties: dict = { - "id": { - "bson_type": "string", - "description": "The item's unique identifier" - }, - "name": { - "bson_type": "string", - "description": "The item's name" - }, - "created_at": { - "bson_type": "string", - "description": "The item's creation time" - } - } - - def get(self) -> dict: - return { - "bson_type": self.bson_type, - "required": self.required, - "properties": self.properties - } \ No newline at end of file diff --git a/backend/src/db/schemas/model_schema.py b/backend/src/db/schemas/model_schema.py deleted file mode 100644 index 1d689c9c..00000000 --- a/backend/src/db/schemas/model_schema.py +++ /dev/null @@ -1,12 +0,0 @@ -from typing import List, Dict, Optional -from pydantic import BaseModel - -class ModelSchemaProperty(BaseModel): - bson_type: str - description: Optional[str] = None - -class ModelSchema(BaseModel): - bson_type: str - required: List['str'] - properties: Dict[str, Dict[str, ModelSchemaProperty]] - diff --git a/backend/src/db/schemas/order_schema.py b/backend/src/db/schemas/order_schema.py new file mode 100644 index 00000000..b55cb0a5 --- /dev/null +++ b/backend/src/db/schemas/order_schema.py @@ -0,0 +1,53 @@ +from pydantic import BaseModel +import datetime +from src.db.itens_database import Item + +class Order(BaseModel): + _id: int + name: str + supplier_name: str + _type: str + img: str | None + quantity: int = 1 + price: float + request_date: datetime.date + delivery_date: datetime.date + delivery_model: str + _status: str + cancel_reason: str | None + payment_method: str + +def convert_cart_to_orders(cart_items: list[Item]): + orders = [] + order_id = 1 + + # Agrupando itens por fornecedor + items_by_supplier = dict() + for item in cart_items: + items_by_supplier[item.supplier_name].append(item) + + # Criando pedidos + for supplier, items in items_by_supplier.items(): + total_price = sum(item.price * item.quantity for item in items) + total_quantity = sum(item.quantity for item in items) + + # Criar um pedido para cada fornecedor + order = Order( + _id=order_id, + name=', '.join([item.name for item in items]), + supplier_name=supplier, + _type="Agrupado", + img=None, # ou alguma lógica para definir a imagem + quantity=total_quantity, + price=total_price, + request_date=datetime.date.today(), + delivery_date=datetime.date.today() + datetime.timedelta(days=7), # Mudar para o cálculo do tempo de entrega + delivery_model="Padrão", + _status="Pendente", + cancel_reason=None, + payment_method="Cartão" # Exemplo + ) + orders.append(order) + order_id += 1 + + return orders \ No newline at end of file diff --git a/backend/src/db/serializers/item_serializers.py b/backend/src/db/serializers/item_serializers.py deleted file mode 100644 index 63241f0b..00000000 --- a/backend/src/db/serializers/item_serializers.py +++ /dev/null @@ -1,24 +0,0 @@ -def item_entity(item) -> dict: - """ - Returns a dict of the item entity - """ - return { - "name": item["name"], - "created_at": item["created_at"], - } - -def item_response_entity(item) -> dict: - """ - Returns a dict of the item response entity - """ - return { - "id": item["id"], - "name": item["name"], - "created_at": item["created_at"], - } - -def item_list_entity(items) -> list: - """ - Returns a list of the item entity - """ - return [item_entity(item) for item in items] \ No newline at end of file diff --git a/backend/src/db/serializers/schema_serializer.py b/backend/src/db/serializers/schema_serializer.py deleted file mode 100644 index 0047a2df..00000000 --- a/backend/src/db/serializers/schema_serializer.py +++ /dev/null @@ -1,42 +0,0 @@ - -def schema_properties_serializer(properties: dict) -> dict: - """ - Serialize a schema's properties to a dict. - - Parameters: - - properties: dict - The properties to serialize. - - Returns: - - dict - The serialized properties. - - """ - - return { - key: { - "bsonType": value['bson_type'], - "description": value['description'] - } - for key, value in properties.items() - } - -def schema_serializer(schema) -> dict: - """ - Serialize a ModelSchema to a dict. - - Parameters: - - schema: ModelSchema - The schema to serialize. - - Returns: - - dict - The serialized schema. - - """ - - return { - "bsonType": schema['bson_type'], - "required": schema['required'], - "properties": schema_properties_serializer(schema['properties']) - } \ No newline at end of file diff --git a/backend/src/db/store_database.py b/backend/src/db/store_database.py new file mode 100644 index 00000000..839eb9d9 --- /dev/null +++ b/backend/src/db/store_database.py @@ -0,0 +1,131 @@ + +from uuid import uuid4 +from logging import INFO, WARNING, getLogger +import jsonpickle +import sys + +if "pytest" in sys.modules: + database_path = "Stores teste.json" +else: + database_path = "Stores.json" + +class Store(object): + nome: str + categoria: str + email: str + cnpj: str + senha: str + + def new(CNPJ: str, Email: str, Senha: str, Categoria: str, Nome: str): + obj = Store(CNPJ, Email, Senha, Categoria, Nome) + return (obj) + + + def __init__(self, CNPJ: str, Email: str, Senha: str, Categoria: str, Nome: str): + self.nome = Nome + self.categoria = Categoria + self.cnpj = CNPJ + self.email = Email + self.senha = Senha + + + + def update_data(self, dados_store: dict): + self.nome = dados_store['nome'] + self.categoria = dados_store['categoria'] + self.email = dados_store['email'] + self.senha = dados_store['senha'] + + return "Success" + + def update_password(self, dados_store: dict): + self.senha = dados_store['senha'] + return "Success" + + def update_nome(self, dados_store: dict): + self.nome = dados_store['nome'] + return "Success" + + def update_categoria(self, dados_store: dict): + self.categoria = dados_store['categoria'] + return "Success" + + def update_email(self, dados_store: dict): + self.email = dados_store['email'] + return "Success" + + + +class StoreDatabase(): + db: dict[Store] + file_path: str + + def signup(self, store: Store): + success, reason = self.add_store(store) + return (success, reason) + + + def __init__(self, path = database_path): + self.db = dict() + self.file_path = path + self.try_read_from_file() + + def try_read_from_file(self): + # Ler stores de um arquivo + import os.path + if not os.path.exists(self.file_path): + self.write_to_file() + return None + + with open(self.file_path) as f: + objetos = f.read() + try: + db = jsonpickle.decode(objetos) + except: + print("corrupted or wrong database") + self.write_to_file() + if type(db) == dict: + self.db = db + + def write_to_file(self): + objetos = jsonpickle.encode(self.db) + with open(self.file_path, 'w+') as f: + f.write(objetos) + + + def add_store(self, store: Store, update = True): + reason = [] + if update: + self.try_read_from_file() + if self.get_store_by_cnpj(store.cnpj, False): + reason.append("CNPJ") + if reason.__len__() > 0: + return (False, reason) + + self.db[store.cnpj] = store + self.write_to_file() + return (True, ["SUCCESS"]) + + def get_store_by_cnpj(self, cnpj: str, update = True) -> Store | None: + if update: + self.try_read_from_file() + return self.db.get(cnpj) + + def get_store_by_name(self, name: str, update = True) -> Store | None: + if update: + self.try_read_from_file() + for key, val in self.db.items(): + if val.nome == name: + return val + return None + + def remove_store_by_cnpj(self, cnpj: str, update = True) -> Store | None: + if update: + self.try_read_from_file() + toreturn = self.db.pop(cnpj, None) + self.write_to_file() + return toreturn + + def clear_database(self): + self.db = dict() + self.write_to_file() \ No newline at end of file diff --git a/backend/src/db/time_arrival_db.py b/backend/src/db/time_arrival_db.py new file mode 100644 index 00000000..1f068bda --- /dev/null +++ b/backend/src/db/time_arrival_db.py @@ -0,0 +1,167 @@ +from typing import List, Dict +from logging import INFO, WARNING, getLogger +from datetime import datetime, timedelta +from math import radians, sin, cos, sqrt, atan2 +import json +import os +import requests + +logger = getLogger('uvicorn') + +database_user = {} +database_product = {} + +def read_file(database, file): + file_path = os.path.join(os.path.dirname(__file__), file) + with open(file_path, "r") as f: + return json.load(f) + +def validate_CEP(cep: str): + url = f'https://viacep.com.br/ws/{cep}/json/' + + api_response = requests.get(url).json() + + if ('cep' not in api_response) == True: + return False + else: + return True + +def haversine_distance(coord1, coord2): + # Raio médio da terra + R = 6371000.0 + + # Converte as coordenadas de graus para radianos + lat1, lon1 = radians(coord1[0]), radians(coord1[1]) + lat2, lon2 = radians(coord2[0]), radians(coord2[1]) + + # Diferenças nas coordenadas + dlat = lat2 - lat1 + dlon = lon2 - lon1 + + # Fórmula de Haversine + a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2 + c = 2 * atan2(sqrt(a), sqrt(1 - a)) + + # Distância em kilometros + distance = (R * c)/1000 + + return distance + +def regions_relations(CEP1, CEP2): + + url_cep1 = f'https://viacep.com.br/ws/{CEP1}/json/' + response_cep1 = requests.get(url_cep1) + + state1 = (response_cep1.json()['uf']) + + url_cep2 = f'https://viacep.com.br/ws/{CEP2}/json/' + response_cep2 = requests.get(url_cep2) + + state2 = (response_cep2.json()['uf']) + + brasil = {'ne' : ['AL','BA','CE','MA','PB','PI','PE','RN','SE'], + 'no' : ['AC','AM','AP','PA','RO','RR','TO'], + 'co' : ['GO','MS','MT'], + 'se' : ['ES','MG','RJ','SP'], + 's' : ['PR','RS','SC'] + } + + same_region = False + + for key in brasil: + if state1 in brasil[key]: + region_state1 = key + + if state2 in brasil[region_state1]: + same_region = True + + if state1 == state2: + return 'same states' + elif same_region: + return 'same regions' + else: + return 'different regions' + +def calculate_distance(product_CEP: str, user_CEP: str): + url_product = f'https://brasilapi.com.br/api/cep/v2/{product_CEP}' + response_product = requests.get(url_product) + + product_coordinates = (float(response_product.json()['location']['coordinates']['latitude']),float(response_product.json()['location']['coordinates']['longitude'])) + + url_user = f'https://brasilapi.com.br/api/cep/v2/{user_CEP}' + response_user = requests.get(url_user) + + user_coordinates = (float(response_user.json()['location']['coordinates']['latitude']),float(response_user.json()['location']['coordinates']['longitude'])) + + distance = haversine_distance(product_coordinates,user_coordinates) + + return distance + +def calculate_date(distance, transportation_type): + if transportation_type == 'traditional': + average_speed = 60 + elif transportation_type == 'express': + average_speed = 80 + else: + average_speed = 100 + + begin_day = datetime.now() + max_daily_time = 6 # h + remaining_distance = distance # km + total_time = 0 # h + days_worked = 0 + delivery_date = 0 + logistic_days = 4 + + while remaining_distance > 0: + # Calculate the hours to run on day + daily_time_hours = min(max_daily_time, remaining_distance / average_speed) + + # Update remaining distance + remaining_distance -= daily_time_hours * average_speed + + days_worked += 1 + + # Discounting rest days + if days_worked > 6: + delivery_date += 3 + days_worked = 0 + else: + delivery_date += 1 + + # Convertendo para um objeto datetime no final + delivery_date = begin_day + timedelta(days=delivery_date) + timedelta(days=logistic_days) + formatted_date = delivery_date.strftime("%d-%m-%Y") + + return formatted_date + +def calculate_time_arrival_db(id: int, user_CPF: str) -> (bool,{}): + + # Read from the JSON with users the right cep + users_db = read_file(database_user, "users.json") + user_CEP = users_db[user_CPF]["CEP"] + + # Exception for the CEP invalid + if not validate_CEP(user_CEP): + return (False,{'error': True}) + else: + id_str = str(id) + + # Read from the JSON with orders the right cep + products_db = read_file(database_product,"products.json") + product_CEP = products_db[id_str]["cep"] + + # Check the type of transportation based on the CEP states + if regions_relations(product_CEP, user_CEP) == 'same states': + transportation = 'Traditional delivery' + elif regions_relations(product_CEP, user_CEP) == 'same regions': + transportation = 'Express delivery' + else: + transportation = 'Air delivery' + distance = calculate_distance(product_CEP, user_CEP) + + delivery_date = calculate_date(distance, transportation) + + request_date = datetime.now().strftime("%d-%m-%Y") + + return (True, {'delivery_model': transportation, 'request_date':request_date,'delivery_date': delivery_date}) \ No newline at end of file diff --git a/backend/src/db/unittest_is_image_path.py b/backend/src/db/unittest_is_image_path.py new file mode 100644 index 00000000..3a3d66d3 --- /dev/null +++ b/backend/src/db/unittest_is_image_path.py @@ -0,0 +1,21 @@ +import unittest +from itens_database import Item + +class Test_is_image_path(unittest.TestCase): + def test_path(self): + self.assertTrue(Item.is_image_path("foto.jpg")) + self.assertTrue(Item.is_image_path("imagem.PNG")) + self.assertTrue(Item.is_image_path("picture.png")) + + def test_path_with_other_extensions(self): + self.assertFalse(Item.is_image_path("documento.pdf")) + self.assertFalse(Item.is_image_path("arquivo.txt")) + self.assertFalse(Item.is_image_path("?.extensao")) + + def test_path_with_more_than_one_point(self): + self.assertFalse(Item.is_image_path("arquivo.com.jpg")) + self.assertFalse(Item.is_image_path("documento.pdf.jpg")) + self.assertFalse(Item.is_image_path("item..jpg")) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/backend/src/db/unittest_new_item.py b/backend/src/db/unittest_new_item.py new file mode 100644 index 00000000..8acfc348 --- /dev/null +++ b/backend/src/db/unittest_new_item.py @@ -0,0 +1,44 @@ +import unittest +from itens_database import Item + +class Test_new_item(unittest.TestCase): + def test_valid_item(self): + self.assertEqual((Item.new_item(self, + id=12345678, + nome="Camisa vermelha", + description="Camisa de linho vermelha", + price="29.99", + quantidade=8, + img="image.png"))[1][0], "SUCCESS" + ) + def test_valid_item_no_img(self): + self.assertEqual((Item.new_item(self, + id=12345678, + nome="Camisa vermelha", + description="Camisa de linho vermelha", + price="29.99", + quantidade=8))[1][0], "SUCCESS" + ) + + def test_invalid_item_ID_length(self): + self.assertEqual((Item.new_item(self, + id=123456789, # 9 digitos + nome="Camisa vermelha", + description="Camisa de linho vermelha", + price="29.99", + quantidade=8))[1][0], "ID_LENGTH" + ) + + def test_invalid_item_invalid_img(self): + self.assertEqual((Item.new_item(self, + id=12345678, + nome="Camisa vermelha", + description="Camisa de linho vermelha", + price="29.99", + quantidade=8, + img="aaaaa"))[1][0], "PATH" + ) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/backend/src/db/user_database.py b/backend/src/db/user_database.py index 1c27b029..b9948670 100644 --- a/backend/src/db/user_database.py +++ b/backend/src/db/user_database.py @@ -182,6 +182,10 @@ def write_to_file(self): with open(self.file_path, 'w+') as f: f.write(objetos) + + def valid_password(self, senha:str): + return senha_pattern.match(senha) + def add_user(self, user: User, update = True): """Adicionar um novo usuário a database diff --git a/backend/src/db/users.json b/backend/src/db/users.json new file mode 100644 index 00000000..0dec24c6 --- /dev/null +++ b/backend/src/db/users.json @@ -0,0 +1,35 @@ +{ + "331.704.260-52": + { + "username": "user1", + "name": "Fulano", + "last_name": "da Silva", + "cpf": "331.704.260-52", + "date_of_birth": "20-02-2004", + "email": "usuario@gmail.com", + "address": "Rua 57", + "CEP": "00870-011" + }, + "111.111.111-11": + { + "username": "user2", + "name": "Cicrano", + "last_name": "das Dores", + "cpf": "111.111.111-11", + "date_of_birth": "18-02-2003", + "email": "usuario2@gmail.com", + "address": "Rua 58", + "CEP": "05508220" + }, + "222.222.222-22": + { + "username": "user3", + "name": "Beltrano", + "last_name": "Santos", + "cpf": "222.222.222-22", + "date_of_birth": "20-04-1998", + "email": "usuario3@gmail.com", + "address": "Avenida Getulio Vargas", + "CEP": "52120-306" + } +} \ No newline at end of file diff --git a/backend/src/schemas/carrinho_response.py b/backend/src/schemas/carrinho_response.py new file mode 100644 index 00000000..9cf45dba --- /dev/null +++ b/backend/src/schemas/carrinho_response.py @@ -0,0 +1,56 @@ +from typing import Optional +from pydantic import BaseModel +from .response import HttpResponseModel + +class HTTPCarrinhoResponses(): + """ Respostas HTTP padronizadas para o carrinho """ + @staticmethod + def CART_NOT_FOUND() -> HttpResponseModel: + return HttpResponseModel( + message="Carrinho não encontrado", + status_code=404, + ) + + @staticmethod + def REMOVE_ITEM_SUCCESSFULLY() -> HttpResponseModel: + return HttpResponseModel( + message = "Item removido com sucesso", + status_code=200, + ) + + @staticmethod + def DECREASE_ITEM_QUANTITY(reason: str) -> HttpResponseModel: + return HttpResponseModel( + message= reason, + status_code=200 + ) + + @staticmethod + def MODIFY_ITEM_QUANTITY() -> HttpResponseModel: + return HttpResponseModel( + message= "Quantidade do item alterada com sucesso", + status_code=200 + ) + + @staticmethod + def CLEAR_CART(success: bool) -> HttpResponseModel: + if success: + return HttpResponseModel( + message= "Conteúdo do carrinho limpo com sucesso", + status_code=200 + ) + return HTTPCarrinhoResponses.CART_NOT_FOUND() + + @staticmethod + def CLEAR_ALL_CARTS() -> HttpResponseModel: + return HttpResponseModel( + message= "Conteúdo da database de carrinhos limpo", + status_code=200 + ) + + @staticmethod + def MODIFY_ADRESS_SUCCESFULLY() -> HttpResponseModel: + return HttpResponseModel( + message="Endereço alterado com sucesso", + status_code=200 + ) \ No newline at end of file diff --git a/backend/src/schemas/item.py b/backend/src/schemas/item.py deleted file mode 100644 index c3d00be0..00000000 --- a/backend/src/schemas/item.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import Optional -from datetime import datetime -from pydantic import BaseModel - -class ItemModel(BaseModel): - name: str - created_at: Optional[datetime] - - -class ItemGet(BaseModel): - id: str - name: str - created_at: Optional[datetime] - -class ItemList(BaseModel): - items: list[ItemGet] \ No newline at end of file diff --git a/backend/src/schemas/item_database_response.py b/backend/src/schemas/item_database_response.py new file mode 100644 index 00000000..6eff4669 --- /dev/null +++ b/backend/src/schemas/item_database_response.py @@ -0,0 +1,60 @@ +from typing import Optional +from pydantic import BaseModel +from .response import HttpResponseModel + +class HTTPDatabaseResponses: + + """ + Contém respostas http referentes a acessos na base de dados de itens + """ + + @staticmethod + def ITEM_ALREADY_EXISTS(reason_list: list[str]) -> HttpResponseModel: + data = [] + for reason in reason_list: + data.append(reason) + + return HttpResponseModel( + message = "Já existe um item com esse ID", + data = data, + status_code = 400 + ) + + @staticmethod + def NO_ITEM_IN_DATABASE() -> HttpResponseModel: + return HttpResponseModel( + message = "Nenhum item encontrado na base de dados", + status_code=404, + ) + + @staticmethod + def BAD_REQUEST(reason_list: list[str]) -> HttpResponseModel: + # Cria uma mensagem detalhada com base na lista de razões + detailed_message = "Solicitação mal feita. Motivos: " + "; ".join(reason_list) + + return HttpResponseModel( + message=detailed_message, + status_code=400 + ) + + + @staticmethod + def ADD_ITEM_SUCCESSFULLY() -> HttpResponseModel: + return HttpResponseModel( + message = "Item cadastrado com sucesso", + status_code=200, + ) + + @staticmethod + def REMOVE_ITEM_SUCCESSFULLY() -> HttpResponseModel: + return HttpResponseModel( + message = "Item removido com sucesso", + status_code=200, + ) + + @staticmethod + def MODIFY_ITEM_SUCCESSFULLY() -> HttpResponseModel: + return HttpResponseModel( + message = "Item modificado com sucesso", + status_code=200, + ) \ No newline at end of file diff --git a/backend/src/schemas/orders_response.py b/backend/src/schemas/orders_response.py new file mode 100644 index 00000000..f11a2312 --- /dev/null +++ b/backend/src/schemas/orders_response.py @@ -0,0 +1,63 @@ +from typing import Optional +from pydantic import BaseModel +from .response import HttpResponseModel + +class HTTPOrdersResponse: + + @staticmethod + def CANCEL_SUCCESSFULLY() -> HttpResponseModel: + return HttpResponseModel ( + message="Pedido cancelado com sucesso!", + status_code=200, + ) + + @staticmethod + def BAD_REQUEST(errors) -> HttpResponseModel: + + if ('canceled' in errors): + return HttpResponseModel ( + message="O pedido já foi cancelado!", + status_code= 400, + ) + elif ('delivered' in errors): + return HttpResponseModel ( + message="O pedido já foi entregue!", + status_code= 400, + ) + elif ('No cancel reason' in errors): + return HttpResponseModel ( + message="O usuário não colocou a razão de cancelamento!", + status_code= 400 + ) + elif ('CPF not found' in errors): + return HttpResponseModel ( + message="Não consta pedidos no CPF", + status_code= 400 + ) + elif ("ID not found" in errors): + return HttpResponseModel( + message="O usuário não tem pedido com este ID", + status_code=400 + ) + else: + return HttpResponseModel( + message="Bad request", + status_code= 400 + ) + + @staticmethod + def GET_SUCCESSFULLY(orders_canceled) -> HttpResponseModel: + + return HttpResponseModel ( + message="Pedido obtidos com sucesso", + status_code=200, + data=orders_canceled + ) + + @staticmethod + def NO_CANCEL_ORDERS() -> HttpResponseModel: + + return HttpResponseModel ( + message="O usuário não tem pedidos cancelados", + status_code=400 + ) diff --git a/backend/src/schemas/provisory_schemas.py b/backend/src/schemas/provisory_schemas.py new file mode 100644 index 00000000..a286bb6a --- /dev/null +++ b/backend/src/schemas/provisory_schemas.py @@ -0,0 +1,53 @@ +from pydantic import BaseModel +from typing import List, Optional +import datetime + +# All these schemas are focus on other features, but I created them before get the schemas of others group members to test calculate the estimative of products arrival and cancel orders + +class Supplier(BaseModel): + + name: str + corporate_name: str + CNPJ: str + CEP: str + email: str + sector: str + +class Product(BaseModel): + + _id: int + name: str + supplier_corporate_name: str + supplier_name: str + CEP: str + _type: str + img: str | None + stock: int + price: float + +class Order(BaseModel): + + _id: int + name: str + supplier_name: str + _type: str + img: str | None + quantity: int = 1 + price: float + request_date: datetime.date + delivery_date: datetime.date + delivery_model: str + _status: str + cancel_reason: str | None + payment_method: str + +class User(BaseModel): + + username: str + name: str + last_name: str + cpf: str + date_of_birth: datetime.date + email: str + address: str | None = None + CEP: str | None = None \ No newline at end of file diff --git a/backend/src/schemas/store_response.py b/backend/src/schemas/store_response.py new file mode 100644 index 00000000..76d6d817 --- /dev/null +++ b/backend/src/schemas/store_response.py @@ -0,0 +1,109 @@ +from typing import Optional +from pydantic import BaseModel +from .response import HttpResponseModel + +class HTTPSignUpResponses: + + + @staticmethod + def CNPJ_ALREADY_EXIST() -> HttpResponseModel: + return HttpResponseModel( + message = "Já existe uma loja registrada com esse CNPJ", + status_code=401, + ) + + @staticmethod + def NAME_ALREADY_EXIST() -> HttpResponseModel: + return HttpResponseModel( + message = "Já existe uma loja registrada com esse Nome", + status_code=401, + ) + + @staticmethod + def ALREADY_EXIST() -> HttpResponseModel: + return HttpResponseModel( + message = "Já existe uma loja registrada com esses dados", + status_code=401, + ) + + @staticmethod + def BAD_REQUEST() -> HttpResponseModel: + return HttpResponseModel( + message="Request mal feito", + status_code=400 + ) + + @staticmethod + def SIGNUP_SUCCESSFUL() -> HttpResponseModel: + return HttpResponseModel( + message = "Loja cadastrada com sucesso", + status_code=200, + ) + +class HTTPLoginResponses: + + @staticmethod + def STORE_NOT_FOUND() -> HttpResponseModel: + return HttpResponseModel( + message = "CNPJ ou Senha incorretos", + status_code = 401 + ) + + @staticmethod + def LOGIN_SUCCESSFUL() -> HttpResponseModel: + return HttpResponseModel( + message = "Login com sucesso", + status_code=200, + ) + + @staticmethod + def LOGIN_FAILED() -> HttpResponseModel: + return HttpResponseModel( + message = "Login falhou, essa loja não deve estar cadastrada", + status_code = 400 + ) + + + +class HTTPUpdateStoreResponses: + + @staticmethod + def STORE_NOT_FOUND_UPDATE() -> HttpResponseModel: + return HttpResponseModel( + message = "CNPJ ou Email incorretos", + status_code = 401 + ) + + @staticmethod + def REMOVE() -> HttpResponseModel: + return HttpResponseModel( + message="loja deletada", + status_code=200 + ) + + @staticmethod + def REMOVE_FAIL() -> HttpResponseModel: + return HttpResponseModel( + message="Deletar loja falhou", + status_code=400 + ) + + @staticmethod + def UPDATE_FAIL() -> HttpResponseModel: + return HttpResponseModel( + message="Atualizar dados de usuário falhou", + status_code=400 + ) + + @staticmethod + def UPDATE_SUCCESS() -> HttpResponseModel: + return HttpResponseModel( + message="Atualização de dados bem sucedida", + status_code=200 + ) + + def UNAUTORIZED() -> HttpResponseModel: + return HttpResponseModel( + message="Não tem autorização para realizar essa requisição", + status_code=401 + ) \ No newline at end of file diff --git a/backend/src/schemas/time_arrival_response.py b/backend/src/schemas/time_arrival_response.py new file mode 100644 index 00000000..fd9a07d4 --- /dev/null +++ b/backend/src/schemas/time_arrival_response.py @@ -0,0 +1,22 @@ +from typing import Optional +from pydantic import BaseModel +from .response import HttpResponseModel + +class HTTPTimeArrivalResponse: + + @staticmethod + def GET_SUCCESSFULLY(data_param) -> HttpResponseModel: + return HttpResponseModel ( + message="Tempo estimado do produto calculado com sucesso!", + status_code=200, + data= data_param + ) + + @staticmethod + def BAD_REQUEST(errors) -> HttpResponseModel: + + return HttpResponseModel ( + message="CEP do usuário inválido!", + status_code= 400, + data=["User CEP"] + ) \ No newline at end of file diff --git a/backend/src/service/impl/carrinho_service.py b/backend/src/service/impl/carrinho_service.py new file mode 100644 index 00000000..a00d1bef --- /dev/null +++ b/backend/src/service/impl/carrinho_service.py @@ -0,0 +1,129 @@ +from src.schemas.response import HTTPResponses, HttpResponseModel +from src.db.__init__ import cart_database as db +from src.db.carrinho_database import Carrinho, Carrinhos +from src.db.itens_database import Item +from src.schemas.item_database_response import HTTPDatabaseResponses +from pydantic import BaseModel +from src.schemas.carrinho_response import HTTPCarrinhoResponses +from src.db.itens_database import DadosItem + +class DadosEndereço(BaseModel): + rua: str + numero: int + bairro: str + cidade: str + estado: str + cep: str + pais: str + complemento: str | None + +class Carrinho_service(): + + @staticmethod + def get_cart(CPF: str, database: Carrinhos = db) -> HttpResponseModel: + """ Tenta obter um carrinho, se não conseguir criar um novo para o CPF selecionado """ + print("Entrou em get_cart") + carrinho = database.get_cart_by_CPF(CPF= CPF) + if carrinho is None: + print("Entrou em carrinho is none") + carrinho = Carrinho(CPF=CPF) + (success, reason) = database.add_new_cart(carrinho) + item_list = [item.to_dados_item() for item in carrinho.get_all_items()] + return HttpResponseModel( + message="Carrinho não encontrado, novo carrinho criado vinculado a este CPF", + status_code=HTTPResponses.ITEM_CREATED().status_code, + data={"Itens:": item_list, "Total": carrinho.total, "Endereço": carrinho.get_adress()}, + ) + item_list = [item.to_dados_item() for item in carrinho.get_all_items()] + return HttpResponseModel( + message=HTTPResponses.ITEM_FOUND().message, + status_code=HTTPResponses.ITEM_FOUND().status_code, + data={"Itens:": item_list, "Total": carrinho.total, "Endereço": carrinho.get_adress()}, + ) + + @staticmethod + def get_all_carts(database: Carrinhos = db) -> HttpResponseModel: + cart_database = database.get_cart_list() + # cart_database é uma lista de objetos do tipo Carrinho. Transformar em um dicionário de cpf: {"Itens": lista de itens,"Total": total} + cart_dict = dict() + for carrinho in cart_database: + lista_itens = carrinho.get_all_items() + carrinho_formatado = {"Itens": lista_itens, "Total": carrinho.total} + cart_dict[carrinho.CPF] = carrinho_formatado + return HttpResponseModel( + message=HTTPResponses.ITEM_FOUND().message, + status_code=HTTPResponses.ITEM_FOUND().status_code, + data=cart_dict, + ) + + @staticmethod + def add_item_to_cart(item_data: DadosItem, CPF: str, database: Carrinhos = db): + """Tenta adicionar um novo item no banco de dados""" + print("Entrou em add_item_to_cart") + print(item_data) + item_data_dict = item_data.model_dump() # Se isso retornar um dicionário com as chaves corretas + (item, reason) = Item.new_item(**item_data_dict) + if item is None: + print("Entrou item is None") + return HTTPDatabaseResponses.BAD_REQUEST(reason) + (success, reason) = database.add_item_to_cart(item=item, CPF= CPF) + + if success: + return HTTPDatabaseResponses.ADD_ITEM_SUCCESSFULLY() + else: + return HTTPCarrinhoResponses.CART_NOT_FOUND() + + @staticmethod + def remove_item_from_cart(item_id: str, CPF: str, database: Carrinhos = db) -> HttpResponseModel: + (success, reason) = database.remove_item_from_cart(item_id= item_id, CPF= CPF) + if not success: + return HttpResponseModel( + message=reason[0], + status_code=HTTPResponses.ITEM_NOT_FOUND().status_code, + ) + return HttpResponseModel( + message=HTTPDatabaseResponses.REMOVE_ITEM_SUCCESSFULLY().message, + status_code=HTTPDatabaseResponses.REMOVE_ITEM_SUCCESSFULLY().status_code, + ) + + @staticmethod + def decrease_item_quantity(item_id: str, CPF: str, database: Carrinhos = db) -> HttpResponseModel: + """Tenta remover um na quantidade do item no carrinho""" + (success, reason) = database.decrease_item_quantity(item_id=item_id, CPF=CPF) + if not success: + return HttpResponseModel( + message=reason[0], + status_code=HTTPResponses.ITEM_NOT_FOUND().status_code, + ) + return HTTPCarrinhoResponses.DECREASE_ITEM_QUANTITY(reason[0]) + + @staticmethod + def increase_item_quantity(item_id: str, CPF: str, database: Carrinhos = db) -> HttpResponseModel: + """Tenta adicionar um na quantidade do item no carrinho""" + (success, reason) = database.increase_item_quantity(item_id= item_id, CPF= CPF) + if not success: + return HttpResponseModel( + message=reason[0], + status_code=HTTPResponses.ITEM_NOT_FOUND().status_code + ) + return HTTPCarrinhoResponses.MODIFY_ITEM_QUANTITY() + + @staticmethod + def clear_cart_by_CPF(CPF: str, database: Carrinhos = db) -> HttpResponseModel: + """ Tenta limpar um carrinho da base de dados """ + print("Entrou em clear_cart_by_CPF") + success = database.clear_cart_by_CPF(CPF=CPF) + return HTTPCarrinhoResponses.CLEAR_CART(success) + + @staticmethod + def clear_all_carts(database: Carrinhos = db) -> HttpResponseModel: + database.clear_cart_database() + return HTTPCarrinhoResponses.CLEAR_ALL_CARTS() + + @staticmethod + def add_adress(adressData: DadosEndereço, CPF: str, database: Carrinhos = db) -> HttpResponseModel: + adressData_dict = adressData.model_dump() # Se isso retornar um dicionário com as chaves corretas + (success, reason) = database.alterar_endereco_de_carrinho_por_CPF(CPF, **adressData_dict) + if success: + return HTTPCarrinhoResponses.MODIFY_ADRESS_SUCCESFULLY() + return HTTPCarrinhoResponses.CART_NOT_FOUND() \ No newline at end of file diff --git a/backend/src/service/impl/item_database_service.py b/backend/src/service/impl/item_database_service.py new file mode 100644 index 00000000..79175721 --- /dev/null +++ b/backend/src/service/impl/item_database_service.py @@ -0,0 +1,86 @@ +from src.schemas.response import HTTPResponses, HttpResponseModel +from src.db.__init__ import ItemDatabase as db +from src.db.itens_database import Item, ItemDatabase +from src.schemas.item_database_response import HTTPDatabaseResponses +from pydantic import BaseModel + +class DadosItem(BaseModel): + id: str # Acessos a database serão pelo ID (8 dígitos) + nome: str # Nome visível na interface + description: str + price: str + quantidade: int + img: str | None # Path para o arquivo + +class ItemService: + + @staticmethod + def get_item(item_id: str) -> HttpResponseModel: + item = ItemDatabase.get_item_by_ID(item_id= item_id) + if item is None: + return HttpResponseModel( + message=HTTPResponses.ITEM_NOT_FOUND().message, + status_code=HTTPResponses.ITEM_NOT_FOUND().status_code, + ) + return HttpResponseModel( + message=HTTPResponses.ITEM_FOUND().message, + status_code=HTTPResponses.ITEM_FOUND().status_code, + data=item, + ) + + @staticmethod + def get_items() -> HttpResponseModel: + items = ItemDatabase.get_itens_list() + if items.__len__() == 0: + return HttpResponseModel( + message=HTTPDatabaseResponses.NO_ITEM_IN_DATABASE().message, + status_code=HTTPDatabaseResponses.NO_ITEM_IN_DATABASE().status_code, + ) + + return HttpResponseModel( + message=HTTPResponses.ITEM_FOUND().message, + status_code=HTTPResponses.ITEM_FOUND().status_code, + data=items, + ) + + @staticmethod + def add_new_item(item_data: DadosItem, database = db): + """Tenta adicionar um novo item no banco de dados""" + (item, reason) = Item.new_item(*item_data.model_dump().values()) + if item is None: + return HTTPDatabaseResponses.BAD_REQUEST(reason) + (success, reason) = db.add_new_item(item=item) + + if success: + return HTTPDatabaseResponses.ADD_ITEM_SUCCESSFULLY() + else: + return HTTPDatabaseResponses.ITEM_ALREADY_EXISTS(reason) + + @staticmethod + def remove_item(item_id: str) -> HttpResponseModel: + item = ItemDatabase.remove_item_by_ID(item_id= item_id) + if item is None: + return HttpResponseModel( + message=HTTPResponses.ITEM_NOT_FOUND().message, + status_code=HTTPResponses.ITEM_NOT_FOUND().status_code, + ) + return HttpResponseModel( + message=HTTPDatabaseResponses.REMOVE_ITEM_SUCCESSFULLY.message, + status_code=HTTPDatabaseResponses.REMOVE_ITEM_SUCCESSFULLY.status_code, + data=item, + ) + + @staticmethod + def modify_item(item_id: str, new_item_data: DadosItem) -> HttpResponseModel: + """Tenta modificar um item do banco de dados. Na prática só associa os novos dados do item ao id do alvo.""" + (item, reason) = Item.new_item(*new_item_data.model_dump().values()) + if item is None: + return HTTPDatabaseResponses.BAD_REQUEST(reason) + (success, reason) = ItemDatabase.modify_item_by_ID(item_id= item_id, new_item=item) + if success: + return HTTPDatabaseResponses.MODIFY_ITEM_SUCCESSFULLY() + else: + return HttpResponseModel( + message=HTTPResponses.ITEM_NOT_FOUND().message, + status_code=HTTPResponses.ITEM_NOT_FOUND().status_code, + ) \ No newline at end of file diff --git a/backend/src/service/impl/item_service.py b/backend/src/service/impl/item_service.py deleted file mode 100644 index 762ea8bf..00000000 --- a/backend/src/service/impl/item_service.py +++ /dev/null @@ -1,38 +0,0 @@ -from src.schemas.response import HTTPResponses, HttpResponseModel -from src.service.meta.item_service_meta import ItemServiceMeta -from src.db.__init__ import database as db - -class ItemService(ItemServiceMeta): - - @staticmethod - def get_item(item_id: str) -> HttpResponseModel: - """Get item by id method implementation""" - item = db.get_item_by_id('items', item_id) - if not item: - return HttpResponseModel( - message=HTTPResponses.ITEM_NOT_FOUND().message, - status_code=HTTPResponses.ITEM_NOT_FOUND().status_code, - ) - return HttpResponseModel( - message=HTTPResponses.ITEM_FOUND().message, - status_code=HTTPResponses.ITEM_FOUND().status_code, - data=item, - ) - - @staticmethod - def get_items(): - """Get items method implementation""" - items = db.get_all_items('items') - if not items: - return HttpResponseModel( - message=HTTPResponses.ITEM_NOT_FOUND().message, - status_code=HTTPResponses.ITEM_NOT_FOUND().status_code, - ) - - return HttpResponseModel( - message=HTTPResponses.ITEM_FOUND().message, - status_code=HTTPResponses.ITEM_FOUND().status_code, - data=items, - ) - - # TODO: implement other methods (create, update, delete) \ No newline at end of file diff --git a/backend/src/service/impl/orders_service.py b/backend/src/service/impl/orders_service.py new file mode 100644 index 00000000..ee9bac1b --- /dev/null +++ b/backend/src/service/impl/orders_service.py @@ -0,0 +1,26 @@ +from src.schemas.provisory_schemas import Product, Order, User +from src.schemas.response import HTTPResponses, HttpResponseModel +from src.schemas.orders_response import HTTPOrdersResponse +from src.db.orders_db import cancel_order_db, get_all_orders_db + +class OrdersService: + + @staticmethod + def cancel_order_service(product_id: int, user_CPF: str, cancel_reason: str) -> HttpResponseModel: + + success, data = cancel_order_db(product_id, user_CPF, cancel_reason) + + if success: + return HTTPOrdersResponse.CANCEL_SUCCESSFULLY() + else: + return HTTPOrdersResponse.BAD_REQUEST(data) + + @staticmethod + def get_all_orders_service(user_CPF: str) -> HttpResponseModel: + + success, data = get_all_orders_db(user_CPF) + + if success: + return HTTPOrdersResponse.GET_SUCCESSFULLY(data) + else: + return HTTPOrdersResponse.NO_CANCEL_ORDERS() \ No newline at end of file diff --git a/backend/src/service/impl/recuperation_service.py b/backend/src/service/impl/recuperation_service.py new file mode 100644 index 00000000..c9ea65ab --- /dev/null +++ b/backend/src/service/impl/recuperation_service.py @@ -0,0 +1,77 @@ +from src.db.__init__ import user_database as db_user +from src.db.user_database import UserDatabase +from src.db.codigos_rec_database import RecuperacaoDatabase +from src.db.__init__ import recuperacao_database as db_recuperacao +from src.db.codigos_rec_database import Recuperacao +import random, string, smtplib +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from datetime import datetime, timedelta + +class RecuperationService: + @staticmethod + def enviar_email(email :str, db_user: UserDatabase = db_user, db_recuperacao: RecuperacaoDatabase = db_recuperacao): + + if not db_user.get_user_by_email(email): + return False + + codigo = ''.join(random.choices(string.digits, k=6)) + + recuperacao = Recuperacao(email, codigo) + db_recuperacao.add_recuperacao(recuperacao) + + email_remetente = "emailprojetoess@gmail.com" + senha_email_remetente = "yayv sgbc urzy frua" + + # Criação do objeto MIMEMultipart + msg = MIMEMultipart() + msg['From'] = email_remetente + msg['To'] = email + msg['Subject'] = "Código de recuperação" + + # Adicionando o corpo do e-mail + msg.attach(MIMEText(codigo, 'plain')) + + # Inicializando a conexão com o servidor SMTP do Gmail + server = smtplib.SMTP('smtp.gmail.com', 587) + server.starttls() + + # Faça login no servidor SMTP do Gmail + server.login(email_remetente, senha_email_remetente) + + # Enviando o e-mail + texto = msg.as_string() + server.sendmail(email_remetente, email, texto) + + # Fechando a conexão com o servidor SMTP do Gmail + server.quit() + + return True + + @staticmethod + def recuperar_conta(email: str, codigo: str, nova_senha: str, nova_senha_repetida: str, db_user: UserDatabase = db_user, db_recuperacao: RecuperacaoDatabase = db_recuperacao): + + user = db_user.get_user_by_email(email) + recuperacao = db_recuperacao.get_rec_by_email(email) + + if not user: + return "Email não cadastrado" + + if not recuperacao: + return "Não há recuperação solicitada para este email" + + if recuperacao.codigo != codigo: + return "Código Incorreto" + + if nova_senha != nova_senha_repetida: + return "Senhas não coincidem" + + if not db_user.valid_password(nova_senha): + return "Senha inválida" + + if datetime.now() - recuperacao.date > timedelta(hours=1): + return "Tempo expirado" + + user.add_password(nova_senha) + db_user.write_to_file() + return True \ No newline at end of file diff --git a/backend/src/service/impl/store_service.py b/backend/src/service/impl/store_service.py new file mode 100644 index 00000000..14189837 --- /dev/null +++ b/backend/src/service/impl/store_service.py @@ -0,0 +1,121 @@ +from src.schemas.response import HttpResponseModel +from pydantic import BaseModel +from src.db.__init__ import store_database as db +from src.db.store_database import Store +from src.schemas.store_response import HTTPSignUpResponses, HTTPLoginResponses, HTTPUpdateStoreResponses + +class DadosLoja(BaseModel): + cnpj: str + email: str + senha: str + categoria: str + nome: str + +class DadosLoginLoja(BaseModel): + cnpj: str + senha: str + +class DadosRetrieveLoja(BaseModel): + cnpj: str + email: str + nsenha: str + + +class DadosChangeLoja(BaseModel): + cnpj: str + nemail: str | None + senha: str + nsenha: str | None + ncategoria: str | None + nnome: str | None + + +class Store_service(): + + @staticmethod + def signup_store(dados: DadosLoja, database = db) -> HttpResponseModel: + """Tentar registrar uma nova loja""" + store = Store.new(*dados.model_dump().values()) + if store == None: + return HTTPSignUpResponses.BAD_REQUEST() + success, r = database.signup(store) + if success: + return HTTPSignUpResponses.SIGNUP_SUCCESSFUL() + else: + return HTTPSignUpResponses.ALREADY_EXIST() + + + + @staticmethod + def login_store(dados: DadosLoginLoja, database = db) -> HttpResponseModel: + CNPJ = dados.cnpj + Senha = dados.senha + store = database.get_store_by_cnpj(CNPJ) + + if store == None: + return HTTPLoginResponses.LOGIN_FAILED() + elif store.senha != Senha: + return HTTPLoginResponses.STORE_NOT_FOUND() + else: + return HTTPLoginResponses.LOGIN_SUCCESSFUL() + + + @staticmethod + def retrieve_password(dados:DadosRetrieveLoja, database = db) -> HttpResponseModel: + CNPJ = dados.cnpj + Email = dados.email + New_password = dados.nsenha + + store = database.get_store_by_cnpj(CNPJ) + + if store == None: + return HTTPUpdateStoreResponses.STORE_NOT_FOUND_UPDATE() + + elif Email != store.email: + return HTTPUpdateStoreResponses.UNAUTORIZED() + + else: + store.update_password({'senha': New_password}) + database.write_to_file() + print(store.senha) + return HTTPUpdateStoreResponses.UPDATE_SUCCESS() + + + @staticmethod + def change_user_data(dados: DadosChangeLoja, database = db) -> HttpResponseModel: + CNPJ = dados.cnpj + Senha = dados.senha + nSenha = dados.nsenha + nEmail = dados.nemail + nCategoria = dados.ncategoria + nNome = dados.nnome + + store = database.get_store_by_cnpj(CNPJ) + something_changed = False + + if store == None: + return HTTPLoginResponses.STORE_NOT_FOUND() + if store.senha != Senha: + print(store.senha) + print(Senha) + return HTTPUpdateStoreResponses.UNAUTORIZED() + + if nSenha and store.senha != nSenha: + store.update_password({'senha': nSenha}) + something_changed = True + if nEmail and store.email != nEmail: + store.update_email({'email': nEmail}) + something_changed = True + if nNome and store.nome != nNome: + store.update_nome({'nome': nNome}) + something_changed = True + if nCategoria and store.categoria != nCategoria: + store.update_categoria({'categoria': nCategoria}) + something_changed = True + + if something_changed: + database.write_to_file() + return HTTPUpdateStoreResponses.UPDATE_SUCCESS() + + + diff --git a/backend/src/service/impl/time_arrival_service.py b/backend/src/service/impl/time_arrival_service.py new file mode 100644 index 00000000..5c0d7a1a --- /dev/null +++ b/backend/src/service/impl/time_arrival_service.py @@ -0,0 +1,16 @@ +from src.schemas.provisory_schemas import Supplier, Product, Order, User +from src.schemas.response import HTTPResponses, HttpResponseModel +from src.schemas.time_arrival_response import HTTPTimeArrivalResponse +from src.db.time_arrival_db import calculate_time_arrival_db + +class TimeArrivalService: + + @staticmethod + def calculating_time_arrival(product_id: int, user_CPF: str) -> HttpResponseModel: + + success, data = calculate_time_arrival_db(product_id, user_CPF) + + if success: + return HTTPTimeArrivalResponse.GET_SUCCESSFULLY(data) + else: + return HTTPTimeArrivalResponse.BAD_REQUEST(data) \ No newline at end of file diff --git a/backend/src/service/meta/__init__.py b/backend/src/service/meta/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/src/service/meta/item_service_meta.py b/backend/src/service/meta/item_service_meta.py deleted file mode 100644 index db1451e0..00000000 --- a/backend/src/service/meta/item_service_meta.py +++ /dev/null @@ -1,11 +0,0 @@ - -from abc import ABC, abstractmethod - -from src.schemas.item import ItemGet - -class ItemServiceMeta(ABC): - - @abstractmethod - def get_item(self, item_id: str) -> ItemGet: - """Get item by id method definition""" - pass \ No newline at end of file diff --git a/backend/src/tests/Stores teste.json b/backend/src/tests/Stores teste.json new file mode 100644 index 00000000..5c06fcbe --- /dev/null +++ b/backend/src/tests/Stores teste.json @@ -0,0 +1 @@ +{"000": {"py/object": "src.db.store_database.Store", "nome": "loja", "categoria": "ali", "cnpj": "000", "email": "vitu", "senha": "123"}, "36.565.457/0001-90": {"py/object": "src.db.store_database.Store", "nome": "lojalegal", "categoria": "alimentos", "cnpj": "36.565.457/0001-90", "email": "vitor@gmail", "senha": "123"}, "37.565.457/0001-90": {"py/object": "src.db.store_database.Store", "nome": "Hortifruti", "categoria": "alimentos", "cnpj": "37.565.457/0001-90", "email": "htft@gmail", "senha": "htft8"}, "37.555.457/0001-90": {"py/object": "src.db.store_database.Store", "nome": "Hortifruti", "categoria": "alimentos", "cnpj": "37.555.457/0001-90", "email": "htft@gmail", "senha": "htft8"}, "121212": {"py/object": "src.db.store_database.Store", "nome": "Hortifrutaria", "categoria": "Eletronicos", "cnpj": "121212", "email": "laland@loja", "senha": "12345"}} \ No newline at end of file diff --git a/backend/src/tests/api/features/cancel_orders.feature b/backend/src/tests/api/features/cancel_orders.feature new file mode 100644 index 00000000..7d9bd477 --- /dev/null +++ b/backend/src/tests/api/features/cancel_orders.feature @@ -0,0 +1,26 @@ +Feature: Cancelamento de pedidos + + Scenario: Cancelar pedido com sucesso + Given o usuário de CPF "111.222.333-44" possui o produto de ID "1" e de status "On the way" atrelado a ele + When é solicitado uma requisição "PUT" para cancelar pedido com dados ID do produto "1", CPF do usuário "111.222.333-44" e razão do cancelamento "Demorou muito" + Then o status de resposta deverá ser de "200" + And a mensagem de resposta deverá conter o produto foi cancelado com sucesso + + Scenario: Cancelar pedido já entregue mal sucedido + Given o usuário de CPF "222.333.444-55" possui o produto de ID "3" e de status "Delivered" atrelado a ele + When é solicitado uma requisição "PUT" para cancelar pedido com dados ID do produto "3", CPF do usuário "222.333.444-55" e razão do cancelamento "Demorou muito" + Then o status de resposta deverá ser de "400" + And a mensagem de resposta deverá conter que o produto já foi entregue + + Scenario: Cancelar pedido já cancelado mal sucedido + Given o usuário de CPF "111.222.333-44" possui o produto de ID "2" e de status "Canceled" atrelado a ele + When é solicitado uma requisição "PUT" para cancelar pedido com dados ID do produto "2", CPF do usuário "111.222.333-44" e razão do cancelamento "Demorou muito" + Then o status de resposta deverá ser de "400" + And a mensagem de resposta deverá conter que o produto já foi cancelado + + Scenario: Solicitação de todos os pedidos + Given o usuário de CPF "331.704.260-52" possui os produtos de ID "1", "2" e "5" atrelados a ele e respectivamente status "Cancelado", "À caminho" e "Cancelado" + When é solicitado uma requisição "POST" para "obter todos os pedidos cancelados" com o dado CPF do usuário "331.704.260-52" + Then o status de resposta deverá ser de "200" + And a mensagem de resposta deverá conter "Os pedidos foram obtidos com sucesso!" + And os dados da resposta devem conter as informações dos pedidos de ID "1" e "5" \ No newline at end of file diff --git a/backend/src/tests/api/features/carrinho.feature b/backend/src/tests/api/features/carrinho.feature new file mode 100644 index 00000000..fee43db0 --- /dev/null +++ b/backend/src/tests/api/features/carrinho.feature @@ -0,0 +1,62 @@ +Feature: Carrinho API + +Scenario: Obter carrinho por CPF + Given o Carrinho_service retorna um carrinho com cpf "123.456.789-10" + When uma requisição GET for enviada para "/backend/api/carrinho/view/123.456.789-10" + Then o status da resposta deve ser "200" + And o resultado do JSON deve ser "{"Itens:": {}, "Total": "0.00", "Endereço": "Endereço não registrado"}" + +Scenario: Adicionar um produto válido ao carrinho + Given um produto com ID "12345678" está disponível + And o carrinho do cliente com CPF "123.456.789-10" está vazio + When o cliente adiciona o produto com ID "12345678" ao carrinho + Then o status da resposta deve ser "200" + And o item deve estar no carrinho + +Scenario: Remover um produto de um carrinho + Given um produto com ID "12345678" está no carrinho de CPF "123.456.789-10"! + When o cliente tenta remover o produto com ID "12345678" do carrinho + Then o status da resposta deve ser "200" + And o carrinho de CPF "123.456.789-10" está vazio + +Scenario: Falha em remover um produto de um carrinho vazio + Given o carrinho do cliente com CPF "123.456.789-10" está vazio + When o cliente tenta remover o produto com ID "12345678" do carrinho + Then o status da resposta deve ser "404" + +Scenario: Limpar conteúdo do carrinho + Given os produtos com ID "12345678" e "11111111" estão no carrinho de CPF "123.456.789-10" + When o carrinho de CPF "123.456.789-10" é limpo + Then o status da resposta deve ser "200" + And o carrinho de CPF "123.456.789-10" está vazio + +Scenario: Limpar a base de dados de carrinhos + Given os carrinhos de CPF "123.456.789-10", "111.111.111-11" e "222.222.222-22" estão registrados + When a base de dados de carrinhos é limpa + Then o status da resposta deve ser "200" + And a base de dados de carrinhos deve estar vazia + +Scenario: Incrementar quantidade de um item no carrinho + Given um produto com ID "12345678" de preço "29.99" está no carrinho de CPF "123.456.789-10" com quantidade "1" + When o item é incrementado + Then o status da resposta deve ser "200" + And o produto de ID "12345678" no carrinho "123.456.789-10" deve ter a quantidade "2" + And o total do carrinho de CPF "123.456.789-10" é "59.98" + +Scenario: Decrementar quantidade de um item no carrinho com quantidade maior que 1 + Given um produto com ID "12345678" de preço "29.99" está no carrinho de CPF "123.456.789-10" com quantidade "2" + When o item é decrementado + Then o status da resposta deve ser "200" + And o produto de ID "12345678" no carrinho "123.456.789-10" deve ter a quantidade "1" + And o total do carrinho de CPF "123.456.789-10" é "29.99" + +Scenario: Decrementar quantidade de um item no carrinho com quantidade 1 + Given um produto com ID "12345678" de preço "29.99" está no carrinho de CPF "123.456.789-10" com quantidade "1" + When o item é decrementado + Then o status da resposta deve ser "200" + And o carrinho de CPF "123.456.789-10" está vazio + +Scenario: Alterar endereço de destino do pedido + Given o endereço do carrinho de CPF "123.456.789-10" não foi registrado + When o endereço do carrinho de CPF "123.456.789-10" é alterado para "Rua , 225, Bairro, Cidade, Estado, CEP, País, Complemento" + Then o carrinho de CPF "123.456.789-10" tem "Rua , 225, Complemento\nBairro, Cidade - Estado\nCEP: CEP\nPaís" no campo endereço diff --git a/backend/src/tests/api/features/estimated_time_arrival.feature b/backend/src/tests/api/features/estimated_time_arrival.feature new file mode 100644 index 00000000..5066d1aa --- /dev/null +++ b/backend/src/tests/api/features/estimated_time_arrival.feature @@ -0,0 +1,32 @@ +Feature: Cálculo estimado de entrega + + Scenario: CEP do usuário que solicita a estimativa de entrega é inválido + Given o usuário de CPF "331.704.260-52" e CEP "01001002" está cadastrado no sistema + And o usuário está interessado em calcular a estimativa de entrega do produto de ID "1" e CEP "53010120" + When uma requisição "GET" for enviada para "calcular tempo de entrega", com dados CPF "331.704.260-52" e ID do produto "1" + Then o status de resposta deverá ser de "400" + And o campo mensagem contém "CEP do usuário inválido!" + + Scenario: Estimativa de entrega bem sucedida para o mesmo estado + Given o usuário de CPF "331.704.260-52" e CEP "50670901" está cadastrado no sistema + And o usuário está interessado em calcular a estimativa de entrega do produto de ID "1" e CEP "53010120" + When uma requisição "GET" for enviada para "calcular tempo de entrega", com dados CPF "331.704.260-52" e ID do produto "1" + Then o status de resposta deverá ser de "200" + And o campo mensagem contém "CEP do usuário inválido!" + And o campo de dados terá data de pedido "19-02-2024", data de entrega "24-02-2024" e modelo de entrega "Entrega tradicional" + + Scenario: Estimativa de entrega bem sucedida para estados diferentes + Given o usuário de CPF "331.704.260-52" e CEP "57690-000" está cadastrado no sistema + And o usuário está interessado em calcular a estimativa de entrega do produto de ID "1" e CEP "53010120" + When uma requisição "GET" for enviada para "calcular tempo de entrega", com dados CPF "331.704.260-52" e ID do produto "1" + Then o status de resposta deverá ser de "200" + And o campo mensagem contém "CEP do usuário inválido!" + And o campo de dados terá data de pedido "19-02-2024", data de entrega "26-02-2024" e modelo de entrega "Entrega expressa" + + Scenario: Estimativa de entrega bem sucedida para regiões diferentes + Given o usuário de CPF "331.704.260-52" e CEP "04109-130" está cadastrado no sistema + And o usuário está interessado em calcular a estimativa de entrega do produto de ID "1" e CEP "53010120" + When uma requisição "GET" for enviada para "calcular tempo de entrega", com dados CPF "331.704.260-52" e ID do produto "1" + Then o status de resposta deverá ser de "200" + And o campo mensagem contém "CEP do usuário inválido!" + And o campo de dados terá data de pedido "19-02-2024", data de entrega "29-02-2024" e modelo de entrega "Entrega aérea" \ No newline at end of file diff --git a/backend/src/tests/api/features/insert_card-api.feature b/backend/src/tests/api/features/insert_card-api.feature new file mode 100644 index 00000000..d88a4ef8 --- /dev/null +++ b/backend/src/tests/api/features/insert_card-api.feature @@ -0,0 +1,43 @@ +Feature: Payment Methods API + +Scenario: Cadastrar cartão de crédito válido + Given o cartao de nome "MasterCard", numero "4916123456789012", cvv "375", cpf "111.111.111-11" e validade "2028-10-08" não está cadastrado + When uma requisição "POST" foi enviada para "inserting/cartao" com Cartao(nome_cartao "MasterCard", numero_cartao "4916123456789012", cvv "134", cpf "111.111.111-11" e validade "2028-08-18") + And a requisição está correta + And o campo de "numero_cartao" de requisição é validado + And o campo de "cpf" de requisição é validado + And o campo de "validade" de requisição é validado + Then o status da resposta deve ser "201" + And o JSON da resposta indica que o cadastro foi bem sucedido + And o cartao de nome "MasterCard", numero "4916123456789012", cvv "375", cpf "111.111.111-11" e validade "2028-10-08" está cadastrado + +Scenario: Cadastrar cartão de crédito com cpf inválido + Given o cartao de nome "MasterCard", numero "4916123456789012", cvv "375", cpf "111.111.111" e validade "2028-10-08" não está cadastrado + When uma requisição "POST" foi enviada para "inserting/cartao" com Cartao(nome_cartao "MasterCard", numero_cartao "4916123456789012", cvv "134", cpf "111.111.111" e validade "2028-08-18") + And a requisição está correta + And o campo de "numero_cartao" de requisição é validado + And o campo de "cpf" de requisição é validado + And o campo de "validade" de requisição é validado + Then o status da resposta deve ser "400" + And o JSON da resposta indica que o cadastro foi mal sucedido + And o JSON da resposta indica que o campo CPF foi mal preenchido + And o cartao de nome "MasterCard", numero "4916123456789012", cvv "375", cpf "111.111.111" e validade "2028-10-08" não está cadastrado + +Scenario: Atualizar o numero do cartao + Given o cartao de nome "MasterCard", numero "4916123456789012", cvv "375", cpf "111.111.111-11" e validade "2028-10-08" está cadastrado + When uma requisição "PUT" foi enviada para "update/cartao/{id}" com CartaoUpdate(nome_cartao "Visa", numero_cartao "5410 9876 5432 1098", cvv "937" e validade "2027-08-20") + And a requisição está correta + And o campo de "numero_cartao" de requisição é validado + And o campo de "cpf" de requisição é validado + And o campo de "validade" de requisição é validado + Then o status da resposta deve ser "200" + And o JSON da resposta indica que a atualização foi bem sucedido + And o cartao de nome "Visa", numero_cartao "5410 9876 5432 1098", cvv "937" e validade "2027-08-20" está cadastrado + + + + + + + + diff --git a/backend/src/tests/api/features/login.feature b/backend/src/tests/api/features/login.feature index 3ba01f3c..e40cb08a 100644 --- a/backend/src/tests/api/features/login.feature +++ b/backend/src/tests/api/features/login.feature @@ -5,8 +5,8 @@ Feature: User Login And Usuário "Gabriel" possui senha "senha1234" When uma requisição "POST" for enviada para "login", com Dados Login(usuário: "Gabriel", senha: "senha1234") Then o status da resposta deve ser "200" - And o campo "data" possui o campo "token" com valor "$token_valor" - When uma requisição "POST" for enviada para "verify", com "$token_valor" + And o campo "data" possui o campo "token" com valor $token_valor + When uma requisição "POST" for enviada para "verify", com $token_valor Then o status da resposta deve ser "200" And o campo "data" possui o campo "user" And os elementos de "user" correspondem aos dados do usuário "Gabriel" diff --git a/backend/src/tests/api/features/store_login.feature b/backend/src/tests/api/features/store_login.feature new file mode 100644 index 00000000..712392b4 --- /dev/null +++ b/backend/src/tests/api/features/store_login.feature @@ -0,0 +1,19 @@ +Feature: Login de Lojas + + Scenario: Login de loja já cadastrada + Given Loja "Hortifruti", de CNPJ "37.565.457/0001-90" já está cadastrada com senha "htft8" + When Uma requisição POST for enviada para "login", com as seguintes informações (CNPJ: "37.565.457/0001-90", Senha: "htft8") + Then O status da resposta deve ser "200" + And O campo "message" tem o resultado positivo "Login com sucesso" + + Scenario: Login de loja não cadastrada + Given Loja "Hortifruti", de CNPJ "48.449.992/0001-65" não está cadastrada + When Uma requisição POST for enviada para "login", com as seguintes informações (CNPJ: "48.449.992/0001-65", Senha: "htft8") + Then O status da resposta deve ser "400" + And O campo "message" tem o resultado "Login falhou, essa loja não deve estar cadastrada" + + Scenario: Tentativa de Login com senha incorreta + Given Loja "Hortifruti", de CNPJ "37.565.457/0001-90" já está cadastrada com senha "htft8" + When Uma requisição POST for enviada para "login", com as seguintes informações (CNPJ: "37.565.457/0001-90", Senha: "bananinha") + Then O status da resposta deve ser "401" + And O campo "message" tem o resultado negativo "CNPJ ou Senha incorretos" diff --git a/backend/src/tests/api/features/store_signup.feature b/backend/src/tests/api/features/store_signup.feature new file mode 100644 index 00000000..c1d11af1 --- /dev/null +++ b/backend/src/tests/api/features/store_signup.feature @@ -0,0 +1,20 @@ +Feature: Cadastro de Lojas + + Scenario: Cadastro bem sucedido + Given Loja "Hortifruti", de CNPJ "48.449.992/0001-65" não está cadastrada + When Uma requisição POST for enviada para "signup", com as seguintes informações (Nome: "Hortifruti", CNPJ: "48.449.992/0001-65", Email: "Hortifruti@loja.com", Senha: "htft8", Categoria: "Alimenticio") + Then o status da resposta deve ser "200" + And o campo "message" tem o resultado positivo "Loja cadastrada com sucesso" + + Scenario: Tentativa de cadastro de loja já cadastrada + Given Loja "Lojalegal", de CNPJ "36.565.457/0001-90" já está cadastrada + When Uma requisição POST for enviada para "signup", com as seguintes informações (Nome: "Hortifruti", CNPJ: "36.565.457/0001-90", Email: "vitor@loja.com", Senha: "123", Categoria: "Alimento") + Then O status da resposta deve ser "401" + And O campo "message" tem o resultado negativo "Já existe uma loja registrada com esses dados" + + Scenario: Tentativa de cadastro com dados em formato invalido + Given Loja "Hortifruti", de CNPJ "48.449.992/0001-65" não está cadastrada + When Uma requisição POST for enviada para "signup", com as seguintes informações (Nome: "Hortifruti", CNPJ: "Mengão_da_massa", Email: "Hortifruti@loja.com", Senha: "htft8", Categoria: "Alimenticio") + Then o status da resposta de ser "400" + And O campo "message" tem o valor "informações com formato invalido" + \ No newline at end of file diff --git a/backend/src/tests/api/features/store_update.feature b/backend/src/tests/api/features/store_update.feature new file mode 100644 index 00000000..de4d60c2 --- /dev/null +++ b/backend/src/tests/api/features/store_update.feature @@ -0,0 +1,25 @@ +Feature: Manutenção de lojas cadastradas + + Scenario: Recuperação de senha + Given Loja "Hortifruti", de CNPJ "121212" já está cadastrada com senha "htft8" + When Uma requisição POST for enviada para "login/retrieve_password", com as seguintes informações (CNPJ: "121212", Email: "@loja.com", Nova_senha: "12345") + Then o status da resposta deve ser "200" + And A senha da loja de CNPJ "121212" agora é "12345" + And O campo "message" tem o resultado positivo "Atualização de dados bem sucedida" + + + + Scenario: Recuperação de senha com dados incorretos + Given Loja "Hortifruti", de CNPJ "121212" já está cadastrada com senha "htft8" + When Uma requisição POST for enviada para "login/retrieve_password", com as seguintes informações (CNPJ: "BrenoMiranda", Email: "Hortifruti@loja.com", Nova_senha: "1234") + Then O status da resposta deve ser "401" + And o campo "message" tem o resultado negativo "CNPJ ou Email incorretos" + + Scenario: Modificação de dados de uma loja + Given Loja "Hortifruti", de CNPJ "121212" já está cadastrada com senha "12345" + When Uma requisição POST for enviada para "store/change_store_data", com as seguintes informações (CNPJ: "121212", Senha: "12345", novoNome: "Hortifrutaria", novaCategoria: "Eletronicos", novoEmail: "laland@loja") + Then O status da resposta deve ser "200" + And O nome da loja de CNPJ "121212" é atualizado para "Hortifrutaria" + And A categoria da loja de CNPJ "121212" é atualizada para "Eletronicos" + And O campo "message" tem o resultado "Atualização de dados bem sucedida" + \ No newline at end of file diff --git a/backend/src/tests/api/features/update_card-api.feature b/backend/src/tests/api/features/update_card-api.feature new file mode 100644 index 00000000..c94525aa --- /dev/null +++ b/backend/src/tests/api/features/update_card-api.feature @@ -0,0 +1,10 @@ +Scenario: Atualizar o numero do cartao + Given o cartao de id "4574450647725381807" tipo "cartao", nome "MasterCard", numero "4916123456789012", cvv "375", cpf "111.111.111-11" e validade "2028-10-08" está cadastrado + When uma requisição "PUT" foi enviada para "update/cartao/{id}" com CartaoUpdate(nome_cartao "Visa", numero_cartao "5410987654321098", cvv "937" e validade "2027-08-20") + And a requisição está correta + And o campo de "numero_cartao" de requisição é validado + And o campo de "cpf" de requisição é validado + And o campo de "validade" de requisição é validado + Then o status da resposta deve ser "200" + And o JSON da resposta indica que a atualização foi bem sucedido + And o cartao de nome "Visa", numero_cartao "5410 9876 5432 1098", cvv "937" e validade "2027-08-20" está cadastrado \ No newline at end of file diff --git a/backend/src/tests/api/features/user_signup.feature b/backend/src/tests/api/features/user_signup.feature index d273baae..cb5caaa1 100644 --- a/backend/src/tests/api/features/user_signup.feature +++ b/backend/src/tests/api/features/user_signup.feature @@ -3,10 +3,6 @@ Feature: User Signup Scenario: Processar dados cadastrais bem sucedido Given Usuário "Enzo" não está cadastrado When uma requisição "POST" for enviada para "register", com Dados Cadastrais(nome: "Enzo Gabriel", sobrenome: "Rocha", user: "Enzo", CPF: 010.010.010-23, endereço: "Av. Dois Rios, 74 - Ibura, Recife - PE nº 700", CEP: "51230-000", data de nascimento: "2002-02-02", email: "EnzoGab@cin.ufpe.br", senha: "Xyzw3456") - And a requisição está correta - Then o campo "CPF" da requisição é validado - And o campo "Data_de_nascimento" da requisição é validado - And o campo "Email" da requisição é validado Then o status da resposta deve ser "200" And o JSON da resposta indica que o cadastro foi bem sucedido And Usuário "Enzo" está cadastrado @@ -14,8 +10,7 @@ Feature: User Signup Scenario: Processar dados cadastrais mal sucedido Given Usuário "Enzo" não está cadastrado When uma requisição "POST" for enviada para "register", com Dados Cadastrais(nome: "Enzo Gabriel", sobrenome: "Rocha", user: "Enzo", CPF: "00.00.00", endereço: "Av. Dois Rios, 74 - Ibura, Recife - PE nº 700", CEP: "51230-000", data de nascimento: "2002-02-02", email: "EnzoGab@cin.ufpe.br", senha: "Xyzw3456") - Then o campo "CPF" da requisição é rejeitado - And o status da resposta deve ser "400" + Then o status da resposta deve ser "400" And o JSON da resposta indica que o cadastro foi mal sucedido And o JSON da resposta indica que o campo "CPF" foi mal preenchido \ No newline at end of file diff --git a/backend/src/tests/api/step_definitions/no_test_items.py b/backend/src/tests/api/step_definitions/no_test_items.py deleted file mode 100644 index d10d720a..00000000 --- a/backend/src/tests/api/step_definitions/no_test_items.py +++ /dev/null @@ -1,110 +0,0 @@ -from src.schemas.response import HTTPResponses, HttpResponseModel -from pytest_bdd import parsers, given, when, then, scenario -from src.service.impl.item_service import ItemService -from src.tests.api.utils.utils import get_response_items_list, req_type_to_function - -""" Scenario: Obter item por ID """ -# This method is used to define the scenario name and feature file path -@scenario(scenario_name="Obter item por ID", feature_name="../features/items.feature") -def test_get_item(): - """ Get item by id """ - -# Step definitions for the "Obter item por ID" scenario -@given(parsers.cfparse('o ItemService retorna um item com id "{item_id}" e nome "{item_name}"')) -def mock_item_service_response(item_id: str, item_name: str): - """ - Mock the ItemService.get_item() method to return an item with the given id and name - """ - - ItemService.get_item = lambda id : HttpResponseModel( - message=HTTPResponses.ITEM_FOUND().message, - status_code=HTTPResponses.ITEM_FOUND().status_code, - data={"id": item_id, "name": item_name} - ) - -@when( - parsers.cfparse('uma requisição "{req_type}" for enviada para "{req_url}"'), - target_fixture="context" -) -def send_get_item_request(client, context, req_type: str, req_url: str): - """ - Send a request to the given URL using the given request type - """ - - response = req_type_to_function(client, req_type)(req_url) - context["response"] = response - return context - -@then(parsers.cfparse('o status da resposta deve ser "{status_code}"'), target_fixture="context") -def check_response_status_code(context, status_code: str): - """ - Check if the response status code is the expected - """ - - assert context["response"].status_code == int(status_code) - return context - -@then( - parsers.cfparse('o JSON da resposta deve conter id "{item_id}" e nome "{item_name}"'), - target_fixture="context" -) -def check_response_json_contains_item_data(context, item_id: str, item_name: str): - """ - Check if the response JSON contains the item id and name - """ - - expected_data = { "id": item_id, "name": item_name } - assert context["response"].json()["data"] == expected_data - return context - - -""" Scenario: Obter todos os itens """ - -@scenario(scenario_name="Obter todos os itens", feature_name="../features/items.feature") -def test_get_items(): - """ Get all items """ - -# Step definitions for the "Obter todos os itens" scenario -@given(parsers.cfparse('o ItemService retorna uma lista de itens')) -def mock_item_service_response_list(): - """ - Mock the ItemService.get_items() method to return a list of items - """ - ItemService.get_items = lambda: HttpResponseModel( - message=HTTPResponses.ITEM_FOUND().message, - status_code=HTTPResponses.ITEM_FOUND().status_code, - data={ - 'items': [ - {"id": "123", "name": "Exemplo de Item"}, - {"id": "456", "name": "Outro Item"} - ] - } - ) - -@then(parsers.cfparse('o item com id "{item_id}" e nome "{item_name}" está na lista'), target_fixture="context") -@given(parsers.cfparse('o item com id "{item_id}" e nome "{item_name}" está na lista'), target_fixture="context") -def check_item_is_in_list(context, item_id: str, item_name: str): - """ - Check if the item with the given id and name is in the response list - """ - items = get_response_items_list(context["response"]) - - assert {"id": item_id, "name": item_name} in items - - return context - -@then(parsers.cfparse('o JSON da resposta deve ser uma lista de itens'), target_fixture="context") -def check_response_json_is_an_item_list(context): - """ - Check if the response JSON is a list of items - """ - - items = get_response_items_list(context["response"]) - - assert isinstance(items, list) - for item in items: - assert isinstance(item, dict) - assert "name" in item and isinstance(item["name"], str) - assert "id" in item and isinstance(item["id"], str) - - return context \ No newline at end of file diff --git a/backend/src/tests/api/step_definitions/test_cancel_orders.py b/backend/src/tests/api/step_definitions/test_cancel_orders.py new file mode 100644 index 00000000..2d6389cc --- /dev/null +++ b/backend/src/tests/api/step_definitions/test_cancel_orders.py @@ -0,0 +1,117 @@ +from fastapi.testclient import TestClient +from unittest.mock import patch, MagicMock +from src.db.orders_db import * +from pytest_bdd import parsers, given, when, then, scenario +import datetime +import pytest +from src.main import app + +TESTCLIENT = TestClient(app) + +@pytest.fixture(autouse=True) +def change_db(): + db = {} + orders_database = read_file(db, "orders.json") + + for order in orders_database["111.222.333-44"]: + if order["id"] == 1: + order["_status"] = "On the way" + order["cancel_reason"] = None + + write_file(orders_database, "orders.json") + +"""Scenario: Cancelar pedidos com sucesoso""" +@scenario(scenario_name="Cancelar pedido com sucesso", feature_name="../features/cancel_orders.feature") +def test_cancel_orders(): + """Processa pedido de cancelamento""" + +"""Scenario: Cancelar pedidos já entregues mal sucedido""" +@scenario(scenario_name="Cancelar pedido já entregue mal sucedido", feature_name="../features/cancel_orders.feature") +def test_cancel_orders_delivered(): + """Processa pedido de cancelmento já entregue mal sucedido""" + +"""Scenario: Cancelar pedidos já entregues mal sucedido""" +@scenario(scenario_name="Cancelar pedido já cancelado mal sucedido", feature_name="../features/cancel_orders.feature") +def test_cancel_orders_canceled(): + """Processa pedido de cancelmento já cancelado mal sucedido""" + +@given(parsers.cfparse('o usuário de CPF "{user_cpf}" possui o produto de ID "{product_id}" e de status "{product_status}" atrelado a ele')) +def verify_product_status_on_the_way(user_cpf: str, product_id: int, product_status:str): + db = {} + orders_database = read_file(db, "orders.json") + + found_product = None + for order in orders_database[user_cpf]: + if order["id"] == int(product_id): + found_product = order + break + + if found_product: + real_product_status = found_product["_status"] + assert real_product_status == product_status + +@given(parsers.cfparse('o usuário de CPF "{user_cpf}" possui o produto de ID "{product_id}" e de status "{product_status}" atrelado a ele')) +def verify_product_status_delivered(user_cpf: str, product_id: int, product_status:str): + db = {} + orders_database = read_file(db, "orders.json") + + found_product = None + for order in orders_database[user_cpf]: + if order["id"] == int(product_id): + found_product = order + break + + if found_product: + real_product_status = found_product["_status"] + assert real_product_status == product_status + +@given(parsers.cfparse('o usuário de CPF "{user_cpf}" possui o produto de ID "{product_id}" e de status "{product_status}" atrelado a ele')) +def verify_product_status_canceled(user_cpf: str, product_id: int, product_status:str): + db = {} + orders_database = read_file(db, "orders.json") + + found_product = None + for order in orders_database[user_cpf]: + if order["id"] == int(product_id): + found_product = order + break + + if found_product: + real_product_status = found_product["_status"] + assert real_product_status == product_status + +@when(parsers.cfparse('é solicitado uma requisição "{request_type}" para cancelar pedido com dados ID do produto "{product_id}", CPF do usuário "{user_cpf}" e razão do cancelamento "{cancel_reason}"'), target_fixture= "request_response") +def cancel_request( + request_type: str, + product_id: int, + user_cpf: str, + cancel_reason: str, + client=TESTCLIENT +): + cancel_request = { + "product_id": product_id, + "user_cpf": user_cpf, + "cancel_reason": cancel_reason + } + + post = getattr(client, request_type.lower()) + return post(f"/backend/api/orders/cancel/{product_id}?user_CPF={user_cpf}&cancel_reason={cancel_reason}", json=cancel_request) + +@then(parsers.cfparse('o status de resposta deverá ser de "{response_status}"')) +def check_response_status(response_status: str, request_response): + assert int(response_status) == request_response.status_code + +@then(parsers.cfparse('a mensagem de resposta deverá conter o produto foi cancelado com sucesso')) +def check_succesfuly_response_msg(request_response): + request_response_json = request_response.json() + assert request_response_json['message'] == 'Pedido cancelado com sucesso!' + +@then(parsers.cfparse('a mensagem de resposta deverá conter que o produto já foi entregue')) +def check_delivery_response_msg(request_response): + request_response_json = request_response.json() + assert request_response_json['message'] == 'O pedido já foi entregue!' + +@then(parsers.cfparse('a mensagem de resposta deverá conter que o produto já foi cancelado')) +def check_canceled_response_msg(request_response): + request_response_json = request_response.json() + assert request_response_json['message'] == 'O pedido já foi cancelado!' \ No newline at end of file diff --git a/backend/src/tests/api/step_definitions/test_carrinho.py b/backend/src/tests/api/step_definitions/test_carrinho.py new file mode 100644 index 00000000..98d541b1 --- /dev/null +++ b/backend/src/tests/api/step_definitions/test_carrinho.py @@ -0,0 +1,271 @@ +from pytest_bdd import parsers, given, when, then, scenario +from src.service.impl.carrinho_service import Carrinho_service +from src.schemas.response import HttpResponseModel, HTTPResponses +from src.schemas.carrinho_response import HTTPCarrinhoResponses +from fastapi.testclient import TestClient +from src.main import app +import pytest +from src.db.__init__ import cart_database +from src.db.itens_database import Item + +TESTCLIENT = TestClient(app) + +import pytest + +@pytest.fixture(autouse=True) +def clean_database(): + # Código para limpar a base de dados antes de cada teste + cart_database.clear_cart_database() + + +@scenario(scenario_name= "Obter carrinho por CPF", feature_name="..//features/carrinho.feature") +def test_get_cart_by_CPF(): + pass + +@scenario(scenario_name="Adicionar um produto válido ao carrinho", feature_name="..//features/carrinho.feature") +def test_add_valid_product_to_cart(): + pass + +@scenario(scenario_name="Remover um produto de um carrinho", feature_name="..//features/carrinho.feature") +def test_remove_item(): + pass + +@scenario(scenario_name="Falha em remover um produto de um carrinho vazio", feature_name="..//features/carrinho.feature") +def test_remove_item_from_empty_cart(): + pass + +@scenario(scenario_name="Limpar conteúdo do carrinho", feature_name="..//features/carrinho.feature") +def test_clear_cart_content(): + pass + +@scenario(scenario_name="Limpar a base de dados de carrinhos", feature_name="..//features/carrinho.feature") +def test_clear_cart_database_content(): + pass + +@scenario(scenario_name="Incrementar quantidade de um item no carrinho", feature_name="..//features/carrinho.feature") +def test_increment_item_quantity(): + pass + +@scenario(scenario_name="Decrementar quantidade de um item no carrinho com quantidade maior que 1", feature_name="..//features/carrinho.feature") +def test_decrementar_item_com_quantidade_maior_que_1(): + pass + +@scenario(scenario_name="Decrementar quantidade de um item no carrinho com quantidade 1", feature_name="..//features/carrinho.feature") +def test_decrementar_item_com_quantidade_1(): + pass + +@scenario(scenario_name="Alterar endereço de destino do pedido", feature_name="..//features/carrinho.feature") +def test_alterar_endereço(): + pass + +@given(parsers.cfparse('o endereço do carrinho de CPF "{CPF}" não foi registrado'), target_fixture="context") +def endereço_nao_registrado(context, CPF: str): + context["CPF"] = CPF + send_get_cart_request(context) + check_response_json(context) + return context + +@when(parsers.cfparse('o endereço do carrinho de CPF "{CPF}" é alterado para "{endereço}"'), target_fixture="context") +def alterar_endereço(context, CPF: str, endereço: str): + endereco_lista = endereço.split(", ") + print(type(context)) + response = TESTCLIENT.put("/backend/api/carrinho/alterar_endereço", params={"CPF": CPF}, json={ + "rua": endereco_lista[0], + "numero": endereco_lista[1], + "bairro": endereco_lista[2], + "cidade": endereco_lista[3], + "estado": endereco_lista[4], + "cep": endereco_lista[5], + "pais": endereco_lista[6], + "complemento": endereco_lista[7] + }) + print(f'{context} CONTEXT') + print(type(context)) + context["response"] = response + return context + +@then(parsers.cfparse('o carrinho de CPF "{CPF}" tem "{endereço}" no campo endereço'), target_fixture="context") +def assert_adress(context, CPF: str, endereço: str): + send_get_cart_request(context) + print(context["response"].json()['data']['Endereço']) + print(endereço) + endereço_carrinho = context['response'].json()['data']["Endereço"] + endereço_carrinho_formatado = endereço_carrinho.replace("\\n", "\n") + endereço_formatado = endereço.replace("\\n", "\n") + print(endereço_formatado) + assert endereço_carrinho_formatado == endereço_formatado + return context + +@given(parsers.cfparse('o Carrinho_service retorna um carrinho com cpf "{CPF_}"')) +def mock_cart_service_response(CPF_: str): + #Carrinho_service.get_cart = lambda CPF: HttpResponseModel( + # message=HTTPResponses.ITEM_FOUND().message, + # status_code=HTTPResponses.ITEM_FOUND().status_code, + # data={"Itens:": {}, "Total": "0.00", "Endereço": "Endereço não registrado"} + #) + pass + +@given(parsers.cfparse('um produto com ID "{id}" está disponível')) +def produto_disponivel(id: str): + # Adicionar lógica para verificar se o produto está disponível na base de dados + pass + +@given(parsers.cfparse('o carrinho do cliente com CPF "{CPF_}" está vazio'), target_fixture="context") +def carrinho_vazio(context, CPF_: str): + # Inicialização do carrinho + context['CPF'] = CPF_ + response = TESTCLIENT.get(url="/backend/api/carrinho/view/123.456.789-10") + assert response.status_code == 200 + return context + +@given(parsers.cfparse('um produto com ID "{id}" está no carrinho de CPF "{CPF_}"!'), target_fixture="context") +def adicionar_item_ao_carrinho(context, id: str, CPF_: str): + context["id"] = id + context["CPF"] = CPF_ + send_get_cart_request(context) + adiciona_produto_ao_carrinho(context, id) + return context + +@given(parsers.cfparse('os produtos com ID "{id1}" e "{id2}" estão no carrinho de CPF "{CPF_}"'), target_fixture="context") +def carrinho_com_dois_itens(context, id1: str, id2: str, CPF_: str): + context["CPF"] = CPF_ + send_get_cart_request(context) + adiciona_produto_ao_carrinho(context, id1) + adiciona_produto_ao_carrinho(context, id2) + return context + +@given(parsers.cfparse('os carrinhos de CPF "{CPF1}", "{CPF2}" e "{CPF3}" estão registrados'), target_fixture="context") +def registrar_carrinhos(context, CPF1: str, CPF2: str, CPF3: str): + context["CPF"] = CPF1 + send_get_cart_request(context) + context["CPF"] = CPF2 + send_get_cart_request(context) + context["CPF"] = CPF3 + send_get_cart_request(context) + return context + +@given(parsers.cfparse('um produto com ID "{ID}" de preço "{preço}" está no carrinho de CPF "{CPF_}" com quantidade "{quantidade}"'), target_fixture="context") +def garantir_condicoes_iniciais(context, ID: str, preço: str, CPF_: str, quantidade: int): + print("given") + context["id"] = str(ID) + context["CPF"] = str(CPF_) + print(context) + send_get_cart_request(context) + assert context["response"].status_code == 200 + adiciona_produto_ao_carrinho(context, id= str(ID), quantidade=int(quantidade), preço=str(preço)) + return context + +@when(parsers.cfparse('o item é incrementado'), target_fixture="context") +def incrementar_item(context): + print("Incrementar item") + response = TESTCLIENT.put("backend/api/carrinho/incrementar_item",params={"item_id": context["id"], "CPF": context["CPF"]}) + print(context["CPF"]) + print(response.url) + context["response"] = response + return context + +@when(parsers.cfparse('o item é decrementado'), target_fixture="context") +def decrementar_item(context): + response = TESTCLIENT.put("backend/api/carrinho/decrementar_item",params={"item_id": context["id"], "CPF": context["CPF"]}) + context["response"] = response + return context + +@then(parsers.cfparse('o produto de ID "{ID}" no carrinho "{CPF_}" deve ter a quantidade "{quantidade}"'), target_fixture="context") +def verify_quantity(context, ID: str, CPF_: str, quantidade: int): + verificar_item_no_carrinho(context) + + # Obter lista de IDs dos itens no carrinho + cart_dict = context["response"].data + for item in cart_dict["Itens:"]: + if item.id == ID: + assert item.quantidade == int(quantidade) + + return context + +@then(parsers.cfparse('o total do carrinho de CPF "{CPF_}" é "{total}"'), target_fixture="context") +def verify_total(context, CPF_: str, total: str): + cart_dict = context["response"].data + assert cart_dict["Total"] == total + return context + + +@when(parsers.cfparse('a base de dados de carrinhos é limpa'), target_fixture="context") +def clear_database(context): + response = TESTCLIENT.delete("/backend/api/carrinho/clear_carts") + context["response"] = response + return context + +@then(parsers.cfparse('a base de dados de carrinhos deve estar vazia')) +def verify_clear_database(database = cart_database): + assert database.db == {} + +@when(parsers.cfparse('o carrinho de CPF "{CPF_}" é limpo'), target_fixture="context") +def clear_cart(context, CPF_: str): + response = TESTCLIENT.delete("/backend/api/carrinho/clear_cart", params= {"CPF": CPF_}) + context["response"] = response + return context + +@when(parsers.cfparse('o cliente tenta remover o produto com ID "{id}" do carrinho'), target_fixture="context") +def remover_item_do_carrinho(context, id: str): + response = TESTCLIENT.delete("/backend/api/carrinho/remover", params={"CPF": context["CPF"], "item_id": id}) + context["response"] = response + return context + +@then(parsers.cfparse('o carrinho de CPF "{CPF_}" está vazio'), target_fixture="context") +def empty_cart(context, CPF_): + send_get_cart_request(context) + check_response_json(context) + return context + + +@when(parsers.cfparse('o cliente adiciona o produto com ID "{id}" ao carrinho'), target_fixture="context") +def adiciona_produto_ao_carrinho(context, id: str, quantidade: int = 1, preço: str = "29.99"): + response = TESTCLIENT.post("/backend/api/carrinho/adicionar", + json={ + "id": str(id), + "nome": "Camisa", + "description": "string", + "price": preço, + "quantidade": quantidade, + "img": "string.jpg" + }, + params={"CPF": context["CPF"]}) + context["response"] = response + context["id"] = id + return context + +@then(parsers.cfparse('o item deve estar no carrinho'), target_fixture="context") +def verificar_item_no_carrinho(context): + response = Carrinho_service.get_cart(context["CPF"]) + cart_dict = response.data # Da forma {"Itens": list[DadosItem], "Total": "29.99", "Endereço": "Endereço não registrado"} + + # Obter lista de IDs dos itens no carrinho + item_ids_no_carrinho = [item.id for item in cart_dict["Itens:"]] + + assert context["id"] in item_ids_no_carrinho + context["response"] = response + return context + +@when(parsers.cfparse('uma requisição GET for enviada para "/backend/api/carrinho/view/123.456.789-10"'), target_fixture="context") +def send_get_cart_request(context, client = TESTCLIENT): + print("send_get_cart_request") + print(type(context)) + response = client.get(url="/backend/api/carrinho/view/123.456.789-10") + context["response"] = response + return context + +@then( + parsers.cfparse('o status da resposta deve ser "{status_code}"'), target_fixture="context" +) +def check_response_status_code(context, status_code: int): + assert context["response"].status_code == int(status_code) + return context + +@then( + parsers.cfparse('o resultado do JSON deve ser "{expected_data}"'), + target_fixture="context" +) +def check_response_json(context): + expected_data = {"Itens:": [], "Total": "0.00", "Endereço": "Endereço não registrado"} + assert context["response"].json()["data"] == expected_data + return context \ No newline at end of file diff --git a/backend/src/tests/api/step_definitions/test_insert_card.py b/backend/src/tests/api/step_definitions/test_insert_card.py new file mode 100644 index 00000000..cabdf9a8 --- /dev/null +++ b/backend/src/tests/api/step_definitions/test_insert_card.py @@ -0,0 +1,123 @@ +from fastapi.testclient import TestClient +from unittest.mock import patch, MagicMock +from src.db.payment_database import * +from pytest_bdd import parsers, given, when, then, scenario +import datetime +import pytest +from src.main import app + +TESTCLIENT = TestClient(app) + +@pytest.fixture(autouse=True) + +def remover_card_from_database(): + + card_info = { + "nome_cartao": "MasterCard", + "numero_cartao": "4916123456789012", + "cvv": "375", + "cpf": "111.111.111-11", + "validade": "2028-10-08" + } + + existing_card = get_card_by_number_and_cpf(card_info["cpf"], card_info["numero_cartao"]) + + if existing_card is not None: + remove_card(card_info["cpf"], card_info["numero_cartao"]) + +"Cenário: Cadastrar cartao de credito válido" +@scenario(scenario_name="Cadastrar cartão de crédito válido", feature_name="../features/insert_card-api.feature") + +def testar_cadastro_cartao_valido(): + pass + +"Cenário: Cadastrar cartão de crédito com cpf inválido" +@scenario(scenario_name="Cadastrar cartão de crédito com cpf inválido", feature_name="../features/insert_card-api.feature") + +def testar_cadastro_cartao_cpf_incorreto(): + pass + + +@given(parsers.cfparse('o cartao de nome "{nome}", numero "{numero}", cvv "{cvv}", cpf "{cpf}" e validade "{validade}" não está cadastrado')) +@then(parsers.cfparse('o cartao de nome "{nome}", numero "{numero}", cvv "{cvv}", cpf "{cpf}" e validade "{validade}" não está cadastrado')) + +def verificar_cartao_nao_existente(nome: str, numero: str, cvv: str, cpf: str, validade: datetime.date): + + assert get_card_by_number_and_cpf(cpf, numero) is None + +@then(parsers.cfparse('O cartao de nome "{nome}", numero "{numero}", cvv "{cvv}", cpf "{cpf}" e validade "{validade}" está cadastrado')) +@given(parsers.cfparse('O cartao de nome "{nome}", numero "{numero}", cvv "{cvv}", cpf "{cpf}" e validade "{validade}" está cadastrado')) +def verificar_cartao_existente(numero: str, cpf: str): + + + assert get_card_by_number_and_cpf(cpf, numero) is not None + +@when( + parsers.cfparse('uma requisição "{request_type}" foi enviada para "{insert_card}" com Cartao(nome_cartao "{nome}", numero_cartao "{numero}", cvv "{cvv}", cpf "{cpf}" e validade "{validade}")'), + target_fixture="request_response" +) +def enviar_requisicao(request_type: str, insert_card: str, nome: str, numero: str, cvv: str, cpf: str, validade: datetime.date, client=TESTCLIENT): + + + + insert_card_request = { + "nome_cartao": nome, + "numero_cartao": numero, + "cvv": cvv, + "cpf": cpf, + "validade": validade + } + + post_ = getattr(client, request_type.lower()) + return post_("/backend/src/api/payment/inserting/cartao", json=insert_card_request) + +@when(parsers.cfparse("a requisição está correta")) + +def assert_correction(): + """A requisição deve estar correta + + Não é necessário checar aqui + """ + pass + +@when(parsers.cfparse('o campo de "{campo}" de requisição é validado')) +def validade_field(): + """O campo deve ser validado + + Não é necessário checar aqui + """ + pass + +@then(parsers.cfparse('o status da resposta deve ser "{status}"')) +def check_response_status(status: str, request_response): + assert int(status) == request_response.status_code + +@then('o JSON da resposta indica que o cadastro foi bem sucedido') +def check_response_sucessfull_json(request_response): + request_response_json = request_response.json() + assert request_response_json['message'] == "metodo de pagamento cadastrado com sucesso" + +@then("o JSON da resposta indica que o cadastro foi mal sucedido") +def check_response_unsucessfull_json(request_response): + request_response_json = request_response.json() + assert request_response_json["message"] == "informações inválidas" + +@then("o JSON da resposta indica que o campo CPF foi mal preenchido") +def check_response_unsucessfull_json_cpf(request_response): + request_response_json = request_response.json() + assert request_response_json["data"][0] == "CPF" + +@then("o JSON da resposta indica que o campo validade foi mal preenchido") +def check_response_unsucessfull_json_validade(request_response): + request_response_json = request_response.json() + assert request_response_json["data"][1] == "VALIDADE" + +@then("o JSON da resposta indica que o campo numero_cartao foi mal preenchido") +def check_response_unsucessfull_json_numero_cartao(request_response): + request_response_json = request_response.json() + assert request_response_json["data"][2] == "CARD_NUMBER" + +@then('o JSON da resposta indica que a atualização foi bem sucedido') +def check_update_response_json(request_reponse): + request_reponse_json = request_reponse.json() + assert request_reponse_json["message"] == "Dados atualizados com sucesso" \ No newline at end of file diff --git a/backend/src/tests/api/step_definitions/test_login.py b/backend/src/tests/api/step_definitions/test_login.py index 9ba0bbdf..32ed3b1c 100644 --- a/backend/src/tests/api/step_definitions/test_login.py +++ b/backend/src/tests/api/step_definitions/test_login.py @@ -106,7 +106,7 @@ def check_response_status(status: str, request_response): assert int(status) == request_response.status_code @then( - parsers.cfparse('o campo "{campo1}" possui o campo "{token}" com valor "$token_valor"'), + parsers.cfparse('o campo "{campo1}" possui o campo "{token}" com valor $token_valor'), target_fixture="token_value" ) def check_field_token(campo1: str, token: str, request_response): @@ -115,7 +115,7 @@ def check_field_token(campo1: str, token: str, request_response): return field @when( - parsers.cfparse('uma requisição "{request_type}" for enviada para "{verify}", com "$token_valor"'), + parsers.cfparse('uma requisição "{request_type}" for enviada para "{verify}", com $token_valor'), target_fixture="request_response" ) def verify_request(request_type: str, verify: str, token_value, client = TESTCLIENT): diff --git a/backend/src/tests/api/step_definitions/test_signup.py b/backend/src/tests/api/step_definitions/test_signup.py index ebe51f3b..70a57e2d 100644 --- a/backend/src/tests/api/step_definitions/test_signup.py +++ b/backend/src/tests/api/step_definitions/test_signup.py @@ -81,29 +81,6 @@ def signup_request( post_ = getattr(client, request_type.lower()) return post_("/backend/api/auth/user/" + signup, json=signup_request) -@when('a requisição está correta') -def assert_correction(): - """A requisição deve estar correta - - Não é necessário checar aqui - """ - pass - -@then(parsers.cfparse('o campo "{campo}" da requisição é validado')) -def validade_field(): - """O campo deve ser validado - - Não é necessário checar aqui - """ - pass - -@then(parsers.cfparse('o campo "{campo}" da requisição é rejeitado')) -def validade_field(): - """O campo deve ser validado - - Não é necessário checar aqui - """ - pass @then(parsers.cfparse('o status da resposta deve ser "{status}"')) def check_response_status(status: str, request_response): diff --git a/backend/src/tests/api/step_definitions/test_store_login.py b/backend/src/tests/api/step_definitions/test_store_login.py new file mode 100644 index 00000000..e96dd091 --- /dev/null +++ b/backend/src/tests/api/step_definitions/test_store_login.py @@ -0,0 +1,112 @@ +from fastapi.testclient import TestClient +from src.db.__init__ import store_database +from src.db.store_database import Store +from pytest_bdd import parsers, given, when, then, scenario +import pytest +from src.main import app +import json + +TESTCLIENT = TestClient(app) + +@pytest.fixture(autouse=True) +def remove_test_from_db(): + store_database.remove_store_by_cnpj("48.449.992/0001-65") + store_database.add_store(Store.new("37.565.457/0001-90", "htft@gmail", "htft8", "alimentos", "Hortifruti")) + + + + +@scenario(scenario_name="Login de loja já cadastrada", feature_name="../features/store_login.feature") +def test_successeful_login(): + pass + +@given(parsers.cfparse('Loja "{nome}", de CNPJ "{cnpj}" já está cadastrada com senha "{senha}"')) +def verify_store_not_db(context, cnpj: str): + assert store_database.get_store_by_cnpj(cnpj) is not None + + +@when(parsers.cfparse('Uma requisição {request_type} for enviada para "{store_login}", com as seguintes informações (CNPJ: "{cnpj}", Senha: "{senha}")'), target_fixture="context",) +def login_request(request_type: str, store_login: str, cnpj: str ,senha: str, client=TESTCLIENT): + login_request = { + "cnpj": cnpj, + "senha": senha, + } + + post_= getattr(client, request_type.lower()) + return post_("/backend/api/stores/" + store_login, json=login_request) + + +@then(parsers.cfparse('o status da resposta deve ser "{status}"')) +def check_status(status: str, context): + assert int(status) == context.status_code + +@then(parsers.cfparse('o campo "{campo}" tem o resultado positivo "{message}"')) +def check_good_message(context): + context_json = context.json() + assert context_json['message'] == "Login com sucesso" + + + + + +@scenario(scenario_name="Tentativa de Login com senha incorreta", feature_name="../features/store_login.feature") +def test_wrong_pass_login(): + pass + +@given(parsers.cfparse('Loja "{nome}", de CNPJ "{cnpj}" já está cadastrada com senha "{senha}"')) +def verify_store_not_db(context, cnpj: str): + assert store_database.get_store_by_cnpj(cnpj) is not None + + +# @when(parsers.cfparse('Uma requisição {request_type} for enviada para "{store_login}", com as seguintes informações (CNPJ: "{cnpj}", Senha: "{senha}")'), target_fixture="context",) +# def login_request(request_type: str, store_login: str, cnpj: str ,senha: str, client=TESTCLIENT): +# login_request = { +# "cnpj": cnpj, +# "senha": senha, +# } + +# post_= getattr(client, request_type.lower()) +# return post_("/backend/api/stores/" + store_login, json=login_request) + + +# @then(parsers.cfparse('o status da resposta deve ser "{status}"')) +# def check_status(status: str, context): +# assert int(status) == context.status_code + +@then(parsers.cfparse('o campo "{campo}" tem o resultado negativo "{message}"')) +def check_good_message(context): + context_json = context.json() + assert context_json['detail'] == "CNPJ ou Senha incorretos" + + + + + +@scenario(scenario_name="Login de loja não cadastrada", feature_name="../features/store_login.feature") +def test_failed_login(): + pass + +@given(parsers.cfparse('Loja "{nome}", de CNPJ "{cnpj}" não está cadastrada')) +def verify_store_not_db(context, cnpj: str): + assert store_database.get_store_by_cnpj(cnpj) is None + + +# @when(parsers.cfparse('Uma requisição {request_type} for enviada para "{store_login}", com as seguintes informações (CNPJ: "{cnpj}", Senha: "{senha}")'), target_fixture="context",) +# def login_request(request_type: str, store_login: str, cnpj: str ,senha: str, client=TESTCLIENT): +# login_request = { +# "cnpj": cnpj, +# "senha": senha, +# } + +# post_= getattr(client, request_type.lower()) +# return post_("/backend/api/stores/" + store_login, json=login_request) + + +# @then(parsers.cfparse('o status da resposta deve ser "{status}"')) +# def check_status(status: str, context): +# assert int(status) == context.status_code + +@then(parsers.cfparse('o campo "{campo}" tem o resultado "{message}"')) +def check_good_message(context): + context_json = context.json() + assert context_json['detail'] == "Login falhou, essa loja não deve estar cadastrada" \ No newline at end of file diff --git a/backend/src/tests/api/step_definitions/test_store_signup.py b/backend/src/tests/api/step_definitions/test_store_signup.py new file mode 100644 index 00000000..dc0afd45 --- /dev/null +++ b/backend/src/tests/api/step_definitions/test_store_signup.py @@ -0,0 +1,105 @@ +from fastapi.testclient import TestClient +from src.db.__init__ import store_database +from src.db.store_database import Store +from pytest_bdd import parsers, given, when, then, scenario +import pytest +from src.main import app +import json + +TESTCLIENT = TestClient(app) + +@pytest.fixture(autouse=True) +def remove_test_from_db(): + store_database.remove_store_by_cnpj("48.449.992/0001-65") + store_database.add_store(Store.new("36.565.457/0001-90", "vitor@gmail", "123", "alimentos", "lojalegal")) + + + + +@scenario(scenario_name="Cadastro bem sucedido", feature_name="../features/store_signup.feature") +def test_successeful_signup(): + pass + +@given(parsers.cfparse('Loja "{nome}", de CNPJ "{cnpj}" não está cadastrada')) +def verify_store_not_db(context, cnpj: str): + assert store_database.get_store_by_cnpj(cnpj) is None + + +@when(parsers.cfparse('Uma requisição {request_type} for enviada para "{store_signup}", com as seguintes informações (Nome: "{nome}", CNPJ: "{cnpj}", Email: "{email}", Senha: "{senha}", Categoria: "{categoria}")'), target_fixture="context",) +def signup_request(request_type: str, store_signup: str + , nome: str, cnpj: str + , email: str, senha: str, categoria: str, client=TESTCLIENT): + signup_request = { + "nome": nome, + "cnpj": cnpj, + "email": email, + "senha": senha, + "categoria": categoria, + } + + post_= getattr(client, request_type.lower()) + return post_("/backend/api/stores/" + store_signup, json=signup_request) + + + +@then(parsers.cfparse('o status da resposta deve ser "{status}"')) +def check_status(status: str, context): + assert int(status) == context.status_code + +@then(parsers.cfparse('o campo "{campo}" tem o resultado positivo "{message}"')) +def check_good_message(context): + context_json = context.json() + assert context_json['message'] == "Loja cadastrada com sucesso" + + + + + + +@scenario(scenario_name="Tentativa de cadastro de loja já cadastrada", feature_name="../features/store_signup.feature") +def test_failed_signup(): + pass + + +@given(parsers.cfparse('Loja "{nome}", de CNPJ "{cnpj}" já está cadastrada')) +def verify_store_in_db(cnpj: str): + assert store_database.get_store_by_cnpj(cnpj) is not None + + +# @when(parsers.cfparse('Uma requisição {request_type} for enviada para "{store_signup}", com as seguintes informações (Nome: "{nome}", CNPJ: "{cnpj}", Email: "{email}", Senha: "{senha}", Categoria: "{categoria}")'), target_fixture="context",) +# def signup_request(request_type: str, store_signup: str +# , nome: str, cnpj: str +# , email: str, senha: str, categoria: str, client=TESTCLIENT): +# signup_request = { +# "nome": nome, +# "cnpj": cnpj, +# "email": email, +# "senha": senha, +# "categoria": categoria, +# } + +# post_= getattr(client, request_type.lower()) +# return post_("/backend/api/stores/" + store_signup, json=signup_request) + + + +# @then(parsers.cfparse('o status da resposta deve ser "{status}"')) +# def check_status(status: str, context): +# assert int(status) == context.status_code + + +@then(parsers.cfparse('O campo "{campo}" tem o resultado negativo "{message}"')) +def check_negative_message(context): + context_json = context.json() + print(context_json) + assert context_json['detail'] == "Já existe uma loja registrada com esses dados" + + + + + + + + + + diff --git a/backend/src/tests/api/step_definitions/test_store_update.py b/backend/src/tests/api/step_definitions/test_store_update.py new file mode 100644 index 00000000..d0a7b2bc --- /dev/null +++ b/backend/src/tests/api/step_definitions/test_store_update.py @@ -0,0 +1,133 @@ +from fastapi.testclient import TestClient +from src.db.__init__ import store_database +from src.db.store_database import Store +from pytest_bdd import parsers, given, when, then, scenario +import pytest +from src.main import app +import json + +TESTCLIENT = TestClient(app) + +@pytest.fixture(autouse=True) +def remove_test_from_db(): + store_database.add_store(Store.new("121212", "@loja.com", "htft8", "alimento", "Hortifruti")) + + + + +@scenario(scenario_name="Recuperação de senha", feature_name="../features/store_update.feature") +def test_successeful_password_retrieval(): + pass + +@given(parsers.cfparse('Loja "{nome}", de CNPJ "{cnpj}" já está cadastrada com senha "{senha}"')) +def verify_store_not_db(context, cnpj: str): + assert store_database.get_store_by_cnpj(cnpj) is not None + + +@when(parsers.cfparse('Uma requisição {request_type} for enviada para "{store_retrieve_pass}", com as seguintes informações (CNPJ: "{cnpj}", Email: "{email}", Nova_senha: "{nsenha}")'), target_fixture="context",) +def retrieve_request(request_type: str, store_retrieve_pass: str, cnpj: str , nsenha: str, email: str, client=TESTCLIENT): + retrieval_request = { + "cnpj": cnpj, + "email": email, + "nsenha": nsenha + } + + post_= getattr(client, request_type.lower()) + return post_("/backend/api/stores/" + store_retrieve_pass, json=retrieval_request) + + +@then(parsers.cfparse('o status da resposta deve ser "{status}"')) +def check_status(status: str, context): + assert int(status) == context.status_code + + +@then(parsers.cfparse('o campo "{campo}" tem o resultado negativo "{message}"')) +def check_good_message(context): + context_json = context.json() + assert context_json['detail'] == "CNPJ ou Email incorretos" + + + + + +@scenario(scenario_name="Recuperação de senha com dados incorretos", feature_name="../features/store_update.feature") +def test_failed_password_retrieval(): + pass + +@given(parsers.cfparse('Loja "{nome}", de CNPJ "{cnpj}" já está cadastrada com senha "{senha}"')) +def verify_store_not_db(context, cnpj: str): + assert store_database.get_store_by_cnpj(cnpj) is not None + + +@when(parsers.cfparse('Uma requisição {request_type} for enviada para "{store_retrieve_pass}", com as seguintes informações (CNPJ: "{cnpj}", Email: "{email}", Nova_senha: "{nsenha}")'), target_fixture="context",) +def retrieve_request(request_type: str, store_retrieve_pass: str, cnpj: str , nsenha: str, email: str, client=TESTCLIENT): + retrieval_request = { + "cnpj": cnpj, + "email": email, + "nsenha": nsenha + } + + post_= getattr(client, request_type.lower()) + return post_("/backend/api/stores/" + store_retrieve_pass, json=retrieval_request) + + +@then(parsers.cfparse('o status da resposta deve ser "{status}"')) +def check_status(status: str, context): + assert int(status) == context.status_code + +@then(parsers.cfparse('A senha da loja de CNPJ "{cnpj}" agora é "{senha}"')) +def check_password(cnpj: str, senha: str, context): + assert store_database.get_store_by_cnpj(cnpj).senha == senha + + +@then(parsers.cfparse('o campo "{campo}" tem o resultado positivo "{message}"')) +def check_good_message(context): + context_json = context.json() + assert context_json['message'] == "Atualização de dados bem sucedida" + + + + + + +@scenario(scenario_name="Modificação de dados de uma loja", feature_name="../features/store_update.feature") +def test_success_change_data(): + pass + +@given(parsers.cfparse('Loja "{nome}", de CNPJ "{cnpj}" já está cadastrada com senha "{senha}"')) +def verify_store_not_db(context, cnpj: str): + assert store_database.get_store_by_cnpj(cnpj) is not None + + +@when(parsers.cfparse('Uma requisição {request_type} for enviada para "{store_change_data}", com as seguintes informações (CNPJ: "{cnpj}", Senha: "{senha}", novoNome: "{nNome}", novaCategoria: "{nCategoria}", novoEmail: "{nEmail}")'), target_fixture="context",) +def retrieve_request(request_type: str, store_change_data: str, cnpj: str , senha: str, nNome: str, nCategoria: str, nEmail: str, client=TESTCLIENT): + change_data_request = { + "cnpj": cnpj, + "nemail": nEmail, + "senha": senha, + "nsenha": senha, + "ncategoria": nCategoria, + "nnome": nNome, + } + + post_= getattr(client, request_type.lower()) + return post_("/backend/api/stores/" + store_change_data, json=change_data_request) + + +@then(parsers.cfparse('o status da resposta deve ser "{status}"')) +def check_status(status: str, context): + assert int(status) == context.status_code + +@then(parsers.cfparse('A categoria da loja de CNPJ "{cnpj}" é atualizada para "{nCategoria}"')) +def check_password(cnpj: str, nCategoria: str, context): + assert store_database.get_store_by_cnpj(cnpj).categoria == nCategoria + +@then(parsers.cfparse('O nome da loja de CNPJ "{cnpj}" é atualizado para "{nNome}"')) +def check_password(cnpj: str, nNome: str, context): + assert store_database.get_store_by_cnpj(cnpj).nome == nNome + + +@then(parsers.cfparse('o campo "{campo}" tem o resultado "{message}"')) +def check_good_message(context): + context_json = context.json() + assert context_json['message'] == "Atualização de dados bem sucedida" diff --git a/backend/src/tests/api/utils/__init__.py b/backend/src/tests/api/utils/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/src/tests/api/utils/utils.py b/backend/src/tests/api/utils/utils.py deleted file mode 100644 index 50cc8145..00000000 --- a/backend/src/tests/api/utils/utils.py +++ /dev/null @@ -1,16 +0,0 @@ -from fastapi.testclient import TestClient - -def req_type_to_function(client: TestClient, req_type: str): - return { - "GET": client.get, - "POST": client.post, - "PUT": client.put, - "DELETE": client.delete - }[req_type] - - -def get_response_items_list(response): - return response.json()["data"]["items"] - - -# TODO: create more utils functions \ No newline at end of file diff --git a/backend/src/tests/integration/features/integrate_login_carrinho.feature b/backend/src/tests/integration/features/integrate_login_carrinho.feature new file mode 100644 index 00000000..5c3a6ba9 --- /dev/null +++ b/backend/src/tests/integration/features/integrate_login_carrinho.feature @@ -0,0 +1,14 @@ +Feature: Integrar User Login e Carrinho + + Scenario: Logar e usar carrinho + Given Usuário "Gabriel" está cadastrado + And um produto com ID "99999999" está no carrinho de "Gabriel" + And Usuário "Gabriel" possui senha "senha1234" + When uma requisição "POST" for enviada para "login", com Dados Login(usuário: "Gabriel", senha: "senha1234") + Then o status da resposta deve ser "200" + And o campo "data" possui o campo "token" com valor $token_valor + When uma requisição "POST" for enviada para "verify", com $token_valor + Then o status da resposta deve ser "200" + When o cliente tenta remover o produto com ID "99999999" do carrinho de "Gabriel" + Then o status da resposta deve ser "200" + And o carrinho de "Gabriel" está vazio \ No newline at end of file diff --git a/backend/src/tests/integration/features/integrate_login_recuperarsenha.feature b/backend/src/tests/integration/features/integrate_login_recuperarsenha.feature new file mode 100644 index 00000000..4b682cd8 --- /dev/null +++ b/backend/src/tests/integration/features/integrate_login_recuperarsenha.feature @@ -0,0 +1,14 @@ +Feature: Integrar User Login e Recuperar Senha + + Scenario: Logar após recuperar Senha + Given Usuário com Cpf "111.111.111-02" está cadastrado + And o usuário possui a senha "faculdade123" + And o usuário possui o campo "email" o valor "djaar@cin.ufpe.br" + When o método Login User é chamado com DadosLogin(cpf_ou_user_ou_email="111.111.111-02", senha="12345Abcx") + Then o método deve retornar que o login foi mal sucedido + When o método Enviar Email é chamado com email: "djaar@cin.ufpe.br" + And o método Recuperar Conta é chamado com email: "djaar@cin.ufpe.br", código válido e nova senha "faculdade124" + Then Usuário com Cpf "111.111.111-02" está cadastrado + And o usuário possui a senha "faculdade124" + When o método Login User é chamado com DadosLogin(cpf_ou_user_ou_email="111.111.111-02", senha="faculdade124") + Then o método deve retornar que o login foi bem sucedido \ No newline at end of file diff --git a/backend/src/tests/integration/features/integrate_login_signup.feature b/backend/src/tests/integration/features/integrate_login_signup.feature new file mode 100644 index 00000000..c0ae0dcb --- /dev/null +++ b/backend/src/tests/integration/features/integrate_login_signup.feature @@ -0,0 +1,24 @@ +Feature: Integrar Login e Cadastro de Usuário + + Scenario: Logar após Cadastrar + Given Usuário "Enzo" não está cadastrado + When uma requisição "POST" for enviada para "register", com Dados Cadastrais(nome: "Enzo Gabriel", sobrenome: "Rocha", user: "Enzo", CPF: 010.010.010-23, endereço: "Av. Dois Rios, 74 - Ibura, Recife - PE nº 700", CEP: "51230-000", data de nascimento: "2002-02-02", email: "EnzoGab@cin.ufpe.br", senha: "Xyzw3456") + Then o status da resposta deve ser "200" + And o JSON da resposta indica que o cadastro foi bem sucedido + And Usuário "Enzo" está cadastrado + When uma requisição "POST" for enviada para "login", com Dados Login(usuário: "Enzo", senha: "Xyzw3456") + Then o status da resposta deve ser "200" + And o campo "data" possui o campo "token" com valor "$token_valor" + When uma requisição "POST" for enviada para "verify", com "$token_valor" + Then o status da resposta deve ser "200" + And o campo "data" possui o campo "user" + And os elementos de "user" correspondem aos dados do usuário "Enzo" + + Scenario: Logar senha errada após Cadastrar + Given Usuário "Enzo" não está cadastrado + When uma requisição "POST" for enviada para "register", com Dados Cadastrais(nome: "Enzo Gabriel", sobrenome: "Rocha", user: "Enzo", CPF: 010.010.010-23, endereço: "Av. Dois Rios, 74 - Ibura, Recife - PE nº 700", CEP: "51230-000", data de nascimento: "2002-02-02", email: "EnzoGab@cin.ufpe.br", senha: "Xyzw3456") + Then o status da resposta deve ser "200" + And o JSON da resposta indica que o cadastro foi bem sucedido + And Usuário "Enzo" está cadastrado + When uma requisição "POST" for enviada para "login", com Dados Login(usuário: "Enzo", senha: "Xyzw3457") + Then o status da resposta deve ser "401" \ No newline at end of file diff --git a/backend/src/tests/integration/step_definitions/test_integrate_login_carrinho.py b/backend/src/tests/integration/step_definitions/test_integrate_login_carrinho.py new file mode 100644 index 00000000..8fe77d50 --- /dev/null +++ b/backend/src/tests/integration/step_definitions/test_integrate_login_carrinho.py @@ -0,0 +1,193 @@ +from fastapi.testclient import TestClient +from unittest.mock import patch, MagicMock +from src.db.__init__ import user_database +from src.db.user_database import User +from pytest_bdd import parsers, given, when, then, scenario +import datetime +import pytest +from src.main import app + +TESTCLIENT = TestClient(app) + +# Set presumed initial conditions for testing +@pytest.fixture(scope="session", autouse=True) +def database_setup(): + user_database.remove_user_by_cpf("453.456.426-95") + enzo_user = user_database.get_user_by_username("Enzo") + if enzo_user is not None: + user_database.remove_user_by_cpf(enzo_user.cpf) + user_gabriel = User.new( + "Gabriel", + "Gabriel Souza", + "de Lima", + "453.456.426-95", + datetime.date.fromisocalendar(1984, 42, 7), + "gabriel@gogle.com", + "senha1234" + )[0] + user_database.add_user(user_gabriel) + + yield + + user_database.remove_user_by_cpf("453.456.426-95") + +"""Scenario: Logar e usar carrinho""" +@scenario(scenario_name="Logar e usar carrinho", feature_name="../features/integrate_login_carrinho.feature") +def test_login_cart(): + """Do Login, then access cart""" + +@when(parsers.cfparse('uma requisição GET for enviada para "/backend/api/carrinho/view/123.456.789-10"'), target_fixture="context") +def send_get_cart_request(context, cpf = 0, client = TESTCLIENT): + print("send_get_cart_request") + print(context) + response = client.get(url="/backend/api/carrinho/view/" + cpf) + print(context) + context["response"] = response + return context + +@when(parsers.cfparse('o cliente adiciona o produto com ID "{id}" ao carrinho'), target_fixture="context") +def adiciona_produto_ao_carrinho(context, id: str, quantidade: int = 1, preço: str = "29.99"): + response = TESTCLIENT.post("/backend/api/carrinho/adicionar", + json={ + "id": str(id), + "nome": "Camisa", + "description": "string", + "price": preço, + "quantidade": quantidade, + "img": "string.jpg" + }, + params={"CPF": context["CPF"]}) + context["response"] = response + context["id"] = id + return context + +@given(parsers.cfparse('um produto com ID "{id}" está no carrinho de "{user}"'), target_fixture="context") +def adicionar_item_ao_carrinho(context, id: str, user: str): + context["id"] = id + context["CPF"] = user_database.get_user_by_username(user).cpf + cpf = context["CPF"] + send_get_cart_request(context, cpf) + adiciona_produto_ao_carrinho(context, id) + return context + +@given(parsers.cfparse('Usuário "{username}" está cadastrado')) +def verify_user(username: str): + """ + Assert that an user exists in db + + Args: + user (str): _description_ + """ + assert user_database.get_user_by_username(username) is not None + +@given(parsers.cfparse('Usuário "{username}" não está cadastrado')) +def verify_user(username: str): + """ + Assert that an user exists in db + + Args: + user (str): _description_ + """ + assert user_database.get_user_by_username(username) is None + +@given(parsers.cfparse('Usuário "{username}" possui senha "{senha}"')) +def verify_psw(username: str, senha: str): + """ + Assert user password + + Args: + username (str): _description_ + senha (str): _description_ + """ + user: User = user_database.get_user_by_username(username) + assert user.check_password(senha) + +@when( + parsers.cfparse('uma requisição "{request_type}" for enviada para "{login}", com Dados Login(usuário: "{username}", senha: "{senha}")'), + target_fixture="request_response" +) +def login_request(request_type: str, login: str, username: str, senha: str, client=TESTCLIENT): + """ + Faz uma requisição de login + + Args: + request_type (str): _description_ + username (str): _description_ + senha (str): _description_ + client (_type_, optional): _description_. Defaults to TestClient. + """ + login_request = { + "cpf_ou_user_ou_email": username, + "senha": senha + } + post_ = getattr(client, request_type.lower()) + return post_("/backend/api/auth/user/" + login, json=login_request) + +@then(parsers.cfparse('o status da resposta deve ser "{status}"')) +def check_response_status(status: str, request_response): + # print('\n\nresponse = ', request_response, '\n\n') + assert int(status) == request_response.status_code + +@then( + parsers.cfparse('o campo "{campo1}" possui o campo "{token}" com valor $token_valor'), + target_fixture="token_value" +) +def check_field_token(campo1: str, token: str, request_response): + request_response_json = request_response.json() + field = request_response_json[campo1][token] + return field + +@when( + parsers.cfparse('uma requisição "{request_type}" for enviada para "{verify}", com $token_valor'), + target_fixture="request_response" +) +def verify_request(request_type: str, verify: str, token_value, client = TESTCLIENT): + verify_request = { + "token" : token_value + } + post_ = getattr(client, request_type.lower()) + return post_("/backend/api/auth/user/" + verify, json=verify_request) + +@then( + parsers.cfparse('o campo "{campo1}" possui o campo "{campo2}"'), + target_fixture="campo_value" +) +def check_field(campo1: str, campo2: str, request_response): + request_response_json = request_response.json() + field = request_response_json[campo1][campo2] + return field + +@then( + parsers.cfparse('os elementos de "user" correspondem aos dados do usuário "{username}"') +) +def check_user_data(username: str, campo_value): + user: User = user_database.get_user_by_username(username) + assert user.username == campo_value['username'] + assert user.nome == campo_value['nome'] + assert user.sobrenome == campo_value['sobrenome'] + assert user.cpf == campo_value['cpf'] + +@then( + parsers.cfparse('o campo "{campo1}" tem o valor "{value}"') +) +def check_field_value(campo1: str, value: str, request_response): + request_response_json = request_response.json() + field = request_response_json[campo1] + assert field == value + +@when(parsers.cfparse('o cliente tenta remover o produto com ID "{id}" do carrinho de "{user}"'), target_fixture="context") +def remover_item_do_carrinho(context, id: str, user: str): + response = TESTCLIENT.delete("/backend/api/carrinho/remover", params={"CPF": user_database.get_user_by_username(user).cpf, "item_id": id}) + context["response"] = response + return context + +def check_response_json(context): + expected_data = {"Itens:": [], "Total": "0.00", "Endereço": "Endereço não registrado"} + assert context["response"].json()["data"] == expected_data + return context + +@then(parsers.cfparse('o carrinho de "{user}" está vazio'), target_fixture="context") +def empty_cart(context, user): + send_get_cart_request(context, user_database.get_user_by_username(user).cpf) + check_response_json(context) + return context diff --git a/backend/src/tests/integration/step_definitions/test_integrate_login_recuperarsenha.py b/backend/src/tests/integration/step_definitions/test_integrate_login_recuperarsenha.py new file mode 100644 index 00000000..21ba3fca --- /dev/null +++ b/backend/src/tests/integration/step_definitions/test_integrate_login_recuperarsenha.py @@ -0,0 +1,103 @@ +from src.db.__init__ import user_database_example as db +from src.db.__init__ import recuperacao_database_test as db_recuperacao +from src.db.codigos_rec_database import Recuperacao +from src.service.impl.recuperation_service import RecuperationService +from datetime import datetime, timedelta +import pytest +import src.service.impl.signup_service as signup +from unittest.mock import patch, MagicMock +from src.db.__init__ import user_database +from src.db.user_database import User +from pytest_bdd import parsers, given, when, then, scenario +import pytest +from src.main import app +from src.schemas.user_schemas import DadosLogin, DadosUser +from src.service.impl.auth_service import AuthService +from src.schemas.user_response import HTTPLoginResponses, HTTPVerifyResponses + +@pytest.fixture(scope="module", autouse=True) +def user_valido(): + dados_cadastrais = signup.DadosCadastrais( + username="Peterson", + nome = "Peterson", + sobrenome="Melo", + cpf="111.111.111-02", + data_de_nascimento=datetime(2003,5,29), + email="djaar@cin.ufpe.br", + senha="faculdade123", + endereço= "Rua tal", + CEP="50000-000" + ) + + signup.SingUpService.signup_user(dados_cadastrais, db) + + user = db.get_user_by_email("djaar@cin.ufpe.br") + + yield user + + db.clear_database() + db_recuperacao.clear_database() + +# Função para fornecer um código válido para os testes +def codigo_valido(email): + codigo = db_recuperacao.get_rec_by_email(email).codigo + return codigo + +"""Scenario: Logar após recuperar Senha""" +@scenario(scenario_name="Logar após recuperar Senha", feature_name="../features/integrate_login_recuperarsenha.feature") +def test_login_recovery(): + """Login after email recovery""" + +@given(parsers.cfparse('Usuário com Cpf "{cpf}" não está cadastrado')) +def check_cpf(cpf: str): + assert user_database.get_user_by_cpf(cpf) is None + +@then(parsers.cfparse('Usuário com Cpf "{cpf}" está cadastrado'), +target_fixture="user_") +@given(parsers.cfparse('Usuário com Cpf "{cpf}" está cadastrado'), +target_fixture="user_") +def check_cpf(cpf: str): + user = user_database.get_user_by_cpf(cpf) + assert user is not None + return user + +@then(parsers.cfparse('o usuário possui a senha "{senha}"')) +@given(parsers.cfparse('o usuário possui a senha "{senha}"')) +def check_psw(senha: str, user_: User): + assert user_.check_password(senha) + +@when(parsers.cfparse('o método Login User é chamado com DadosLogin(cpf_ou_user_ou_email="{cpf}", senha="{senha}")'), +target_fixture="response") +def login(cpf: str, senha: str): + dados = DadosLogin(cpf_ou_user_ou_email=cpf, senha=senha) + res = AuthService.login_user(dados) + return res + +@then('o método deve retornar que o login foi bem sucedido') +def check_success(response): + assert response.message == "Login com sucesso" + +@then('o método deve retornar que o login foi mal sucedido') +def check_fail(response): + assert response == HTTPLoginResponses.LOGIN_FAILED() or response == HTTPLoginResponses.USER_NOT_FOUND() + +@then(parsers.cfparse('o campo "{campo}" da resposta deve conter o valor "{value}"')) +def check_value(campo: str, value: str, response): + assert getattr(response, campo) == value + +@then(parsers.cfparse('o campo "{campo1}" da resposta deve conter o campo "{campo2}"')) +def check_field(campo1: str, campo2: str, response): + assert getattr(response, campo1)[campo2] + +@given(parsers.cfparse('o usuário possui o campo "{campo}" o valor "{value}"')) +def check_value_in_user(campo: str, value: str, user_: User): + assert getattr(user_, campo) == value + +@when(parsers.cfparse('o método Enviar Email é chamado com email: "{email}"')) +def email_send(email: str): + assert RecuperationService.enviar_email(email, db_user=db, db_recuperacao=db_recuperacao) == True + +@when(parsers.cfparse('o método Recuperar Conta é chamado com email: "{email}", código válido e nova senha "{senha}"')) +def recovery_account(email: str, senha: str): + assert RecuperationService.recuperar_conta(email, codigo_valido(email), senha, senha, db_user=db, db_recuperacao=db_recuperacao) == True + diff --git a/backend/src/tests/integration/step_definitions/test_integrate_login_signup.py b/backend/src/tests/integration/step_definitions/test_integrate_login_signup.py new file mode 100644 index 00000000..5b0e60f9 --- /dev/null +++ b/backend/src/tests/integration/step_definitions/test_integrate_login_signup.py @@ -0,0 +1,207 @@ +from fastapi.testclient import TestClient +from unittest.mock import patch, MagicMock +from src.db.__init__ import user_database +from src.db.user_database import User +from pytest_bdd import parsers, given, when, then, scenario +import datetime +import pytest +from src.main import app + +TESTCLIENT = TestClient(app) + +@pytest.fixture(autouse=True) +def remove_enzo_from_db(): + user_database.remove_user_by_cpf("010.010.010-23") + +"""Scenario: Logar após Cadastrar""" +@scenario(scenario_name="Logar após Cadastrar", feature_name="../features/integrate_login_signup.feature") +def test_login_signup(): + """Process Signup then Login""" + +"""Scenario: Logar senha errada após Cadastrar""" +@scenario(scenario_name="Logar senha errada após Cadastrar", feature_name="../features/integrate_login_signup.feature") +def test_login_signup_fail(): + """Process Signup then Login""" + +@given(parsers.cfparse('Usuário "{username}" está cadastrado')) +def verify_user(username: str): + """ + Assert that an user exists in db + + Args: + user (str): _description_ + """ + assert user_database.get_user_by_username(username) is not None + +@given(parsers.cfparse('Usuário "{username}" não está cadastrado')) +def verify_user(username: str): + """ + Assert that an user exists in db + + Args: + user (str): _description_ + """ + assert user_database.get_user_by_username(username) is None + +@given(parsers.cfparse('Usuário "{username}" possui senha "{senha}"')) +def verify_psw(username: str, senha: str): + """ + Assert user password + + Args: + username (str): _description_ + senha (str): _description_ + """ + user: User = user_database.get_user_by_username(username) + assert user.check_password(senha) + +@when( + parsers.cfparse('uma requisição "{request_type}" for enviada para "{login}", com Dados Login(usuário: "{username}", senha: "{senha}")'), + target_fixture="request_response" +) +def login_request(request_type: str, login: str, username: str, senha: str, client=TESTCLIENT): + """ + Faz uma requisição de login + + Args: + request_type (str): _description_ + username (str): _description_ + senha (str): _description_ + client (_type_, optional): _description_. Defaults to TestClient. + """ + login_request = { + "cpf_ou_user_ou_email": username, + "senha": senha + } + post_ = getattr(client, request_type.lower()) + return post_("/backend/api/auth/user/" + login, json=login_request) + +@then(parsers.cfparse('o status da resposta deve ser "{status}"')) +def check_response_status(status: str, request_response): + # print('\n\nresponse = ', request_response, '\n\n') + assert int(status) == request_response.status_code + +@then( + parsers.cfparse('o campo "{campo1}" possui o campo "{token}" com valor "$token_valor"'), + target_fixture="token_value" +) +def check_field_token(campo1: str, token: str, request_response): + request_response_json = request_response.json() + field = request_response_json[campo1][token] + return field + +@when( + parsers.cfparse('uma requisição "{request_type}" for enviada para "{verify}", com "$token_valor"'), + target_fixture="request_response" +) +def verify_request(request_type: str, verify: str, token_value, client = TESTCLIENT): + verify_request = { + "token" : token_value + } + post_ = getattr(client, request_type.lower()) + return post_("/backend/api/auth/user/" + verify, json=verify_request) + +@then( + parsers.cfparse('o campo "{campo1}" possui o campo "{campo2}"'), + target_fixture="campo_value" +) +def check_field(campo1: str, campo2: str, request_response): + request_response_json = request_response.json() + field = request_response_json[campo1][campo2] + return field + +@then( + parsers.cfparse('os elementos de "user" correspondem aos dados do usuário "{username}"') +) +def check_user_data(username: str, campo_value): + user: User = user_database.get_user_by_username(username) + assert user.username == campo_value['username'] + assert user.nome == campo_value['nome'] + assert user.sobrenome == campo_value['sobrenome'] + assert user.cpf == campo_value['cpf'] + + + +@then( + parsers.cfparse('o campo "{campo1}" tem o valor "{value}"') +) +def check_field_value(campo1: str, value: str, request_response): + request_response_json = request_response.json() + field = request_response_json[campo1] + assert field == value + + +@then(parsers.cfparse('Usuário "{username}" está cadastrado')) +@given(parsers.cfparse('Usuário "{username}" está cadastrado')) +def verify_user(username: str): + """ + Assert that an user exists in db + + Args: + user (str): _description_ + """ + assert user_database.get_user_by_username(username) is not None + +@given(parsers.cfparse('Usuário "{username}" não está cadastrado')) +def verify_user(username: str): + """ + Assert that an user exists in db + + Args: + user (str): _description_ + """ + assert user_database.get_user_by_username(username) is None + +@when( + parsers.cfparse('uma requisição "{request_type}" for enviada para "{signup}", com Dados Cadastrais('\ + 'nome: "{nome}", sobrenome: "{sobrenome}", user: "{username}", CPF: {cpf}, endereço: "{endereço}", '\ + 'CEP: "{cep}", data de nascimento: "{data_de_nascimento}", email: "{email}", senha: "{senha}")'), + target_fixture="request_response" +) +def signup_request( + request_type: str, + signup: str, + nome: str, + sobrenome: str, + username: str, + cpf: str, + endereço: str, + cep: str, + data_de_nascimento: str, + email: str, + senha: str, + client=TESTCLIENT +): + signup_request = { + "username": username, + "nome": nome, + "sobrenome": sobrenome, + "cpf": cpf, + "data_de_nascimento": data_de_nascimento, + "email": email, + "senha": senha, + "endereço": endereço, + "CEP": cep + } + post_ = getattr(client, request_type.lower()) + return post_("/backend/api/auth/user/" + signup, json=signup_request) + + +@then(parsers.cfparse('o status da resposta deve ser "{status}"')) +def check_response_status(status: str, request_response): + assert int(status) == request_response.status_code + +@then('o JSON da resposta indica que o cadastro foi bem sucedido') +def check_response_json(request_response): + request_response_json = request_response.json() + assert request_response_json['message'] == "Usuário cadastrado com sucesso" + +@then('o JSON da resposta indica que o cadastro foi mal sucedido') +def check_response_fail_json(request_response): + request_response_json = request_response.json() + assert request_response_json['message'] != "Usuário cadastrado com sucesso" + +@then(parsers.cfparse('o JSON da resposta indica que o campo "{campo}" foi mal preenchido')) +def check_response_field_fail_json(campo: str, request_response): + request_response_json = request_response.json() + assert f"Campo {campo} mal formulado" in request_response_json['data'] \ No newline at end of file diff --git a/backend/src/tests/service/features/auth_service.feature b/backend/src/tests/service/features/auth_service.feature index ba1f329c..16bb8504 100644 --- a/backend/src/tests/service/features/auth_service.feature +++ b/backend/src/tests/service/features/auth_service.feature @@ -10,8 +10,8 @@ Feature: Auth Service Scenario: login user fail Given Usuário com Cpf "444.324.424-09" está cadastrado - And o usuário possui a senha "12345Abcz" - When o método Login User é chamado com DadosLogin(cpf_ou_user_ou_email="444.324.424-09", senha="12345Abcx") + And o usuário possui a senha "12345Abcx" + When o método Login User é chamado com DadosLogin(cpf_ou_user_ou_email="444.324.424-09", senha="12345Abcz") Then o método deve retornar que o login foi mal sucedido And o campo "message" da resposta deve conter o valor "CPF ou Senha incorretos" diff --git a/backend/src/tests/service/features/items-service.feature b/backend/src/tests/service/features/items-service.feature deleted file mode 100644 index 50735a2f..00000000 --- a/backend/src/tests/service/features/items-service.feature +++ /dev/null @@ -1,12 +0,0 @@ -Feature: Items Service - -# Service -Scenario: Obter todos os itens - Given o método getItems do ItemService retorna um array com o item de nome "item" e id "123" - When o método getItems do ItemService for chamado - Then o array retornado deve conter o item de nome "item" e id "123" - -Scenario: Obter item por ID - Given o método getItem do ItemService retorna um item de nome "item" e id "123" - When o método getItem do ItemService for chamado com o id "123" - Then o item retornado deve ter o nome "item" e id "123" \ No newline at end of file diff --git a/backend/src/tests/service/features/payment_methods-service.feature b/backend/src/tests/service/features/payment_methods-service.feature new file mode 100644 index 00000000..1d633af5 --- /dev/null +++ b/backend/src/tests/service/features/payment_methods-service.feature @@ -0,0 +1,51 @@ +Feature: Payment Methods Service + +#Service +Scenario: Inserir cartão + Given o cartao de número "4916123456789012" e cpf "111.111.111-11" não existe no banco de dados + When o método inserting_card do PaymentMethodService for chamado com Cartao nome "masterCard", número "4916123456789012", cvv "847", cpf "111.111.111-11" e validade "2030-12-31" + Then o metodo insert_card retorna uma mensagem de confirmação para o cartao de número "4916123456789012" e cpf "111.111.111-11" + And o cartao de número "4916123456789012" e cpf "111.111.111-11" existe no banco de dados + + +Scenario: Inserir cartão com cpf inválido + Given o cartao de número "4916123456789012" e cpf "111.111.111" não existe no banco de dados + When o método inserting_card do PaymentMethodService for chamado com Cartao nome "masterCard", número "4916123456789012", cvv "847", cpf "111.111.111" e validade "2030-12-31" + Then o metodo insert_card retorna uma mensagem de insucesso para o cartao de nome "MasterCard", número "4916123456789012", cvv "847" e cpf "111.111.111" e validade "2030-12-31" + And o cartao de número "4916123456789012" e cpf "111.111.111" não existe no banco de dados + +Scenario: Inserir cartão fora da validade + Given o cartao de número "4916123456789012" e cpf "111.111.111-11" não existe no banco de dados + When o método inserting_card do PaymentMethodService for chamado com Cartao nome "masterCard", número "4916123456789012", cvv "847", cpf "111.111.111-11" e validade "2020-12-31" + Then o metodo insert_card retorna uma mensagem de insucesso para o cartao de nome "MasterCard", número "4916123456789012", cvv "847" e cpf "111.111.111-11" e validade "2020-12-31" + And o cartao de número "4916123456789012" e cpf "111.111.111" não existe no banco de dados + +Scenario: Inserir cartão com numero inválido + Given o cartao de número "4916123456789" e cpf "111.111.111" não existe no banco de dados + When o método inserting_card do PaymentMethodService for chamado com Cartao nome "masterCard", número "4916123456789", cvv "847", cpf "111.111.111" e validade "2030-12-31" + Then o metodo insert_card retorna uma mensagem de insucesso para o cartao de nome "MasterCard", número "4916123456789", cvv "847" e cpf "111.111.111" e validade "2030-12-31" + And o cartao de número "4916123456789012" e cpf "111.111.111" não existe no banco de dados + +Scenario: Inserir pix + Given o pix de nome "Breno Gabriel de Melo Lima" e cpf "222.222.222-22" não existe no banco de dados + When o método insert_pix do PaymentDatabase for chamado com Pix nome "Breno Gabriel de Melo Lima" e cpf "222.222.222-22" + Then o metodo insert_pix retorna uma mensagem de confirmação para o pix de cpf "222.222.222-22" + And o pix de nome "Breno Gabriel de Melo Lima" e cpf "222.222.222-22" existe no banco de dados + +# Scenario: Inserir pix com cpf inválido +# Given o pix de nome "Breno Gabriel de Melo Lima" e cpf "222.222.222-22" não existe no banco de dados +# When o método insert_pix do PaymentDatabase for chamado com Pix(nome "Breno Gabriel de Melo Lima" e cpf "222.222.222-22") +# Then o metodo insert_pix retorna uma mensagem de confirmação +# And o pix de nome "Breno Gabriel de Melo Lima" e cpf "222.222.222-22" existe no banco de dados + +# Scenario: Inserir boleto +# Given o pix de nome "Breno Gabriel de Melo Lima" e cpf "222.222.222-22" não existe no banco de dados +# When o método insert_pix do PaymentDatabase for chamado com Pix(nome "Breno Gabriel de Melo Lima" e cpf "222.222.222-22") +# Then o metodo insert_pix retorna uma mensagem de confirmação +# And o pix de nome "Breno Gabriel de Melo Lima" e cpf "222.222.222-22" existe no banco de dados + +# Scenario: Inserir boleto com cpf inválido +# Given o pix de nome "Breno Gabriel de Melo Lima" e cpf "222.222.222-22" não existe no banco de dados +# When o método insert_pix do PaymentDatabase for chamado com Pix(nome "Breno Gabriel de Melo Lima" e cpf "222.222.222-22") +# Then o metodo insert_pix retorna uma mensagem de confirmação +# And o pix de nome "Breno Gabriel de Melo Lima" e cpf "222.222.222-22" existe no banco de dados diff --git a/backend/src/tests/service/step_definitions/no_test_items_service.py b/backend/src/tests/service/step_definitions/no_test_items_service.py deleted file mode 100644 index 262c7723..00000000 --- a/backend/src/tests/service/step_definitions/no_test_items_service.py +++ /dev/null @@ -1,81 +0,0 @@ -from src.schemas.response import HTTPResponses, HttpResponseModel -from pytest_bdd import parsers, given, when, then, scenario -from src.service.impl.item_service import ItemService - -""" Scenario: Obter item por ID """ -# This method is used to define the scenario name and feature file path -@scenario(scenario_name="Obter item por ID", feature_name="../features/items-service.feature") -def test_service_get_item(): - """ Get item by id """ - -# Step definitions for the "Obter item por ID" scenario -@given(parsers.cfparse( - 'o método getItem do ItemService retorna um item de nome "{item_name}" e id "{item_id}"')) -def mock_item_service(item_id: str, item_name: str): - """ - Mock the ItemService.get_item() method to return an item with the given id and name - """ - - ItemService.get_item = lambda id : HttpResponseModel( - message=HTTPResponses.ITEM_FOUND().message, - status_code=HTTPResponses.ITEM_FOUND().status_code, - data={"id": item_id, "name": item_name} - ) - -@when( - parsers.cfparse('o método getItem do ItemService for chamado com o id "{item_id}"'), - target_fixture="context" -) -def get_item(context, item_id: str): - context['item'] = ItemService.get_item(item_id) - return context - -@then( - parsers.cfparse('o item retornado deve ter o nome "{item_name}" e id "{item_id}"'), - target_fixture="context" -) -def check_received_item(context, item_name: str, item_id: str): - assert context['item'].data.name == item_name - assert context['item'].data.id == item_id - - return context - -""" Scenario: Obter todos os itens """ - -@scenario(scenario_name="Obter todos os itens", feature_name="../features/items-service.feature") -def test_service_get_items(): - """ Get all items """ - -# Step definitions for the "Obter todos os itens" scenario -@given(parsers.cfparse( - 'o método getItems do ItemService retorna um array com o item de nome "{item_name}" e id "{item_id}"')) -def mock_item_service_list(item_name: str, item_id: str): - - ItemService.get_items = lambda: HttpResponseModel( - message=HTTPResponses.ITEM_FOUND().message, - status_code=HTTPResponses.ITEM_FOUND().status_code, - data={ - 'items': [{"id": item_id, "name": item_name}] - } - ) - -@when( - parsers.cfparse('o método getItems do ItemService for chamado'), - target_fixture="context" -) -def get_service_items(context): - context['items'] = ItemService.get_items() - return context - -@then( - parsers.cfparse('o item retornado deve ter o nome "{item_name}" e id "{item_id}"'), - target_fixture="context" -) -def check_service_list(context, item_id: str, item_name: str): - """ - Check if the item with the given id and name is in the response list - """ - - assert [{"id": item_id, "name": item_name}] == context['items'].data['items'] - - return context \ No newline at end of file diff --git a/backend/src/tests/service/step_definitions/test_auth_service b/backend/src/tests/service/step_definitions/test_auth_service.py similarity index 85% rename from backend/src/tests/service/step_definitions/test_auth_service rename to backend/src/tests/service/step_definitions/test_auth_service.py index 16b934a3..1180059a 100644 --- a/backend/src/tests/service/step_definitions/test_auth_service +++ b/backend/src/tests/service/step_definitions/test_auth_service.py @@ -19,6 +19,7 @@ @pytest.fixture(scope="session", autouse=True) def database_setup(): + user_database.remove_user_by_cpf("444.324.424-09") user = User.new( "Cleiton", "Cleiton Moura", @@ -27,7 +28,7 @@ def database_setup(): datetime.date.fromisocalendar(1,1,1), "Cleiton@momo", "12345Abcx" - ) + )[0] user_database.add_user(user) yield @@ -35,17 +36,17 @@ def database_setup(): user_database.remove_user_by_cpf("444.324.424-09") """Scenario: login user""" -@scenario(scenario_name=" login user", feature_name="../features/auth_service.feature") +@scenario(scenario_name="login user", feature_name="../features/auth_service.feature") def test_login(): """User login""" """Scenario: login user fail""" -@scenario(scenario_name=" login user fail", feature_name="../features/auth_service.feature") +@scenario(scenario_name="login user fail", feature_name="../features/auth_service.feature") def test_login_fail(): """User login fail""" """Scenario: login user fail 2""" -@scenario(scenario_name=" login user fail 2", feature_name="../features/auth_service.feature") +@scenario(scenario_name="login user fail 2", feature_name="../features/auth_service.feature") def test_login_fail2(): """User login fail""" @@ -73,11 +74,11 @@ def login(cpf: str, senha: str): @then('o método deve retornar que o login foi bem sucedido') def check_success(response): - assert response == HTTPLoginResponses.LOGIN_SUCCESSFUL() + assert response.message == "Login com sucesso" @then('o método deve retornar que o login foi mal sucedido') def check_fail(response): - assert response == HTTPLoginResponses.LOGIN_FAILED() + assert response.message == "Login falhou" or response.message == 'CPF ou Senha incorretos' @then(parsers.cfparse('o campo "{campo}" da resposta deve conter o valor "{value}"')) def check_value(campo: str, value: str, response): diff --git a/backend/src/tests/service/step_definitions/test_payment_methods.py b/backend/src/tests/service/step_definitions/test_payment_methods.py new file mode 100644 index 00000000..c0d97c81 --- /dev/null +++ b/backend/src/tests/service/step_definitions/test_payment_methods.py @@ -0,0 +1,138 @@ +from fastapi.testclient import TestClient +from unittest.mock import patch, MagicMock +from src.db.payment_database import * +from pytest_bdd import given, when, then, scenario, parsers +from datetime import * +import pytest +from bcrypt import hashpw, checkpw, gensalt +import datetime +from pydantic import BaseModel +from src.schemas.response import HTTPResponses, HttpResponseModel +from src.schemas.payment_schema import Cartao +from src.schemas.payment_response import HTTPPaymentResponse +from src.service.impl.payment_method_service import PaymentService +import src.service.impl.payment_method_service + +@scenario(scenario_name="Inserir cartão", feature_name="../features/payment_methods-service.feature") +def testar_cadastro_bem_sucedido(): + pass + + +@scenario(scenario_name="Inserir cartão com cpf inválido", feature_name="../features/payment_methods-service.feature") +def testar_cadastro_com_cpf_invalido(): + pass + + +@scenario(scenario_name="Inserir cartão fora da validade", feature_name="../features/payment_methods-service.feature") +def testar_cadastro_fora_validade(): + pass + + +@scenario(scenario_name="Inserir cartão fora da validade", feature_name="../features/payment_methods-service.feature") +def testar_cadastro_fora_validade(): + pass + + +@scenario(scenario_name="Inserir cartão com numero inválido", feature_name="../features/payment_methods-service.feature") +def testar_cadastro_numero_invalido(): + pass + + +@scenario(scenario_name="Inserir pix", feature_name="../features/payment_methods-service.feature") +def testar_cadastro_pix(): + pass + + +@then(parsers.cfparse('o cartao de número "{numero}" e cpf "{cpf}" não existe no banco de dados')) +@given(parsers.cfparse('o cartao de número "{numero}" e cpf "{cpf}" não existe no banco de dados')) + +def checar_cartao_nao_existe(numero: str, cpf: str): + + assert get_card_by_number_and_cpf(numero, cpf) is None + + +@then(parsers.cfparse('o cartao de número "{numero}" e cpf "{cpf}" existe no banco de dados')) +@given(parsers.cfparse('o cartao de número "{numero}" e cpf "{cpf}" existe no banco de dados')) + +def checar_cartao_existe(numero: str, cpf: str): + + assert get_card_by_number_and_cpf(cpf, numero) is not None + + +@given(parsers.cfparse('o pix de nome "{nome}" e cpf "{cpf}" não existe no banco de dados')) + +def checar_existencia_pix(nome: str, cpf: str): + + assert get_pix_by_cpf(cpf) is None + +@then(parsers.cfparse('o pix de nome "{nome}" e cpf "{cpf}" existe no banco de dados')) +@given(parsers.cfparse('o pix de nome "{nome}" e cpf "{cpf}" existe no banco de dados')) + +def checar_nao_existencia_pix(nome: str, cpf: str): + + assert get_pix_by_cpf(cpf) is not None + + +@when(parsers.cfparse('o método inserting_card do PaymentMethodService for chamado com Cartao nome "{nome}", número "{numero}", cvv "{cvv}", cpf "{cpf}" e validade "{validade}"') + , target_fixture="response") + +def inserir_cartao(nome: str, numero: str, cvv: str, cpf: str, validade: datetime): + + data_datetime = datetime.datetime.strptime(validade.strip('""'), "%Y-%m-%d").date() + + cartao = src.service.impl.payment_method_service.Cartao( + nome_cartao=nome, + numero_cartao=numero, + cvv= cvv, + cpf = cpf, + validade= data_datetime + ) + + return src.service.impl.payment_method_service.PaymentService.inserting_card(cartao) + + +@when(parsers.cfparse('o método insert_pix do PaymentDatabase for chamado com Pix nome "{nome}" e cpf "{cpf}"'), target_fixture="response") + +def inserir_pix(nome: str, cpf: str): + + pix = src.service.impl.payment_method_service.Pix( + nome_completo=nome, + cpf = cpf, + ) + + return src.service.impl.payment_method_service.PaymentService.insertion_pix(pix) + + +@then(parsers.cfparse('o metodo insert_pix retorna uma mensagem de confirmação para o pix de cpf {cpf}')) + +def verificar_mensagem_resposta(response, cpf: str): + + id = get_pix_id(cpf) + + res: HttpResponseModel = response + assert res == HTTPPaymentResponse.INSERTION_SUCESSFULLY(id) + + +@then(parsers.cfparse('o metodo insert_card retorna uma mensagem de confirmação para o cartao de número "{numero}" e cpf "{cpf}"')) + +def checar_retorno_cartao(response, numero: str, cpf: str): + + id = get_cartao_id(cpf, numero) + + res: HttpResponseModel = response + assert res == HTTPPaymentResponse.INSERTION_SUCESSFULLY(id) + + +@then(parsers.cfparse('o metodo insert_card retorna uma mensagem de insucesso para o cartao de nome "{nome_cartao}", número "{numero}", cvv "{cvv}" e cpf "{cpf}" e validade "{validade}"')) + +def checar_retorno_insucesso_cartao(response, nome_cartao: str, numero: str, cvv: str, cpf: str, validade: str): + + data_datetime = datetime.datetime.strptime(validade.strip('""'), "%Y-%m-%d").date() + + sucesso, problemas = insert_card(nome_cartao, numero, cvv, cpf, data_datetime) + + res: HttpResponseModel = response + assert res == HTTPPaymentResponse.BAD_REQUEST(problemas) + + + diff --git a/backend/src/tests/service/test_auth_service.py b/backend/src/tests/service/test_auth_service2.py similarity index 100% rename from backend/src/tests/service/test_auth_service.py rename to backend/src/tests/service/test_auth_service2.py diff --git a/backend/src/tests/service/test_rec_service.py b/backend/src/tests/service/test_rec_service.py new file mode 100644 index 00000000..5e8dacdf --- /dev/null +++ b/backend/src/tests/service/test_rec_service.py @@ -0,0 +1,79 @@ +from src.db.__init__ import user_database_example as db +from src.db.__init__ import recuperacao_database_test as db_recuperacao +from src.db.codigos_rec_database import Recuperacao +from src.service.impl.recuperation_service import RecuperationService +from datetime import datetime, timedelta +import pytest +import src.service.impl.signup_service as signup + +@pytest.fixture(scope="module") +def user_valido(): + dados_cadastrais = signup.DadosCadastrais( + username="Peterson", + nome = "Peterson", + sobrenome="Melo", + cpf="111.111.111-02", + data_de_nascimento=datetime(2003,5,29), + email="djaar@cin.ufpe.br", + senha="faculdade123", + endereço= "Rua tal", + CEP="50000-000" + ) + + signup.SingUpService.signup_user(dados_cadastrais, db) + + user = db.get_user_by_email("djaar@cin.ufpe.br") + + yield user + + db.clear_database() + db_recuperacao.clear_database() + +# Fixture para fornecer um email válido para os testes +@pytest.fixture +def email_valido(user_valido): + return user_valido.email + +# Fixture para fornecer um código válido para os testes +@pytest.fixture +def codigo_valido(): + codigo = db_recuperacao.get_rec_by_email("djaar@cin.ufpe.br").codigo + return codigo + +@pytest.fixture +def codigo_expirado(codigo_valido): + recuperacao = Recuperacao("djaar@cin.ufpe.br",codigo_valido,datetime.now() - timedelta(hours=1,seconds=1) ) + db_recuperacao.add_recuperacao(recuperacao) + return recuperacao.codigo + +# Fixture para fornecer uma nova senha válida para os testes +@pytest.fixture +def nova_senha_valida(): + return "nova_senha123" + + +# Testes de Recuperação de Conta +def test_recuperar_conta_email_nao_cadastrado(): + assert RecuperationService.recuperar_conta("emailnaocadastrado@example.com", "123456", "nova_senha", "nova_senha", db_user=db, db_recuperacao=db_recuperacao) == "Email não cadastrado" + +def test_recuperar_conta_sem_recuperacao(email_valido): + assert RecuperationService.recuperar_conta(email_valido, "123456", "nova_senha", "nova_senha", db_user=db, db_recuperacao=db_recuperacao) == "Não há recuperação solicitada para este email" + +# Testes de Envio de Email +def test_enviar_email(email_valido): + assert RecuperationService.enviar_email(email_valido, db_user=db, db_recuperacao=db_recuperacao) == True + +def test_recuperar_conta(email_valido, codigo_valido, nova_senha_valida): + assert RecuperationService.recuperar_conta(email_valido, codigo_valido, nova_senha_valida, nova_senha_valida, db_user=db, db_recuperacao=db_recuperacao) == True + +def test_recuperar_conta_codigo_incorreto(email_valido, codigo_valido, nova_senha_valida): + assert RecuperationService.recuperar_conta(email_valido, "codigo_incorreto", nova_senha_valida, nova_senha_valida, db_user=db, db_recuperacao=db_recuperacao) == "Código Incorreto" + +def test_recuperar_conta_senhas_nao_coincidem(email_valido, codigo_valido): + assert RecuperationService.recuperar_conta(email_valido, codigo_valido, "nova_senha", "outra_senha", db_user=db, db_recuperacao=db_recuperacao) == "Senhas não coincidem" + +def test_recuperar_conta_senha_invalida(email_valido, codigo_valido): + assert RecuperationService.recuperar_conta(email_valido, codigo_valido, "", "", db_user=db, db_recuperacao=db_recuperacao) == "Senha inválida" + +def test_recuperar_conta_tempo_expirado(email_valido, codigo_expirado, nova_senha_valida): + assert RecuperationService.recuperar_conta(email_valido, codigo_expirado, nova_senha_valida, nova_senha_valida, db_user=db, db_recuperacao=db_recuperacao) == "Tempo expirado" diff --git a/features/cancel_orders.feature b/features/cancel_orders.feature new file mode 100644 index 00000000..f416d404 --- /dev/null +++ b/features/cancel_orders.feature @@ -0,0 +1,54 @@ +Feature: Cancelamento de pedidos +As a usuário comum do sistema +I want to acessar meus pedidos +So that eu possa cancelar meus pedidos + +Cenário: Cancelamento de pedido bem sucedido +Given o usuário de email “usuario@gmail.com” +And o usuário vê o pedido de número “59” no “histórico de pedidos” +When o usuário seleciona em "detalhes do pedido" do pedido de número "59" +Then ele consegue ver os detalhes data do pedido "20/11/2023", valor "R$131,50" e status "a caminho" +When o usuário seleciona em “cancelar pedido” +Then o usuário recebe “requisição do motivo de cancelamento” +And o usuário preenche com o motivo de cancelamento com “Pedido demorou muito” +Then o usuário recebe a mensagem "Pedido cancelado com sucesso!" +And o pedido de número “59” está no “histórico de pedidos” como “cancelado” + +Cenário: Cancelamento de pedido sem o preenchimento de informações +Given o usuário de email “usuario@gmail.com” +And o usuário vê o pedido de número “59” no “histórico de pedidos” +When o usuário seleciona em "detalhes do pedido" do pedido de número "59" +Then ele consegue ver os detalhes data do pedido "20/11/2023", valor "R$131,50" e status "a caminho" +And o usuário seleciona em “cancelar pedido” +Then o usuário recebe “requisição do motivo de cancelamento” +And o usuário seleciona em “confirmar o cancelamento” +Then o usuário recebe a mensagem “Você precisa preencher todos os dados, tente novamente!” +And o usuário recebe “requisição de senha e motivo de cancelamento” novamente + +Cenário: Cancelamento de pedido já entregue +Given o usuário de email “usuario@gmail.com” +And o usuário vê o pedido de número “59” no “histórico de pedidos” +When o usuário seleciona em "detalhes do pedido" do pedido de número "59" +Then ele consegue ver os detalhes data do pedido "20/11/2023", valor "R$131,50" e status "entregue" +When o usuário seleciona em “cancelar pedido” +Then o usuário recebe “requisição do motivo de cancelamento” +And o usuário preenche com o motivo de cancelamento com “Não posso mais receber o produto” +Then o usuário recebe a mensagem "Este pedido já foi entregue" +And o usuário volta para "detalhes do pedido" + +Cenário: Cancelamento de pedido já cancelado +Given o usuário de email “usuario@gmail.com” +And o usuário vê o pedido de número “59” no “histórico de pedidos” +When o usuário seleciona em "detalhes do pedido" do pedido de número "59" +Then ele consegue ver os detalhes data do pedido "20/11/2023", valor "R$131,50" e status "cancelado" +And o usuário seleciona em “cancelar pedido” +Then o usuário recebe “requisição do motivo de cancelamento” +And o usuário preenche com o motivo de cancelamento com “Não quero mais o produto” +Then o usuário recebe a mensagem "Este pedido já foi cancelado" +And o usuário volta para "detalhes do pedido" + +Cenário: Vendo lista de pedidos cancelados +Given o usuário de email "usuario@gmail.com" está na página "histórico de pedidos" +And o usuário vê os pedidos "10", "12", "15" e "19" +When o usuário clica em "pedidos cancelados" +Then o usuário agora está na página "pedidos cancelados" e vê os pedidos "12" e "15" \ No newline at end of file diff --git a/features/carrinho.feature b/features/carrinho.feature index 1c5e34f4..0757045c 100644 --- a/features/carrinho.feature +++ b/features/carrinho.feature @@ -4,7 +4,7 @@ I want to gerenciar os meus interesses de compra So that eu possa tomar decisões informadas sobre o que comprar Scenario: Remover item do carrinho com sucesso -Given eu estou logado com o email “usuario@gmail.com” +Given eu estou logado com o CPF "123.456.789-10" And estou na página “Carrinho” And vejo o item “Camisa” com preço “60” e quantidade "1" na lista de itens do carrinho And vejo o item “Calça” com preço “100” e quantidade "1" na lista de itens do carrinho @@ -16,7 +16,7 @@ And vejo uma mensagem de confirmação And vejo que o preço total agora é “100” Scenario: Atualizar a quantidade de um item no carrinho -Given eu estou logado com o email “usuario@gmail.com” +Given eu estou logado com o CPF "123.456.789-10" And eu estou na página “Carrinho” And eu vejo que tem “2” do item “Camisa” And o preço do carrinho é de “120” @@ -26,7 +26,7 @@ And eu vejo o número “1” ao lado do item “Camisa” And o preço no carrinho é “60” Scenario: Finalizar compra -Given eu estou logado com o email “usuario@gmail.com” +Given eu estou logado com o CPF "123.456.789-10" And estou na página “Carrinho” And eu vejo “Camisa” na lista de itens do carrinho When eu seleciono a opção “Realizar pagamento” @@ -36,7 +36,7 @@ And eu estou na página “Carrinho” And “Camisa” não está mais no carrinho Scenario: Adicionar item no carrinho com sucesso -Given eu estou logado com o email “usuario@gmail.com” +Given eu estou logado com o CPF "123.456.789-10" And estou na página “Produto” de um item “Camisa” And o carrinho tem “1” produto When eu seleciono “Adicionar produto no carrinho” @@ -44,7 +44,7 @@ Then eu recebo uma mensagem de confirmação And eu vejo que o carrinho tem “2” itens no carrinho Scenario: Falha no pagamento -Given eu estou logado com o email "usuario@gmail.com" +Given eu estou logado com o CPF "123.456.789-10" And eu estou na página "Pagamento" When eu realizo o pagamento Then eu vejo uma mensagem de erro @@ -53,7 +53,7 @@ And Eu estou na página "Carrinho" And o carrinho permanece inalterado Scenario: Falha em remover item do carrinho -Given eu estou logado com o email “usuario@gmail.com” +Given eu estou logado com o CPF "123.456.789-10" And estou na página “Carrinho” And vejo o item “Camisa” com preço “60” na lista de itens do carrinho And vejo o item “Calça” com preço “100” na lista de itens do carrinho @@ -65,7 +65,7 @@ And eu vejo o item “Calça” com preço “100” no carrinho And vejo que o preço ainda é “160” Scenario: Produto do carrinho esgotado -Given eu estou logado com o email "usuario@gmail.com" +Given eu estou logado com o CPF "123.456.789-10" And eu estou na página "Carrinho" And vejo o item "Camisa" no carrinho When eu tento realizar o pagamento @@ -74,13 +74,13 @@ And estou na página "Carrinho" And o produto "Camisa" está marcado como "Esgotado" Scenario: Ir para o carrinho -Given estou logado com o email "usuario@gmail.com" +Given estou logado com o CPF "123.456.789-10" And estou em qualquer página do sistema When eu tento clicar no ícone do carrinho Then eu estou na página "Carrinho" Scenario: Alterar endereço destino -Given estou logado com o email "usuario@gmail.com" +Given estou logado com o CPF "123.456.789-10" And estou na página "Carrinho" And vejo o endereço "Rua 12345, nº 250" When eu seleciono a opção "Alterar endereço" diff --git a/features/estimated_time_arrival.feature b/features/estimated_time_arrival.feature index cee3638e..485fbb54 100644 --- a/features/estimated_time_arrival.feature +++ b/features/estimated_time_arrival.feature @@ -3,24 +3,32 @@ As a usuário comum do sistema I want to conferir o tempo estimado de entrega So that eu possa saber em até quanto tempo receberei meu pedido -Cenário: Tempo estimado de entrega de pedido à caminho -Given o usuário de email “usuario@gmail.com” está na página “histórico de pedidos” -And o usuário de email “usuario@gmail.com” vê o pedido de número “80” no “histórico de pedidos” como “a caminho” -When o usuário de email “usuario@gmail.com” seleciona em “acompanhar pedido” de número “80” -Then o usuário de email “usuario@gmail.com” é enviado à pagina de “acompanhamento de pedido” -And o o usuário de email “usuario@gmail.com” pode ver o “tempo estimado de entrega” +Cenário: Cálculo de tempo estimado de entrega com CEP do user inválido +Given o usuário de email "usuario@gmail.com" e CEP "11111111" está na página "tempo estimado de entrega" do produto de número "19" +And o usuário seleciona "calcular estimativa de entrega" +Then o usuário recebe a mensagem "Seu CEP é inválido, coloque nas suas informações de endereço um CEP válido" +And o usuário é redirecionado para a página de "alteração dos dados cadastrais" +Cenário: Cálculo de tempo estimado de entrega entre o mesmo estado +Given o usuário de email "usuario@gmail.com" e CEP "55875970" está na página "tempo estimado de entrega" do produto de número "21" +And o usuário seleciona "calcular estimativa de entrega" +Then o usuário pode ver a data de estimativa de entrega do produto "16/02/2024" +And o modelo de entrega "tradicional" -Cenário: Tempo estimado de entrega de pedido cancelado -Given o usuário de email “usuario@gmail.com” está na página “histórico de pedidos” -And o usuário de email “usuario@gmail.com” vê o pedido de número “75” no “histórico de pedidos” como “cancelado” -When o usuário de email “usuario@gmail.com” seleciona em “acompanhar pedido” de número “75” -Then o usuário de email “usuario@gmail.com” é enviado à pagina de “acompanhamento de pedido” -And o o usuário de email “usuario@gmail.com” pode ver o aviso “pedido cancelado” +Cenário: Cálculo de tempo estimado de entrega na mesma região +Given o usuário de email "usuario@gmail.com" e CEP "58819-970" está na página "tempo estimado de entrega" do produto de número "21" +And o usuário seleciona "calcular estimativa de entrega" +Then o usuário pode ver a estimativa de entrega do produto "18/02/2024" +And o modelo de entrega "expresso" -Cenário: Tempo estimado de entrega de pedido entregue -Given o usuário de email “usuario@gmail.com” está na página “histórico de pedidos” -And o usuário de email “usuario@gmail.com” vê o pedido de número “70” no “histórico de pedidos” como “cancelado” -When o usuário de email “usuario@gmail.com” seleciona em “acompanhar pedido” de número “70” -Then o usuário de email “usuario@gmail.com” é enviado à pagina de “acompanhamento de pedido” -And o o usuário de email “usuario@gmail.com” pode ver o aviso “pedido já foi entregue” +Cenário: Cálculo de tempo estimado de entrega entre regiões diferentes +Given o usuário de email "usuario@gmail.com" e CEP "01153-000" está na página "tempo estimado de entrega" do produto de número "29" +And o usuário seleciona "calcular estimativa de entrega" +Then o usuário pode ver a estimativa de entrega do produto "28/02/2024" +And o modelo de entrega "aéreo" + +Cenário: Consulta de tempo estimado de entrega de pedido +Given o usuário de email “usuario@gmail.com” e CEP "58819-970" está na página “histórico de pedidos” +And o usuário vê o pedido de número “80” no “histórico de pedidos” +When o usuário seleciona em "detalhes do pedido" do pedido de número "80" +Then ele consegue ver os detalhes data do pedido "20/11/2023", valor "R$131,50", status "a caminho", data estimada de entrega "15/02/2024" e o modelo de entrega "expresso" diff --git a/features/login.feature b/features/login.feature index ca2e50f8..fe01bb9f 100644 --- a/features/login.feature +++ b/features/login.feature @@ -70,4 +70,4 @@ Given Usuário "Gabriel" está cadastrado And Usuário "Gabriel" possui senha "senha1234" When uma requisição "POST" for enviada para "login", com Dados Login(usuário: "Gabriel", senha: "senha1235") Then o status da resposta deve ser "401" -And o campo "message" tem o valor "CPF ou Senha incorretos" +And o campo "message" tem o valor "CPF ou Senha incorretos" \ No newline at end of file diff --git a/features/orders_cancel.feature b/features/orders_cancel.feature deleted file mode 100644 index 0c0debde..00000000 --- a/features/orders_cancel.feature +++ /dev/null @@ -1,31 +0,0 @@ -Feature: Cancelamento de pedidos -As a usuário comum do sistema -I want to acessar meus pedidos -So that eu possa cancelar meus pedidos - -Cenário: Cancelamento de pedido bem sucedido -Given o usuário de email “usuario@gmail.com” está na página “histórico de pedidos” -And o usuário de email “usuario@gmail.com” vê o pedido de número “59” no “histórico de pedidos” como “a caminho” -When o usuário de email “usuario@gmail.com” seleciona em “cancelar pedido” do pedido de número “59” -Then o usuário de email “usuario@gmail.com” recebe “requisição de senha e motivo de cancelamento” -And o usuário de email “usuario@gmail.com” preenche com o motivo de cancelamento com “Pedido demorou muito” e preenche sua senha corretamente -Then o pedido de número “59” está no “histórico de pedidos” como “cancelado” -And o usuário de email “usuario@gmail.com” recebe a mensagem “Pedido cancelado com sucesso!” - -Cenário: Cancelamento de pedido com senha errada -Given o usuário de email “usuario@gmail.com” está na página “histórico de pedidos” -And o usuário de email “usuario@gmail.com” vê o pedido de número “59” no “histórico de pedidos” como “à caminho” -When o usuário de email “usuario@gmail.com” seleciona em “cancelar pedido” do pedido de número “59” -Then o usuário de email “usuario@gmail.com” recebe “requisição de senha e motivo de cancelamento” -And o usuário de email “usuario@gmail.com” preenche com o motivo de cancelamento com “Pedido demorou muito” e preenche a senha errada -Then o usuário de email “usuario@gmail.com” recebe a mensagem “Senha incorreta, tente novamente!” -And o usuário de email “usuario@gmail.com” recebe “requisição de senha e motivo de cancelamento” novamente - -Cenário: Cancelamento de pedido sem o preenchimento de informações -Given o usuário de email “usuario@gmail.com” está na página “histórico de pedidos” -And o usuário de email “usuario@gmail.com” vê o pedido de número “59” no “histórico de pedidos” como “a caminho” -When o usuário de email “usuario@gmail.com” seleciona em “cancelar pedido” do pedido de número “59” -Then o usuário de email “usuario@gmail.com” recebe “requisição de senha e motivo de cancelamento” -And o usuário de email “usuario@gmail.com” seleciona em “confirmar o cancelamento” -Then o usuário de email “usuario@gmail.com” recebe a mensagem “Você precisa preencher todos os dados, tente novamente!” -And o usuário de email “usuario@gmail.com” recebe “requisição de senha e motivo de cancelamento” novamente diff --git a/features/payment_method.feature b/features/payment_method.feature new file mode 100644 index 00000000..051ba485 --- /dev/null +++ b/features/payment_method.feature @@ -0,0 +1,108 @@ +Feature: Cadastro e manutenção de métodos de pagamento (inserir, remover, atualizar) +As a usuário cadastrado +I want inserir, remover ou atualizar os métodos de pagamento +So that eu posso realizar o pagamento de minhas compras + +Scenario: Inserção bem-sucedida de boleto como método de pagamento +Given o usuário de email "usuário@gmail.com" está na página "Métodos de pagamento" +When usuário clicar na opção "inserir método de pagamento" +And seleciona o a opção "Boleto" +And preencher o campo "Nome completo" com "Breno Gabriel de Melo Lima" +And preencher o campo "CPF" com "925.830.910-34" +And o usuário clica em "Confirmar" +Then o método de pagamento será cadastrado no sistema + +Scenario: Inserção bem-sucedida de Pix como método de pagamento +Given o usuário de email "usuário@gmail.com" está na página "Métodos de pagamento" +When usuário clicar na opção "inserir método de pagamento" +And seleciona o a opção "Pix" +And preencher o campo "Nome completo" com "Breno Gabriel de Melo Lima" +And preencher o campo "CPF" com "925.830.910-34" +And o usuário clica em "Confirmar" +Then o método de pagamento será cadastrado no sistema + +Scenario: Inserção bem-sucedida de cartão como método de pagamento +Given o usuário de email "usuário@gmail.com" está na página "Métodos de pagamento" +When usuário clicar na opção "inserir método de pagamento" +And seleciona o a opção "Cartão" +And preencher o campo "Nome do cartão" com "Nubank" +And preencher o campo "CPF" com "925.830.910-34" +And preencher o campo "CVV" com "1234" +And preencher o campo "Número do cartão" com "12345678" +And preencher o campo "Validade" com "06/2028" +And o usuário clica em "Confirmar" +Then o método de pagamento será cadastrado no sistema + +Scenario: Inserção mal-sucedida de boleto como método de pagamento +Given o usuário de email "usuário@gmail.com" está na página "Métodos de pagamento" +When usuário clicar na opção "inserir método de pagamento" +And seleciona o a opção "Boleto" +And preencher o campo "Nome completo" com "Breno Gabriel de Melo Lima" +And preencher o campo "CPF" com "925.830.910" +And o usuário clica em "Confirmar" +Then o usuário visualiza a mensagem "Informações inválidas" + +Scenario: Inserção mal-sucedida de Pix como método de pagamento +Given o usuário de email "usuário@gmail.com" está na página "Métodos de pagamento" +When usuário clicar na opção "inserir método de pagamento" +And seleciona o a opção "Boleto" +And preencher o campo "Nome completo" com "Breno Gabriel de Melo Lima" +And preencher o campo "CPF" com "925.830.910" +And o usuário clica em "Confirmar" +Then o usuário visualiza a mensagem "Informações inválidas" + +Scenario: Inserção mal-sucedida de cartão como método de pagamento devido a CPF inválido +Given o usuário de email "usuário@gmail.com" está na página "Métodos de pagamento" +When usuário clicar na opção "inserir método de pagamento" +And seleciona o a opção "Cartão" +And preencher o campo "Nome do cartão" com "Nubank" +And preencher o campo "CPF" com "925.830.910" +And preencher o campo "CVV" com "1234" +And preencher o campo "Número do cartão" com "12345678" +And preencher o campo "Validade" com "06/2028" +And o usuário clica em "Confirmar" +Then o usuário visualiza a mensagem "Informações inválidas" + +Scenario: Inserção mal-sucedida de cartão como método de pagamento devido a CVV inválido +Given o usuário de email "usuário@gmail.com" está na página "Métodos de pagamento" +When usuário clicar na opção "inserir método de pagamento" +And seleciona o a opção "Cartão" +And preencher o campo "Nome do cartão" com "Nubank" +And preencher o campo "CPF" com "925.830.910-34" +And preencher o campo "CVV" com "123" +And preencher o campo "Número do cartão" com "12345678" +And preencher o campo "Validade" com "06/2028" +And o usuário clica em "Confirmar" +Then o usuário visualiza a mensagem "Informações inválidas" + +Scenario: Inserção mal-sucedida de cartão como método de pagamento devido a número de cartão inválido +Given o usuário de email "usuário@gmail.com" está na página "Métodos de pagamento" +When usuário clicar na opção "inserir método de pagamento" +And seleciona o a opção "Cartão" +And preencher o campo "Nome do cartão" com "Nubank" +And preencher o campo "CPF" com "925.830.910" +And preencher o campo "CVV" com "1234" +And preencher o campo "Número do cartão" com "123456" +And preencher o campo "Validade" com "06/2028" +And o usuário clica em "Confirmar" +Then o usuário visualiza a mensagem "Informações inválidas" + +Scenario: Inserção mal-sucedida de cartão como método de pagamento devido a data de validade inválida +Given o usuário de email "usuário@gmail.com" está na página "Métodos de pagamento" +When usuário clicar na opção "inserir método de pagamento" +And seleciona o a opção "Cartão" +And preencher o campo "Nome do cartão" com "Nubank" +And preencher o campo "CPF" com "925.830.910" +And preencher o campo "CVV" com "1234" +And preencher o campo "Número do cartão" com "12345678" +And preencher o campo "Validade" com "06/2020" +And o usuário clica em "Confirmar" +Then o usuário visualiza a mensagem "Informações inválidas" + +Scenario: Atualização bem-sucedida de informações do cartão +Given o usuário de email "usuário@gmail.com" está na página "Métodos de pagamento" +When usuário clicar na opção "atualizar método de pagamento" +And selecionar a opção "nubank" +And atualizar o campo "Nome completo" com "Maria Alvez da Cunha" +And o usuário clica em "Confirmar" +Then o usuário visualiza a mensagem "Informações atualizadas"