diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index c065d30..c59b314 100644 --- a/.github/workflows/CD.yml +++ b/.github/workflows/CD.yml @@ -1,4 +1,3 @@ - name: CD on: @@ -7,52 +6,83 @@ on: - prod - dev - homolog + - fix/githubActions workflow_dispatch: jobs: DeployToAWS: environment: - name: ${{ github.ref_name }} + name: ${{ github.ref_name }} runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - - uses: actions/checkout@v2 - - name: Setup AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 - with: - aws-region: ${{ vars.AWS_REGION }} - role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/GithubActionsRole - role-session-name: github-action - - - name: Setting stage and stack name - run: | - echo "STAGE=${{ github.ref_name }}" - echo "STACK_NAME=ReservationStackScheduleANDCourts${{github.ref_name}}" >> $GITHUB_ENV - - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: '3.9' - - - name: Installing Dependencies - run: | - npm install -g aws-cdk - cd iac - pip install -r requirements.txt - - - name: DeployWithCDK - run: | - cd iac - cdk synth - cdk deploy --require-approval never - - env: - AWS_REGION: ${{ vars.AWS_REGION }} - AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }} - STACK_NAME: ${{ env.STACK_NAME }} - GITHUB_REF_NAME: ${{ github.ref_name }} - GRAPH_MICROSOFT_ENDPOINT: ${{ secrets.GRAPH_MICROSOFT_ENDPOINT }} \ No newline at end of file + - uses: actions/checkout@v2 + + - name: Set AWS Account ID + run: | + if [[ "${{ github.ref_name }}" == "dev" ]]; then + echo "AWS_ACCOUNT_ID=${{ secrets.AWS_ACCOUNT_ID_DEV }}" >> $GITHUB_ENV + elif [[ "${{ github.ref_name }}" == "homolog" ]]; then + echo "AWS_ACCOUNT_ID=${{ secrets.AWS_ACCOUNT_ID_HOML }}" >> $GITHUB_ENV + elif [[ "${{ github.ref_name }}" == "prod" ]]; then + echo "AWS_ACCOUNT_ID=${{ secrets.AWS_ACCOUNT_ID_PROD }}" >> $GITHUB_ENV + else + echo "Invalid branch name!" && exit 1 + fi + + - name: Setup AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-region: ${{ vars.AWS_REGION }} + role-to-assume: arn:aws:iam::${{ env.AWS_ACCOUNT_ID }}:role/GithubActionsRole + role-session-name: github-action + + - name: Setting stage and stack name + run: | + echo "STAGE=${{ github.ref_name }}" + if [[ "${{ github.ref_name }}" == "prod" ]]; then + echo "STAGE=prod" >> $GITHUB_ENV + elif [[ "${{ github.ref_name }}" == "homolog" ]]; then + echo "STAGE=homolog" >> $GITHUB_ENV + elif [[ "${{ github.ref_name }}" == "dev" ]]; then + echo "STAGE=dev" >> $GITHUB_ENV + elif [[ "${{ github.ref_name }}" == "fix/githubActions" ]]; then + echo "STAGE=dev" >> $GITHUB_ENV + else + echo "Invalid branch name!" && exit 1 + fi + echo "STACK_NAME=ReservationStackScheduleANDCourts${{env.STAGE}}" >> $GITHUB_ENV + + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.13" + + - name: Installing Dependencies + run: | + npm install -g aws-cdk + cd iac + pip install -r requirements.txt + + - name: DeployWithCDK + run: | + cd iac + cdk synth + cdk deploy --require-approval never + + env: + AWS_REGION: ${{ vars.AWS_REGION }} + AWS_ACCOUNT_ID: ${{ env.AWS_ACCOUNT_ID }} + STACK_NAME: ${{ env.STACK_NAME }} + GITHUB_REF_NAME: ${{ github.ref_name }} + MSS_NAME: ${{ github.event.repository.name }} + S3_ASSETS_CDN: ${{ vars.S3_ASSETS_CDN }} + AUTH_DEV_SYSTEM_USERPOOL_ARN_DEV: ${{ secrets.AUTH_DEV_SYSTEM_USERPOOL_ARN_DEV }} + FROM_EMAIL: ${{ vars.FROM_EMAIL }} + HIDDEN_COPY: ${{ vars.HIDDEN_COPY }} + REPLY_TO_EMAIL: ${{ vars.REPLY_TO_EMAIL }} + USER_API_URL: ${{ secrets.USER_API_URL }} diff --git a/.gitignore b/.gitignore index 5e06bd6..9d783f9 100644 --- a/.gitignore +++ b/.gitignore @@ -135,5 +135,5 @@ dmypy.json /.idea/ iac/local/docker/dynamodb/shared-local-instance.db - +# MACOS temp files .DS_Store \ No newline at end of file diff --git a/iac/adjust_layer_directory.py b/iac/adjust_layer_directory.py index 5232368..61845b2 100644 --- a/iac/adjust_layer_directory.py +++ b/iac/adjust_layer_directory.py @@ -1,37 +1,57 @@ import os import shutil -from pathlib import Path -import sys - -IAC_DIRECTORY_NAME = "iac" -SOURCE_DIRECTORY_NAME = "src" -LAMBDA_LAYER_PREFIX = os.path.join("python", "src") - - -def adjust_layer_directory(shared_dir_name: str, destination: str): - # Get the root directory of the source directory - root_directory = Path(__file__).parent.parent - iac_directory = os.path.join(root_directory, IAC_DIRECTORY_NAME) - - print(f"Root directory: {root_directory}") - print(f"Root direcotry files: {os.listdir(root_directory)}") - print(f"IaC directory: {iac_directory}") - print(f"IaC directory files: {os.listdir(iac_directory)}") - - - # Get the destination and source directory - destination_directory = os.path.join(root_directory, IAC_DIRECTORY_NAME, destination) - source_directory = os.path.join(root_directory, SOURCE_DIRECTORY_NAME, shared_dir_name) - - # Delete the destination directory if it exists - if os.path.exists(destination_directory): - shutil.rmtree(destination_directory) - - # Copy the source directory to the destination directory - shutil.copytree(source_directory, os.path.join(destination_directory, LAMBDA_LAYER_PREFIX, shared_dir_name)) - print( - f"Copying files from {source_directory} to {os.path.join(destination_directory, LAMBDA_LAYER_PREFIX, shared_dir_name)}") - - +import subprocess +from pathlib import Path # Importe a biblioteca pathlib + +# --- Configurações --- +BUILD_DIRECTORY = "build" +PYTHON_TOP_LEVEL_DIR = os.path.join(BUILD_DIRECTORY, "python") +REQUIREMENTS_FILE = "requirements-layer.txt" + +# --- CONSTRUÇÃO CORRETA DO CAMINHO --- +# Pega o diretório do projeto (a raiz 'reservation_api') subindo um nível a partir do script atual. +PROJECT_ROOT = Path(__file__).parent.parent +# Agora, constrói o caminho para 'src/shared' a partir da raiz do projeto. +SHARED_CODE_SOURCE = os.path.join(PROJECT_ROOT, "src", "shared") + + +def adjust_layer_directory(): + """ + Prepara um diretório 'build' para uma Lambda Layer do AWS CDK. + + A função junta o código local compartilhado e as dependências externas (pip) + na estrutura de pastas que a Lambda espera (/python). + """ + + # Garante que o build seja sempre limpo, removendo qualquer artefato antigo. + if os.path.exists(BUILD_DIRECTORY): + shutil.rmtree(BUILD_DIRECTORY) + + # Cria a estrutura de pastas 'build/python/src/'. + # Isso é necessário para que os imports 'from src.shared...' funcionem na Lambda. + shared_code_intermediate_dir = os.path.join(PYTHON_TOP_LEVEL_DIR, "src") + os.makedirs(shared_code_intermediate_dir) + + # Copia o código compartilhado (de 'src/shared') para dentro da estrutura da Layer. + # O resultado final será 'build/python/src/shared'. + print(f"Copiando código de: {SHARED_CODE_SOURCE}") # Adicionado para debug + shared_code_dest = os.path.join(shared_code_intermediate_dir, os.path.basename(SHARED_CODE_SOURCE)) + shutil.copytree(SHARED_CODE_SOURCE, shared_code_dest) + + # Se o arquivo de dependências existir, instala todas as bibliotecas. + # O arquivo de requirements também precisa ser lido a partir da raiz. + requirements_path = os.path.join(PROJECT_ROOT, REQUIREMENTS_FILE) + if os.path.exists(requirements_path): + # Instala os pacotes diretamente na pasta 'build/python'. + # Isso permite que a Lambda importe as bibliotecas de forma padrão (ex: import requests). + subprocess.check_call( + ["pip", "install", "-r", requirements_path, "-t", PYTHON_TOP_LEVEL_DIR, "--no-cache-dir"] + ) + else: + # Apenas um aviso caso o arquivo não seja encontrado. + print(f"Aviso: Arquivo '{requirements_path}' não encontrado. Nenhuma dependência externa será instalada.") + + +# Ponto de entrada do script. if __name__ == '__main__': - adjust_layer_directory(shared_dir_name="shared", destination="copied_shared") \ No newline at end of file + adjust_layer_directory() \ No newline at end of file diff --git a/iac/app.py b/iac/app.py index edebb46..6e19da3 100644 --- a/iac/app.py +++ b/iac/app.py @@ -10,7 +10,7 @@ print("Starting the CDK") print("Adjusting the layer directory") -adjust_layer_directory(shared_dir_name="shared", destination="copied_shared") +adjust_layer_directory() print("Finished adjusting the layer directory") diff --git a/iac/local/minio/docker-compose-minio.yml b/iac/local/minio/docker-compose-minio.yml new file mode 100644 index 0000000..4a0b046 --- /dev/null +++ b/iac/local/minio/docker-compose-minio.yml @@ -0,0 +1,41 @@ +version: '3.8' # The top-level 'version' is obsolete but using a more modern one like 3.8 is fine + +services: + minio: + image: minio/minio:latest + container_name: minio-reservation + # ✅ SET credentials for the MinIO server + environment: + MINIO_ROOT_USER: root # If you change any credentials, you have to match it in environments + MINIO_ROOT_PASSWORD: root1234 + ports: + - "9000:9000" + - "9001:9001" + volumes: + - minio-data:/data + command: server /data --console-address :9001 + + create-bucket: + image: minio/mc:latest + depends_on: + - minio + entrypoint: > + /bin/sh -c " + # ✅ Make the script exit on any error + set -e; + + # Wait for MinIO to be ready + # Use the more modern 'mc alias set' and provide the credentials + /usr/bin/mc alias set s3 http://minio:9000 root root1234; + + # Create the bucket + /usr/bin/mc mb s3/bucket-test; + + # Set the policy + /usr/bin/mc policy set public s3/bucket-test; + + echo '✅ Bucket created successfully!'; + " + +volumes: + minio-data: diff --git a/iac/stacks/bucket_stack.py b/iac/stacks/bucket_stack.py index df796f8..2e28d8d 100644 --- a/iac/stacks/bucket_stack.py +++ b/iac/stacks/bucket_stack.py @@ -1,8 +1,9 @@ from aws_cdk import ( aws_s3 as s3, + aws_cloudfront as cloudfront, + aws_cloudfront_origins as origins, aws_iam as iam, RemovalPolicy, - Stack, ) from constructs import Construct import os @@ -24,8 +25,19 @@ def __init__(self, scope: Construct, **kwargs) -> None: stage = 'DEV' self.bucket = s3.Bucket( - self, f"BACK_S3_REPORT_BUCKET_{stage}", - bucket_name=f"{self.stack_name}-report-bucket{stage}".lower(), + self, f"RESERVATION_BACK_S3_BUCKET_{stage}", + # TODO remover isso quando voltar pra conta nova ou tentar deletar o bucket criado la com power user + bucket_name=f"{self.stack_name}-bucket-{stage}".lower(), versioned=True, removal_policy=RemovalPolicy.DESTROY if not (stage == 'PROD') else RemovalPolicy.RETAIN, + block_public_access=s3.BlockPublicAccess.BLOCK_ALL + ) + + self.distribution = cloudfront.Distribution( + self, f"ReservationBucketDistribution{stage}", + default_behavior=cloudfront.BehaviorOptions( + origin=origins.S3Origin(self.bucket), + viewer_protocol_policy=cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + ), + default_root_object=None # não obrigatório, mas evita erro se não tiver index.html ) diff --git a/iac/stacks/iac_stack.py b/iac/stacks/iac_stack.py index 7ef8b21..38c16d3 100644 --- a/iac/stacks/iac_stack.py +++ b/iac/stacks/iac_stack.py @@ -1,5 +1,5 @@ from aws_cdk import ( - Stack, + Stack, aws_iam ) from constructs import Construct from aws_cdk.aws_apigateway import RestApi, Cors @@ -55,7 +55,11 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: "DYNAMO_PARTITION_KEY": "PK", "DYNAMO_SORT_KEY": "SK", "REGION": self.aws_region, - "GRAPH_MICROSOFT_ENDPOINT": os.getenv("GRAPH_MICROSOFT_ENDPOINT") + "USER_API_URL": os.environ.get("USER_API_URL"), + "S3_BUCKET_NAME": self.s3_bucket.bucket.bucket_name, + "FROM_EMAIL": os.environ.get("FROM_EMAIL"), + "HIDDEN_COPY": os.environ.get("HIDDEN_COPY"), + "S3_ASSETS_CDN": os.environ.get("S3_ASSETS_CDN") } @@ -69,4 +73,23 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: for function in self.lambda_stack.functions_that_need_s3_permissions: self.s3_bucket.bucket.grant_read_write(function) + ses_admin_policy = aws_iam.PolicyStatement( + effect=aws_iam.Effect.ALLOW, + actions=[ + "ses:*", + ], + resources=[ + "*" + ] + ) + + functions_that_need_ses_permissions = [ + self.lambda_stack.delete_booking + ] + + for f in functions_that_need_ses_permissions: + f.add_environment("HIDDEN_COPY", os.environ.get("HIDDEN_COPY")) + f.add_environment("FROM_EMAIL", os.environ.get("FROM_EMAIL")) + f.add_environment("REPLY_TO_EMAIL", os.environ.get("REPLY_TO_EMAIL")) + f.add_to_role_policy(ses_admin_policy) diff --git a/iac/stacks/lambda_stack.py b/iac/stacks/lambda_stack.py index dc3ebe4..dbf1138 100644 --- a/iac/stacks/lambda_stack.py +++ b/iac/stacks/lambda_stack.py @@ -3,13 +3,12 @@ from aws_cdk import ( aws_lambda as lambda_, NestedStack, Duration, - aws_apigateway as apigw, + aws_apigateway as apigw ) from constructs import Construct from aws_cdk.aws_apigateway import Resource, LambdaIntegration -from aws_cdk.aws_events import Rule, Schedule -from aws_cdk.aws_events_targets import LambdaFunction - +from aws_cdk.aws_events import Rule, Schedule, EventField, RuleTargetInput +from aws_cdk.aws_events_targets import LambdaFunction class LambdaStack(Construct): functions_that_need_dynamo_permissions = [] @@ -20,7 +19,7 @@ def create_lambda_api_gateway_integration(self, module_name: str, method: str, a self, module_name.title(), code=lambda_.Code.from_asset(f"../src/modules/{module_name}"), handler=f"app.{module_name}_presenter.lambda_handler", - runtime=lambda_.Runtime.PYTHON_3_9, + runtime=lambda_.Runtime("python3.13"), layers=[self.lambda_layer], environment=environment_variables, timeout=Duration.seconds(15) @@ -42,18 +41,23 @@ def create_lambda_event_bridge_integration(self, module_name.title(), code=lambda_.Code.from_asset(f"../src/modules/{module_name}"), handler=f"app.{module_name}_presenter.lambda_handler", - runtime=lambda_.Runtime.PYTHON_3_9, + runtime=lambda_.Runtime("python3.13"), layers=[self.lambda_layer], environment=environment_variables, timeout=Duration.seconds(15) ) rule = Rule( - self, f"{module_name.title()}EventRule", + self, f"{module_name.title()}EventRuleForWeeklyUpload", schedule=cron_schedule ) - rule.add_target(LambdaFunction(function)) + input_transformer = RuleTargetInput.from_object({ + "current_date": EventField.time, + "message": "weekly report trigger!" + }) + + rule.add_target(LambdaFunction(function, event=input_transformer)) return function @@ -71,43 +75,47 @@ def __init__(self, scope: Construct, api_gateway_resource: Resource, environment super().__init__(scope, f"{self.stack_name}_LambdaStack_{stage}") self.lambda_layer = lambda_.LayerVersion(self, f"{self.stack_name}_Lambda_Layer_{stage}", - code=lambda_.Code.from_asset("./copied_shared"), - compatible_runtimes=[lambda_.Runtime.PYTHON_3_9] + code=lambda_.Code.from_asset("./build"), + compatible_runtimes=[lambda_.Runtime("python3.13")] ) - - self.graph_authorizer_lambda = lambda_.Function( - self, "GraphAuthorizerLambdaReservationStacksANDCourts", - code=lambda_.Code.from_asset("../src/functions/graph_authorizer"), - handler="graph_authorizer.lambda_handler", - runtime=lambda_.Runtime.PYTHON_3_9, + + authorizer_lambda = lambda_.Function( + self, "AuthorizerUserMssReservationApiLambda", + code=lambda_.Code.from_asset("../src/shared/authorizer"), + handler="user_mss_authorizer.lambda_handler", + runtime=lambda_.Runtime("python3.13"), layers=[self.lambda_layer], environment=environment_variables, - timeout=Duration.seconds(15), + timeout=Duration.seconds(15) ) - self.token_authorizer_graph = apigw.TokenAuthorizer( - self, "TokenAuthorizerGraphReservationStacksANDCourts", - handler=self.graph_authorizer_lambda, + token_authorizer_lambda = apigw.TokenAuthorizer( + self, "TokenAuthorizerReservationApi", + handler=authorizer_lambda, identity_source=apigw.IdentitySource.header("Authorization"), - authorizer_name="GraphAuthorizerReservationStacksANDCourts", + authorizer_name="AuthorizerUserMssReservationMssAlertLambda", results_cache_ttl=Duration.seconds(0) ) + #ready for auth self.create_booking = self.create_lambda_api_gateway_integration( module_name="create_booking", method="POST", api_resource=api_gateway_resource, environment_variables=environment_variables, - authorizer=self.token_authorizer_graph + authorizer=token_authorizer_lambda ) + #ready for auth self.update_booking = self.create_lambda_api_gateway_integration( module_name="update_booking", method="PUT", api_resource=api_gateway_resource, - environment_variables=environment_variables + environment_variables=environment_variables, + authorizer=token_authorizer_lambda ) + #not ready for auth AND not used? self.get_booking = self.create_lambda_api_gateway_integration( module_name="get_booking", method="GET", @@ -122,14 +130,16 @@ def __init__(self, scope: Construct, api_gateway_resource: Resource, environment environment_variables=environment_variables, ) + #not ready for auth??TODO self.delete_booking = self.create_lambda_api_gateway_integration( module_name="delete_booking", method="DELETE", api_resource=api_gateway_resource, environment_variables=environment_variables, - authorizer=self.token_authorizer_graph + authorizer=token_authorizer_lambda ) + #not auth and unused self.get_all_bookings = self.create_lambda_api_gateway_integration( module_name="get_all_bookings", method="GET", @@ -137,13 +147,16 @@ def __init__(self, scope: Construct, api_gateway_resource: Resource, environment environment_variables=environment_variables ) + #ready for auth self.create_court = self.create_lambda_api_gateway_integration( module_name="create_court", method="POST", api_resource=api_gateway_resource, - environment_variables=environment_variables + environment_variables=environment_variables, + authorizer=token_authorizer_lambda ) + #not ready TODO self.get_court = self.create_lambda_api_gateway_integration( module_name="get_court", method="GET", @@ -151,20 +164,25 @@ def __init__(self, scope: Construct, api_gateway_resource: Resource, environment environment_variables=environment_variables ) + #ready self.update_court = self.create_lambda_api_gateway_integration( module_name="update_court", method="PUT", api_resource=api_gateway_resource, - environment_variables=environment_variables + environment_variables=environment_variables, + authorizer=token_authorizer_lambda ) + #ready self.delete_court = self.create_lambda_api_gateway_integration( module_name="delete_court", method="DELETE", api_resource=api_gateway_resource, - environment_variables=environment_variables + environment_variables=environment_variables, + authorizer=token_authorizer_lambda ) + #not ready? needed? self.get_all_courts = self.create_lambda_api_gateway_integration( module_name="get_all_courts", method="GET", @@ -172,6 +190,7 @@ def __init__(self, scope: Construct, api_gateway_resource: Resource, environment environment_variables=environment_variables ) + #yes self.health_check = self.create_lambda_api_gateway_integration( module_name="health_check", method="GET", @@ -179,9 +198,24 @@ def __init__(self, scope: Construct, api_gateway_resource: Resource, environment environment_variables=environment_variables ) + #does not need auth / not a route self.generate_report = self.create_lambda_event_bridge_integration( module_name="generate_report", - cron_schedule=Schedule.cron(minute="0", hour="18", week_day="FRI"), + cron_schedule=Schedule.cron(minute="30", hour="11", week_day="FRI"), + environment_variables=environment_variables + ) + + self.get_all_admin_bookings = self.create_lambda_api_gateway_integration( + module_name="get_all_admin_bookings", + method="GET", + api_resource=api_gateway_resource, + environment_variables=environment_variables + ) + + self.get_all_bookings_grouped_by_role = self.create_lambda_api_gateway_integration( + module_name="get_all_bookings_grouped_by_role", + method="GET", + api_resource=api_gateway_resource, environment_variables=environment_variables ) @@ -197,7 +231,9 @@ def __init__(self, scope: Construct, api_gateway_resource: Resource, environment self.delete_booking, self.get_all_bookings, self.get_bookings, - self.graph_authorizer_lambda + self.generate_report, + self.get_all_admin_bookings, + self.get_all_bookings_grouped_by_role ] self.functions_that_need_s3_permissions = [ diff --git a/requirements-dev.txt b/requirements-dev.txt index ab21f95..2a9eb03 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,4 +3,8 @@ pytest-cov==4.0.0 boto3==1.24.88 python-dotenv==0.21.0 aws-lambda-powertools==2.9.0 -aws_xray_sdk==2.11.0 \ No newline at end of file +aws_xray_sdk==2.11.0 + +pandas==2.2.3 +XlsxWriter==3.2.2 +requests==2.32.3 \ No newline at end of file diff --git a/requirements-layer.txt b/requirements-layer.txt new file mode 100644 index 0000000..d6f5bfe --- /dev/null +++ b/requirements-layer.txt @@ -0,0 +1,7 @@ +#boto3 compatibility +urllib3<1.27 + +#lambda needs +pandas==2.2.3 +XlsxWriter==3.2.2 +requests==2.32.3 \ No newline at end of file diff --git a/src/modules/create_booking/app/create_booking_controller.py b/src/modules/create_booking/app/create_booking_controller.py index 8e737e9..798ee6b 100644 --- a/src/modules/create_booking/app/create_booking_controller.py +++ b/src/modules/create_booking/app/create_booking_controller.py @@ -1,5 +1,7 @@ import json +from src.shared.domain.enums.type import BOOKING_TYPE + from .create_booking_usecase import CreateBookingUsecase from .create_booking_viewmodel import CreateBookingViewmodel from src.shared.helpers.errors.controller_errors import MissingParameters, WrongTypeParameter, AuthorizerError @@ -29,8 +31,9 @@ def __call__(self, request: IRequest): end_date = request.data.get('end_date', None) court_number = request.data.get('court_number', None) sport = request.data.get('sport', None) - user_id = user_from_authorizer.get('id', None) + user_id = user_from_authorizer.get('user_id', None) materials = request.data.get('materials', None) + booking_type = request.data.get('type', None) try: @@ -75,6 +78,13 @@ def __call__(self, request: IRequest): raise WrongTypeParameter(fieldName='materials', fieldTypeExpected='list', fieldTypeReceived=type(materials).__name__) + + if booking_type is None: + raise MissingParameters('type') + if not isinstance(booking_type, str): + raise WrongTypeParameter(fieldName='type', + fieldTypeExpected='str', + fieldTypeReceived=type(booking_type).__name__) booking = self.create_booking_use_case( start_date=start_date, @@ -82,7 +92,8 @@ def __call__(self, request: IRequest): court_number=court_number, sport=sport, user_id=user_id, - materials=materials + materials=materials, + booking_type=booking_type ) viewmodel = CreateBookingViewmodel(booking=booking) diff --git a/src/modules/create_booking/app/create_booking_presenter.py b/src/modules/create_booking/app/create_booking_presenter.py index c67b811..c0875c4 100644 --- a/src/modules/create_booking/app/create_booking_presenter.py +++ b/src/modules/create_booking/app/create_booking_presenter.py @@ -2,14 +2,23 @@ from .create_booking_usecase import CreateBookingUsecase from src.shared.environments import Environments from src.shared.helpers.external_interfaces.http_lambda_requests import LambdaHttpRequest, LambdaHttpResponse +import json repo = Environments.get_booking_repo()() usecase = CreateBookingUsecase(repo) controller = CreateBookingController(usecase) def lambda_handler(event, context): + httpRequest = LambdaHttpRequest(data=event) - httpRequest.data['user_from_authorizer'] = event.get('requestContext', {}).get('authorizer', {}).get('user', None) + + user_info_string = event.get('requestContext', {}).get('authorizer', {}).get('user') + + if user_info_string: + httpRequest.data['user_from_authorizer'] = json.loads(user_info_string).get('user') + else: + httpRequest.data['user_from_authorizer'] = None + response = controller(request=httpRequest) httpResponse = LambdaHttpResponse(status_code=response.status_code, body=response.body, headers=response.headers) diff --git a/src/modules/create_booking/app/create_booking_usecase.py b/src/modules/create_booking/app/create_booking_usecase.py index aca8453..a795713 100644 --- a/src/modules/create_booking/app/create_booking_usecase.py +++ b/src/modules/create_booking/app/create_booking_usecase.py @@ -3,6 +3,7 @@ from src.shared.domain.entities.booking import Booking from src.shared.domain.enums.sport import SPORT +from src.shared.domain.enums.type import BOOKING_TYPE from src.shared.domain.repositories.booking_repository_interface import IBookingRepository from src.shared.helpers.errors.usecase_errors import DuplicatedItem, InvalidSchedule @@ -15,6 +16,7 @@ class CreateBookingUsecase: user_id: str booking_id: str materials: List[str] + booking_type: BOOKING_TYPE def __init__(self, repo: IBookingRepository): self.repo = repo @@ -25,7 +27,8 @@ def __call__(self, court_number: int, sport: str, user_id: str, - materials: List[str] + materials: List[str], + booking_type: str ) -> Booking: booking_id = str(uuid.uuid4()) @@ -41,12 +44,22 @@ def __call__(self, for material in materials: if not isinstance(material, str): raise ValueError("Invalid material type") + + if booking_type not in [type.value for type in BOOKING_TYPE]: + raise ValueError("Invalid type enum value") + + self.booking_type = BOOKING_TYPE(booking_type) all_bookings = self.repo.get_all_bookings() for booking in all_bookings: - if (booking.start_date < end_date and booking.end_date > start_date - and booking.court_number == court_number): + if ( + ( + booking.start_date < end_date + (15 * 60 * 1000) #15 minutes in mseconds + and booking.end_date > start_date - (15 * 60 * 1000) #15 minutes in mseconds + and booking.court_number == court_number + ) + ): raise InvalidSchedule() resp = self.repo.create_booking(Booking(start_date, @@ -55,7 +68,8 @@ def __call__(self, self.sport, user_id, booking_id, - materials)) + materials, + self.booking_type)) return resp diff --git a/src/modules/create_booking/app/create_booking_viewmodel.py b/src/modules/create_booking/app/create_booking_viewmodel.py index b91e23a..19675b4 100644 --- a/src/modules/create_booking/app/create_booking_viewmodel.py +++ b/src/modules/create_booking/app/create_booking_viewmodel.py @@ -1,6 +1,7 @@ from typing import List from src.shared.domain.enums.sport import SPORT +from src.shared.domain.enums.type import BOOKING_TYPE class CreateBookingViewmodel: @@ -12,6 +13,7 @@ class CreateBookingViewmodel: user_id: str booking_id: str materials: List[str] + booking_type: BOOKING_TYPE def __init__(self, booking): self.start_date = booking.start_date @@ -21,6 +23,7 @@ def __init__(self, booking): self.user_id = booking.user_id self.booking_id = booking.booking_id self.materials = booking.materials + self.booking_type = booking.booking_type def to_dict(self): @@ -32,7 +35,8 @@ def to_dict(self): "sport": self.sport.value, "user_id": self.user_id, "booking_id": self.booking_id, - "materials": self.materials + "materials": self.materials, + "type": self.booking_type.value }, "message": "Booking created successfully" } diff --git a/src/modules/create_court/app/create_court_controller.py b/src/modules/create_court/app/create_court_controller.py index 8ebc8ef..4203880 100644 --- a/src/modules/create_court/app/create_court_controller.py +++ b/src/modules/create_court/app/create_court_controller.py @@ -2,10 +2,11 @@ from .create_court_viewmodel import CreateCourtViewmodel from src.shared.helpers.errors.controller_errors import MissingParameters, WrongTypeParameter from src.shared.helpers.errors.domain_errors import EntityError -from src.shared.helpers.errors.usecase_errors import DuplicatedItem +from src.shared.helpers.errors.usecase_errors import DuplicatedItem, ForbiddenAction from src.shared.helpers.external_interfaces.external_interface import IRequest, IResponse from src.shared.helpers.external_interfaces.http_codes import BadRequest, Created, InternalServerError from src.shared.domain.enums.status_enum import STATUS +import json class CreateCourtController: @@ -15,7 +16,7 @@ def __init__(self, usecase: CreateCourtUsecase): def __call__(self, request: IRequest): - try: + try: if request.data.get('number') is None: raise MissingParameters('number') @@ -39,11 +40,18 @@ def __call__(self, request: IRequest): if request.data.get('photo') is not None and type(request.data.get('photo')) is not str: raise WrongTypeParameter(fieldName= 'photo', fieldTypeExpected= str, fieldTypeReceived= type(request.data.get('photo'))) + user = request.data.get("user_from_authorizer") + + if not isinstance(user, dict): + + user = json.loads(user) + court = self.usecase( number= request.data.get('number'), status= status, is_field= request.data.get('is_field'), - photo= request.data.get('photo') + photo= request.data.get('photo'), + role=user.get("role") ) viewmodel = CreateCourtViewmodel(court= court) @@ -62,6 +70,9 @@ def __call__(self, request: IRequest): except EntityError as err: return BadRequest(body=err.message) + except ForbiddenAction as err: + return BadRequest(body=err.message) + except Exception as err: return InternalServerError(body=err.args[0]) diff --git a/src/modules/create_court/app/create_court_presenter.py b/src/modules/create_court/app/create_court_presenter.py index 88639b0..746f31a 100644 --- a/src/modules/create_court/app/create_court_presenter.py +++ b/src/modules/create_court/app/create_court_presenter.py @@ -2,14 +2,24 @@ from .create_court_usecase import CreateCourtUsecase from src.shared.environments import Environments from src.shared.helpers.external_interfaces.http_lambda_requests import LambdaHttpRequest, LambdaHttpResponse +import json repo = Environments.get_reservation_repo()() usecase = CreateCourtUsecase(repo) controller = CreateCourtController(usecase) def lambda_handler(event, context): + httpRequest = LambdaHttpRequest(data=event) + + user_info_string = event.get('requestContext', {}).get('authorizer', {}).get('user') + + if user_info_string: + httpRequest.data['user_from_authorizer'] = json.loads(user_info_string).get('user') + else: + httpRequest.data['user_from_authorizer'] = None + response = controller(request=httpRequest) httpResponse = LambdaHttpResponse(status_code=response.status_code, body=response.body, headers=response.headers) - + return httpResponse.toDict() \ No newline at end of file diff --git a/src/modules/create_court/app/create_court_usecase.py b/src/modules/create_court/app/create_court_usecase.py index 35bb56b..9a9068c 100644 --- a/src/modules/create_court/app/create_court_usecase.py +++ b/src/modules/create_court/app/create_court_usecase.py @@ -1,17 +1,20 @@ from typing import Optional from src.shared.domain.entities.court import Court from src.shared.domain.enums.status_enum import STATUS -from src.shared.helpers.errors.usecase_errors import DuplicatedItem +from src.shared.helpers.errors.usecase_errors import DuplicatedItem, ForbiddenAction from src.shared.domain.repositories.reservation_repository_interface import IReservationRepository class CreateCourtUsecase: def __init__(self, repo: IReservationRepository): self.repo = repo - def __call__(self, number: int, status: STATUS, is_field: bool, photo: Optional[str] = None): + def __call__(self, number: int, status: STATUS, is_field: bool, role: str, photo: Optional[str] = None): if self.repo.get_court(number) is not None: raise DuplicatedItem('number') + + if role != "ADMIN": + raise ForbiddenAction("user, only admin can create courts") court = Court( number = number, diff --git a/src/modules/delete_booking/app/delete_booking_controller.py b/src/modules/delete_booking/app/delete_booking_controller.py index ed24ede..7315569 100644 --- a/src/modules/delete_booking/app/delete_booking_controller.py +++ b/src/modules/delete_booking/app/delete_booking_controller.py @@ -1,13 +1,13 @@ -import json - +from typing import Any +from src.shared.domain.entities.booking import Booking from .delete_booking_usecase import DeleteBookingUsecase from .delete_booking_viewmodel import DeleteBookingViewModel -from src.shared.helpers.errors.controller_errors import MissingParameters, WrongTypeParameter, AuthorizerError +from src.shared.helpers.errors.controller_errors import MissingParameters, WrongTypeParameter from src.shared.helpers.errors.domain_errors import EntityError from src.shared.helpers.errors.usecase_errors import NoItemsFound, ForbiddenAction from src.shared.helpers.external_interfaces.external_interface import IRequest, IResponse -from src.shared.helpers.external_interfaces.http_codes import OK, BadRequest, InternalServerError, NotFound, Forbidden - +from src.shared.helpers.external_interfaces.http_codes import OK, BadRequest, Created, InternalServerError, NotFound, Forbidden +from src.shared.domain.enums.sport import SPORT class DeleteBookingController: @@ -15,35 +15,23 @@ def __init__(self, usecase: DeleteBookingUsecase): self.usecase = usecase def __call__(self, request: IRequest) -> IResponse: - try: - - user_from_authorizer = request.data.get('user_from_authorizer', None) - if not isinstance(request.data.get('user_from_authorizer'), dict): - - user_from_authorizer = json.loads(request.data.get('user_from_authorizer')) - - if user_from_authorizer is None: - raise AuthorizerError() + try: - user_id = user_from_authorizer.get('id', None) + if request.data.get('user_from_authorizer') is None: + raise MissingParameters('user authorizer') + + user = request.data.get('user_from_authorizer') if request.data.get('booking_id') is None: raise MissingParameters('booking_id') - booking = self.usecase(booking_id=request.data.get('booking_id'), - user_id=user_id) + booking = self.usecase(booking_id=request.data.get('booking_id'), user=user) viewmodel = DeleteBookingViewModel(booking) return OK(viewmodel.to_dict()) except MissingParameters as err: return BadRequest(body=err.message) - - except ForbiddenAction as err: - return Forbidden(body=err.message) - - except AuthorizerError as err: - return InternalServerError(body=err.message) except WrongTypeParameter as err: return BadRequest(body=err.message) @@ -54,5 +42,8 @@ def __call__(self, request: IRequest) -> IResponse: except EntityError as err: return BadRequest(body=err.message) + except ForbiddenAction as err: + return Forbidden(body=err.message) + except Exception as err: return InternalServerError(body=err.args[0]) \ No newline at end of file diff --git a/src/modules/delete_booking/app/delete_booking_presenter.py b/src/modules/delete_booking/app/delete_booking_presenter.py index 5d2836e..72a5648 100644 --- a/src/modules/delete_booking/app/delete_booking_presenter.py +++ b/src/modules/delete_booking/app/delete_booking_presenter.py @@ -2,15 +2,24 @@ from .delete_booking_usecase import DeleteBookingUsecase from src.shared.environments import Environments from src.shared.helpers.external_interfaces.http_lambda_requests import LambdaHttpRequest, LambdaHttpResponse +import json repo = Environments.get_booking_repo()() usecase = DeleteBookingUsecase(repo) controller = DeleteBookingController(usecase) def lambda_handler(event, context): + httpRequest = LambdaHttpRequest(data=event) - httpRequest.data['user_from_authorizer'] = event.get('requestContext', {}).get('authorizer', {}).get('user', {}) + + user_info_string = event.get('requestContext', {}).get('authorizer', {}).get('user') + + if user_info_string: + httpRequest.data['user_from_authorizer'] = json.loads(user_info_string).get('user') + else: + httpRequest.data['user_from_authorizer'] = None + response = controller(request=httpRequest) httpResponse = LambdaHttpResponse(status_code=response.status_code, body=response.body, headers=response.headers) - + return httpResponse.toDict() \ No newline at end of file diff --git a/src/modules/delete_booking/app/delete_booking_usecase.py b/src/modules/delete_booking/app/delete_booking_usecase.py index ceda421..18559e1 100644 --- a/src/modules/delete_booking/app/delete_booking_usecase.py +++ b/src/modules/delete_booking/app/delete_booking_usecase.py @@ -1,30 +1,36 @@ from src.shared.domain.entities.booking import Booking from src.shared.domain.enums.sport import SPORT -from src.shared.domain.repositories.booking_repository_interface import IBookingRepository -from src.shared.helpers.errors.usecase_errors import DuplicatedItem, ForbiddenAction +from src.shared.helpers.errors.usecase_errors import DuplicatedItem from src.shared.helpers.errors.domain_errors import EntityError from src.shared.helpers.errors.usecase_errors import NoItemsFound -from src.shared.domain.repositories.reservation_repository_interface import IReservationRepository +from src.shared.domain.repositories.booking_repository_interface import IBookingRepository class DeleteBookingUsecase: - def __init__(self, repo: IBookingRepository): + def __init__(self, repo:IBookingRepository): self.repo = repo - def __call__(self, - booking_id: str, - user_id: str): + def __call__(self, booking_id: int, user): + if user.get('user_id') is None: + raise EntityError('user id') - if not Booking.validate_booking_id(booking_id): - raise EntityError('booking_id') + if user.get('name') is None: + raise EntityError('user id') - booking = self.repo.get_booking(booking_id=booking_id) + if user.get('email') is None: + raise EntityError('user email') + + if user.get('role') is None: + raise EntityError('user role') + if not Booking.validate_booking_id(booking_id): + raise EntityError('booking_id') + + booking = self.repo.delete_booking(booking_id=booking_id, user=user) + if booking is None: raise NoItemsFound('booking') - - if booking.user_id != user_id: - raise ForbiddenAction('user; trying to delete booking that is not his') - booking = self.repo.delete_booking(booking_id=booking_id) + if booking is None: + raise NoItemsFound('booking') return booking \ No newline at end of file diff --git a/src/modules/delete_booking/app/delete_booking_viewmodel.py b/src/modules/delete_booking/app/delete_booking_viewmodel.py index eddbcd4..309192c 100644 --- a/src/modules/delete_booking/app/delete_booking_viewmodel.py +++ b/src/modules/delete_booking/app/delete_booking_viewmodel.py @@ -20,6 +20,7 @@ def __init__(self, booking: Booking): self.user_id = booking.user_id self.booking_id = booking.booking_id self.materials = booking.materials + self.booking_type = booking.booking_type def to_dict(self): return { @@ -29,7 +30,8 @@ def to_dict(self): 'sport': self.sport.value, 'user_id': self.user_id, 'booking_id': self.booking_id, - 'materials': self.materials + 'materials': self.materials, + 'type': self.booking_type.value } class DeleteBookingViewModel: diff --git a/src/modules/delete_court/app/delete_court_controller.py b/src/modules/delete_court/app/delete_court_controller.py index 7902822..bed8620 100644 --- a/src/modules/delete_court/app/delete_court_controller.py +++ b/src/modules/delete_court/app/delete_court_controller.py @@ -1,13 +1,10 @@ -from typing import Any -from src.shared.domain.entities.court import Court from .delete_court_usecase import DeleteCourtUsecase from .delete_court_viewmodel import DeleteCourtViewModel from src.shared.helpers.errors.controller_errors import MissingParameters, WrongTypeParameter from src.shared.helpers.errors.domain_errors import EntityError -from src.shared.helpers.errors.usecase_errors import NoItemsFound +from src.shared.helpers.errors.usecase_errors import NoItemsFound, ForbiddenAction from src.shared.helpers.external_interfaces.external_interface import IRequest, IResponse -from src.shared.helpers.external_interfaces.http_codes import OK, BadRequest, Created, InternalServerError, NotFound -from src.shared.domain.enums.status_enum import STATUS +from src.shared.helpers.external_interfaces.http_codes import OK, BadRequest, InternalServerError, NotFound class DeleteCourtController: @@ -16,10 +13,24 @@ def __init__(self, usecase: DeleteCourtUsecase): def __call__(self, request: IRequest) -> IResponse: try: - if request.data.get('number') is None: - raise MissingParameters('number') - court = self.usecase(number=request.data.get('number')) + user = request.data.get("user_from_authorizer") + number = request.data.get("number") + + if user is None: + raise MissingParameters('user') + + if number is not None: + try: + number = int(number) + except ValueError: + raise WrongTypeParameter(fieldName="number", + fieldTypeExpected="int", + fieldTypeReceived=type(number).__name__) + else: + raise MissingParameters("number") + + court = self.usecase(number=number, role=user.get("role")) viewmodel = DeleteCourtViewModel(court) return OK(viewmodel.to_dict()) @@ -36,5 +47,8 @@ def __call__(self, request: IRequest) -> IResponse: except EntityError as err: return BadRequest(body=err.message) + except ForbiddenAction as err: + return BadRequest(body=err.message) + except Exception as err: return InternalServerError(body=err.args[0]) \ No newline at end of file diff --git a/src/modules/delete_court/app/delete_court_presenter.py b/src/modules/delete_court/app/delete_court_presenter.py index bd8d419..c0aafa0 100644 --- a/src/modules/delete_court/app/delete_court_presenter.py +++ b/src/modules/delete_court/app/delete_court_presenter.py @@ -1,15 +1,26 @@ from .delete_court_controller import DeleteCourtController from .delete_court_usecase import DeleteCourtUsecase -from src.shared.environments import Environments from src.shared.helpers.external_interfaces.http_lambda_requests import LambdaHttpRequest, LambdaHttpResponse +from src.shared.environments import Environments +import json repo = Environments.get_reservation_repo()() usecase = DeleteCourtUsecase(repo) controller = DeleteCourtController(usecase) + def lambda_handler(event, context): + httpRequest = LambdaHttpRequest(data=event) + + user_info_string = event.get('requestContext', {}).get('authorizer', {}).get('user') + + if user_info_string: + httpRequest.data['user_from_authorizer'] = json.loads(user_info_string).get('user') + else: + httpRequest.data['user_from_authorizer'] = None + response = controller(request=httpRequest) httpResponse = LambdaHttpResponse(status_code=response.status_code, body=response.body, headers=response.headers) - - return httpResponse.toDict() \ No newline at end of file + + return httpResponse.toDict() diff --git a/src/modules/delete_court/app/delete_court_usecase.py b/src/modules/delete_court/app/delete_court_usecase.py index e13d719..2bb34e8 100644 --- a/src/modules/delete_court/app/delete_court_usecase.py +++ b/src/modules/delete_court/app/delete_court_usecase.py @@ -1,19 +1,20 @@ from src.shared.domain.entities.court import Court -from src.shared.domain.enums.status_enum import STATUS -from src.shared.helpers.errors.usecase_errors import DuplicatedItem from src.shared.helpers.errors.domain_errors import EntityError -from src.shared.helpers.errors.usecase_errors import NoItemsFound +from src.shared.helpers.errors.usecase_errors import NoItemsFound, ForbiddenAction from src.shared.domain.repositories.reservation_repository_interface import IReservationRepository class DeleteCourtUsecase: def __init__(self, repo:IReservationRepository): self.repo = repo - def __call__(self, number: int): + def __call__(self, number: int, role: str): if not Court.validate_number(number): raise EntityError('number') + if role != "ADMIN": + raise ForbiddenAction("user, only admin can delete courts") + court = self.repo.delete_court(number=number) if court is None: diff --git a/src/modules/generate_report/app/generate_report_aggregator.py b/src/modules/generate_report/app/generate_report_aggregator.py new file mode 100644 index 0000000..6d1dee8 --- /dev/null +++ b/src/modules/generate_report/app/generate_report_aggregator.py @@ -0,0 +1,129 @@ +from src.shared.clients.user_api_client import UserAPIClient +from src.shared.domain.entities.booking import Booking +from .generate_report_extractor import GenerateReportExtractor +from typing import List + + +class GenerateReportAggregator: + + def __init__(self, extractor: GenerateReportExtractor): + self.extractor = extractor + + def __call__(self, initial_date, final_date): + + bookings = self.extractor(initial_date, final_date) + + users_statistics = {} + + user_api_client = UserAPIClient() + + for booking in bookings: + + user_name = user_api_client.get_user_name(booking.user_id) + key = user_name if user_name is not None else booking.user_id + + if key not in users_statistics: + + users_statistics[key] = { + "reservas_feitas": 0, + "Tennis": 0, + "Football": 0, + "Basketball": 0, + "Volleyball": 0, + "Handball": 0, + "Futsal": 0, + "Rugby": 0, + "Ping Pong": 0, + "Beach Tennis": 0, + "Natação": 0, + "Corrida": 0, + "NA": 0, + 0: 0, + 1: 0, + 2: 0, + 3: 0, + 4: 0, + 5: 0, + 6: 0, + 7: 0, + "tempo_em_quadra": 0 + } + + + users_statistics[key]["reservas_feitas"] += 1 + users_statistics[key][booking.sport.value] += 1 + users_statistics[key][booking.court_number] += 1 + users_statistics[key]["tempo_em_quadra"] += (booking.end_date - booking.start_date) / (1000 * 60 * 60) + + + #court statistics logic + court_statistics = {} + + for booking in bookings: + user_name = user_api_client.get_user_name(booking.user_id) + court_key = booking.court_number + + if court_key not in court_statistics: + court_statistics[court_key] = { + "reservas_feitas": 0, + "tempo_de_uso": 0, + "Tennis": 0, + "Football": 0, + "Basketball": 0, + "Volleyball": 0, + "Handball": 0, + "Futsal": 0, + "Rugby": 0, + "Ping Pong": 0, + "Beach Tennis": 0, + "NA": 0 + } + + sport_column = booking.sport.value + if sport_column not in court_statistics[court_key]: + court_statistics[court_key][sport_column] = 0 + + user_column = user_name + if user_column not in court_statistics[court_key]: + court_statistics[court_key][user_column] = 0 + + court_statistics[court_key]["reservas_feitas"] += 1 + court_statistics[court_key]["tempo_de_uso"] += (booking.end_date - booking.start_date) + court_statistics[court_key][sport_column] += 1 + court_statistics[court_key][user_column] += 1 + + + #sport_statistics logic + sport_statistics = {} + + for booking in bookings: + sport_key = booking.sport.value + user_name = user_api_client.get_user_name(booking.user_id) + # Se ainda não existir, inicializa + if sport_key not in sport_statistics: + sport_statistics[sport_key] = { + "Reservas Feitas": 0, + "quadra 1": 0, + "quadra 2": 0, + "quadra 3": 0, + "quadra 4": 0, + "quadra 5": 0, + "quadra 6": 0, + "quadra 7": 0, + "Tempo Total Praticado": 0, + } + + # Atualiza os valores + sport_statistics[sport_key]["Reservas Feitas"] += 1 + sport_statistics[sport_key][f"quadra {booking.court_number}"] += 1 + sport_statistics[sport_key]["Tempo Total Praticado"] += (booking.end_date - booking.start_date) + + user_column = user_name + if user_column not in sport_statistics[sport_key]: + sport_statistics[sport_key][user_column] = 0 + + sport_statistics[sport_key][user_column] += 1 + + #sport_statistics logic + + return users_statistics, court_statistics, sport_statistics diff --git a/src/modules/generate_report/app/generate_report_extractor.py b/src/modules/generate_report/app/generate_report_extractor.py new file mode 100644 index 0000000..e0e732f --- /dev/null +++ b/src/modules/generate_report/app/generate_report_extractor.py @@ -0,0 +1,20 @@ +from src.shared.domain.repositories.booking_repository_interface import IBookingRepository +from src.shared.helpers.errors.usecase_errors import DynamoDBBaseError + + +class GenerateReportExtractor: + + def __init__(self, booking_repository: IBookingRepository): + self.booking_repository = booking_repository + + def __call__(self, initial_date, final_date): + + try: + + #TODO Add method to interface + bookings = self.booking_repository.get_all_bookings_by_date_range(initial_date, final_date) + + except: + raise DynamoDBBaseError("Error extracting bookings from dynamo") + + return bookings diff --git a/src/modules/generate_report/app/generate_report_presenter.py b/src/modules/generate_report/app/generate_report_presenter.py index e02c936..06f7eed 100644 --- a/src/modules/generate_report/app/generate_report_presenter.py +++ b/src/modules/generate_report/app/generate_report_presenter.py @@ -1,9 +1,58 @@ +import datetime +import time + +from src.shared.environments import Environments +from src.shared.clients.s3_client import s3_client + +from .generate_report_extractor import GenerateReportExtractor +from .generate_report_aggregator import GenerateReportAggregator +from .generate_report_transformer import GenerateReportTransformer + +repo = Environments.get_envs().get_booking_repo()() def lambda_handler(event, context): - """ - Lambda handler function for the generate_report module - """ - print("IM ALIVE!! LAMBDA TRIGGERED BY EVENTBRIDGE") - print('event: ', event) - print('context', context) \ No newline at end of file + #"2025-04-03T15:00:00Z" date format + + current_date = datetime.datetime.now() + + year = current_date.year + month = current_date.month + year_start_date = datetime.datetime(year, 1, 1) + + start_date = int(time.mktime(year_start_date.timetuple())) * 1000 + final_date = int(time.mktime(current_date.timetuple())) * 1000 + + file_name = f"relatorio_gerado_em_{current_date.day}_{month}_{year}.xlsx" + file_path = f"relatorios/" + + extractor = GenerateReportExtractor(booking_repository=repo) + aggregator = GenerateReportAggregator(extractor=extractor) + transformer = GenerateReportTransformer(aggregator=aggregator) + report = transformer(start_date, final_date) + + if report: + + bucket_manager = s3_client() + + try: + + report = report.getvalue() + + response = bucket_manager.upload_file(key=file_path + file_name, + file_type=".xlsx", + decode_string=report, + ) + + print("Report generated and uploaded successfully") + + return 1 + + except: + + raise Exception("Error uploading file to S3") + + + else: + + raise Exception("Error generating report") diff --git a/src/modules/generate_report/app/generate_report_sender.py b/src/modules/generate_report/app/generate_report_sender.py new file mode 100644 index 0000000..0844a2f --- /dev/null +++ b/src/modules/generate_report/app/generate_report_sender.py @@ -0,0 +1,57 @@ +# import datetime +# import time + +# from src.shared.environments import Environments +# from src.shared.infra.repositories.util.S3Manager import S3Manager + +# from .generate_report_extractor import GenerateReportExtractor +# from .generate_report_aggregator import GenerateReportAggregator +# from .generate_report_transformer import GenerateReportTransformer + +# repo = Environments.get_envs().get_booking_repo()() + + +# def lambda_handler(event, context): +# extractor = GenerateReportExtractor(repo) +# #"2025-04-03T15:00:00Z" date format + +# current_date = datetime.datetime.now() + +# year = current_date.year +# month = current_date.month +# year_start_date = datetime.datetime(year, 1, 1) + +# start_date = int(time.mktime(year_start_date.timetuple())) * 1000 +# final_date = int(time.mktime(current_date.timetuple())) * 1000 + +# file_name = f"relatorio_gerado_em_{current_date.day}_{month}_{year}.xlsx" +# file_path = f"relatorios/" + +# extractor = GenerateReportExtractor(booking_repository=repo) +# aggregator = GenerateReportAggregator(extractor=extractor) +# transformer = GenerateReportTransformer(aggregator=aggregator) +# report = transformer(start_date, final_date) + +# if report: + +# bucket_manager = S3Manager() + +# try: + +# response = bucket_manager.upload_file(key=file_path + file_name, +# file_type=".xlsx", +# decode_string=report, +# ) + +# print("Report generated and uploaded successfully") + +# return 1 + +# except: + +# raise Exception("Error uploading file to S3") + + +# else: + +# raise Exception("Error generating report") diff --git a/src/modules/generate_report/app/generate_report_transformer.py b/src/modules/generate_report/app/generate_report_transformer.py new file mode 100644 index 0000000..3a46ef0 --- /dev/null +++ b/src/modules/generate_report/app/generate_report_transformer.py @@ -0,0 +1,33 @@ +from .generate_report_aggregator import GenerateReportAggregator +import pandas as pd +import io + +class GenerateReportTransformer: + + def __init__(self, aggregator: GenerateReportAggregator): + self.aggregator = aggregator + + def __call__(self, initial_date, final_date): + + user_statistics, court_statistics, sports_statistics = self.aggregator(initial_date, final_date) + + df_users = pd.DataFrame.from_dict(user_statistics, orient="index") + df_courts = pd.DataFrame.from_dict(court_statistics, orient="index") + df_sports = pd.DataFrame.from_dict(sports_statistics, orient="index") + + + print(f"DF Users shape: {df_users.shape}") + print(f"DF Courts shape: {df_courts.shape}") + print(f"DF Sports shape: {df_sports.shape}") + + output = io.BytesIO() + + with pd.ExcelWriter(output, engine="xlsxwriter") as writer: + df_users.to_excel(writer, sheet_name="Usuários") + df_courts.to_excel(writer, sheet_name="Quadras") + df_sports.to_excel(writer, sheet_name="Esportes") + writer.close() + output.seek(0) + + return output + diff --git a/src/functions/__init__.py b/src/modules/get_all_admin_bookings/__init__.py similarity index 100% rename from src/functions/__init__.py rename to src/modules/get_all_admin_bookings/__init__.py diff --git a/src/functions/graph_authorizer/__init__.py b/src/modules/get_all_admin_bookings/app/__init__.py similarity index 100% rename from src/functions/graph_authorizer/__init__.py rename to src/modules/get_all_admin_bookings/app/__init__.py diff --git a/src/modules/get_all_admin_bookings/app/get_all_admin_bookings_controller.py b/src/modules/get_all_admin_bookings/app/get_all_admin_bookings_controller.py new file mode 100644 index 0000000..9692885 --- /dev/null +++ b/src/modules/get_all_admin_bookings/app/get_all_admin_bookings_controller.py @@ -0,0 +1,37 @@ +from .get_all_admin_bookings_usecase import GetAllAdminBookingsUsecase +from .get_all_admin_bookings_viewmodel import GetAllAdminBookingsViewmodel +from src.shared.helpers.external_interfaces.external_interface import IRequest, IResponse +from src.shared.helpers.external_interfaces.http_codes import * +from src.shared.helpers.errors.domain_errors import * +from src.shared.helpers.errors.usecase_errors import * + + + +class GetAllAdminBookingsController: + + def __init__(self, usecase: GetAllAdminBookingsUsecase): + + self.usecase = usecase + + def __call__(self, request: IRequest) -> IResponse: + + try: + + response = self.usecase() + + return OK(GetAllAdminBookingsViewmodel(bookings=response).to_dict()) + + except EntityError as err: + return BadRequest(body=err.message) + + except NoItemsFound as err: + return BadRequest(body=err.message) + + except NoAdminFound as err: + return BadRequest(body=err.message) + + except NoAdminBookingsFound as err: + return BadRequest(body=err.message) + + except Exception as err: + return InternalServerError(body=err.args[0]) \ No newline at end of file diff --git a/src/modules/get_all_admin_bookings/app/get_all_admin_bookings_presenter.py b/src/modules/get_all_admin_bookings/app/get_all_admin_bookings_presenter.py new file mode 100644 index 0000000..4adf9c4 --- /dev/null +++ b/src/modules/get_all_admin_bookings/app/get_all_admin_bookings_presenter.py @@ -0,0 +1,25 @@ +import json +from .get_all_admin_bookings_controller import GetAllAdminBookingsController +from .get_all_admin_bookings_usecase import GetAllAdminBookingsUsecase +from src.shared.environments import Environments +from src.shared.helpers.external_interfaces.http_lambda_requests import LambdaHttpRequest, LambdaHttpResponse + + +repo = Environments.get_booking_repo()() +usecase = GetAllAdminBookingsUsecase(repo) +controller = GetAllAdminBookingsController(usecase) + +def lambda_handler(event, context): + httpRequest = LambdaHttpRequest(data=event) + + user_info_string = event.get('requestContext', {}).get('authorizer', {}).get('user') + + if user_info_string: + httpRequest.data['user_from_authorizer'] = json.loads(user_info_string).get('user') + else: + httpRequest.data['user_from_authorizer'] = None + + response = controller(request=httpRequest) + httpResponse = LambdaHttpResponse(status_code=response.status_code, body=response.body, headers=response.headers) + + return httpResponse.toDict() \ No newline at end of file diff --git a/src/modules/get_all_admin_bookings/app/get_all_admin_bookings_usecase.py b/src/modules/get_all_admin_bookings/app/get_all_admin_bookings_usecase.py new file mode 100644 index 0000000..d76f450 --- /dev/null +++ b/src/modules/get_all_admin_bookings/app/get_all_admin_bookings_usecase.py @@ -0,0 +1,40 @@ +from src.shared.domain.repositories.booking_repository_interface import IBookingRepository +from src.shared.clients.user_api_client import UserAPIClient +from src.shared.helpers.errors.usecase_errors import * + +class GetAllAdminBookingsUsecase: + + def __init__(self, repo: IBookingRepository): + + self.repo = repo + self.user_client = UserAPIClient() + + def __call__(self): + + all_users = self.user_client.all_users + admin_user = None + + for item in all_users: + + if item.get("role") == "ADMIN" and item.get("email") == "dev@maua.br": + + admin_user = item + + if admin_user == None: + + raise NoAdminFound() + + all_bookings = self.repo.get_all_bookings() + admin_bookings = [] + + for item in all_bookings: + + if item.user_id == admin_user.get("user_id", None): + + admin_bookings.append(item) + + if not admin_bookings: + + raise NoAdminBookingsFound() + + return admin_bookings \ No newline at end of file diff --git a/src/modules/get_all_admin_bookings/app/get_all_admin_bookings_viewmodel.py b/src/modules/get_all_admin_bookings/app/get_all_admin_bookings_viewmodel.py new file mode 100644 index 0000000..b16af11 --- /dev/null +++ b/src/modules/get_all_admin_bookings/app/get_all_admin_bookings_viewmodel.py @@ -0,0 +1,21 @@ +from typing import List +from src.shared.domain.entities.booking import Booking + + +class GetAllAdminBookingsViewmodel: + + bookings: List[Booking] + + def __init__(self, bookings: List[Booking]): + + self.bookings = bookings + + def to_dict(self): + + return { + "bookings": [ + {k: v for k, v in booking.to_dict().items() if k != 'user_id' or k != 'booking_id'} + for booking in self.bookings + ], + "message": "Admin bookings retreived" + } \ No newline at end of file diff --git a/src/modules/get_all_bookings/app/get_all_bookings_controller.py b/src/modules/get_all_bookings/app/get_all_bookings_controller.py index df4369a..3caa8bc 100644 --- a/src/modules/get_all_bookings/app/get_all_bookings_controller.py +++ b/src/modules/get_all_bookings/app/get_all_bookings_controller.py @@ -1,5 +1,7 @@ +from typing import Any from .get_all_bookings_usecase import GetAllBookingsUsecase -from .get_all_bookings_viewmodel import GetAllBookingViewModel +from .get_all_bookings_viewmodel import GetAllBookingsViewModel +from src.shared.helpers.errors.controller_errors import MissingParameters, WrongTypeParameter from src.shared.helpers.errors.domain_errors import EntityError from src.shared.helpers.external_interfaces.external_interface import IRequest from src.shared.helpers.external_interfaces.http_codes import BadRequest, OK, InternalServerError @@ -13,7 +15,7 @@ def __init__(self, usecase: GetAllBookingsUsecase): def __call__(self, request: IRequest): try: bookings = self.usecase() - viewmodel = GetAllBookingViewModel(bookings).to_dict() + viewmodel = GetAllBookingsViewModel(bookings).to_dict() return OK(viewmodel) except EntityError as err: return BadRequest(body=err.message) diff --git a/src/modules/get_all_bookings/app/get_all_bookings_usecase.py b/src/modules/get_all_bookings/app/get_all_bookings_usecase.py index 70acd06..fe4533e 100644 --- a/src/modules/get_all_bookings/app/get_all_bookings_usecase.py +++ b/src/modules/get_all_bookings/app/get_all_bookings_usecase.py @@ -1,4 +1,6 @@ +from typing import Any from src.shared.domain.repositories.booking_repository_interface import IBookingRepository +from src.shared.domain.entities.booking import Booking class GetAllBookingsUsecase: def __init__(self, repo: IBookingRepository): diff --git a/src/modules/get_all_bookings/app/get_all_bookings_viewmodel.py b/src/modules/get_all_bookings/app/get_all_bookings_viewmodel.py index b693175..ee8d77f 100644 --- a/src/modules/get_all_bookings/app/get_all_bookings_viewmodel.py +++ b/src/modules/get_all_bookings/app/get_all_bookings_viewmodel.py @@ -1,5 +1,6 @@ from src.shared.domain.entities.booking import Booking from src.shared.domain.enums.sport import SPORT +from src.shared.domain.enums.type import BOOKING_TYPE from typing import List class BookingViewModel: @@ -7,18 +8,18 @@ class BookingViewModel: end_date: int court_number: int sport: SPORT - user_id: str booking_id: str materials: List[str] + booking_type: BOOKING_TYPE def __init__(self, booking: Booking): self.start_date = booking.start_date self.end_date = booking.end_date self.court_number = booking.court_number self.sport = booking.sport - self.user_id = booking.user_id self.booking_id = booking.booking_id self.materials = booking.materials + self.booking_type = booking.booking_type def to_dict(self): return { @@ -26,9 +27,9 @@ def to_dict(self): 'end_date': self.end_date, 'court_number': self.court_number, 'sport': self.sport.value, - 'user_id': self.user_id, 'booking_id': self.booking_id, - 'materials': self.materials + 'materials': self.materials, + "type": self.booking_type.value } class GetBookingViewModel: booking: Booking @@ -41,7 +42,7 @@ def to_dict(self): 'booking' : self.booking_viewmodel.to_dict() } -class GetAllBookingViewModel: +class GetAllBookingsViewModel: bookings: List[GetBookingViewModel] def __init__(self, bookings: list): diff --git a/src/modules/get_all_bookings_grouped_by_role/__init__.py b/src/modules/get_all_bookings_grouped_by_role/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/get_all_bookings_grouped_by_role/app/get_all_bookings_grouped_by_role_controller.py b/src/modules/get_all_bookings_grouped_by_role/app/get_all_bookings_grouped_by_role_controller.py new file mode 100644 index 0000000..8d04e22 --- /dev/null +++ b/src/modules/get_all_bookings_grouped_by_role/app/get_all_bookings_grouped_by_role_controller.py @@ -0,0 +1,25 @@ +from .get_all_bookings_grouped_by_role_usecase import GetAllBookingsGroupedByRoleUsecase +from .get_all_bookings_grouped_by_role_viewmodel import GetAllBookingsGroupedByRoleViewmodel +from src.shared.helpers.errors.usecase_errors import NoItemsFound +from src.shared.helpers.external_interfaces.external_interface import IRequest, IResponse +from src.shared.helpers.external_interfaces.http_codes import OK, InternalServerError, NotFound + + +class GetAllBookingsGroupedByRoleController: + + def __init__(self, usecase: GetAllBookingsGroupedByRoleUsecase): + + self.usecase = usecase + + def __call__(self, request: IRequest) -> IResponse: + + try: + + response = self.usecase() + + return OK(GetAllBookingsGroupedByRoleViewmodel(response).to_dict()) + + except NoItemsFound as err: + return NotFound(body=err.message) + except Exception as err: + return InternalServerError(body=err.args[0]) \ No newline at end of file diff --git a/src/modules/get_all_bookings_grouped_by_role/app/get_all_bookings_grouped_by_role_presenter.py b/src/modules/get_all_bookings_grouped_by_role/app/get_all_bookings_grouped_by_role_presenter.py new file mode 100644 index 0000000..7d0fa41 --- /dev/null +++ b/src/modules/get_all_bookings_grouped_by_role/app/get_all_bookings_grouped_by_role_presenter.py @@ -0,0 +1,25 @@ +import json +from .get_all_bookings_grouped_by_role_controller import GetAllBookingsGroupedByRoleController +from .get_all_bookings_grouped_by_role_usecase import GetAllBookingsGroupedByRoleUsecase +from src.shared.environments import Environments +from src.shared.helpers.external_interfaces.http_lambda_requests import LambdaHttpRequest, LambdaHttpResponse + + +repo = Environments.get_booking_repo()() +usecase = GetAllBookingsGroupedByRoleUsecase(repo) +controller = GetAllBookingsGroupedByRoleController(usecase) + +def lambda_handler(event, context): + httpRequest = LambdaHttpRequest(data=event) + + user_info_string = event.get('requestContext', {}).get('authorizer', {}).get('user') + + if user_info_string: + httpRequest.data['user_from_authorizer'] = json.loads(user_info_string).get('user') + else: + httpRequest.data['user_from_authorizer'] = None + + response = controller(request=httpRequest) + httpResponse = LambdaHttpResponse(status_code=response.status_code, body=response.body, headers=response.headers) + + return httpResponse.toDict() \ No newline at end of file diff --git a/src/modules/get_all_bookings_grouped_by_role/app/get_all_bookings_grouped_by_role_usecase.py b/src/modules/get_all_bookings_grouped_by_role/app/get_all_bookings_grouped_by_role_usecase.py new file mode 100644 index 0000000..e9f5eab --- /dev/null +++ b/src/modules/get_all_bookings_grouped_by_role/app/get_all_bookings_grouped_by_role_usecase.py @@ -0,0 +1,45 @@ +from src.shared.clients.user_api_client import UserAPIClient +from src.shared.domain.repositories.booking_repository_interface import IBookingRepository +from src.shared.helpers.errors.usecase_errors import NoItemsFound + + +class GetAllBookingsGroupedByRoleUsecase: + + def __init__(self, repo: IBookingRepository): + self.repo = repo + self.user_client = UserAPIClient() + + def __call__(self): + all_users = self.user_client.all_users + + all_bookings = self.repo.get_all_bookings() + + if all_bookings is None: + raise NoItemsFound('all_bookings') + + all_bookings_by_role = { + 'UNKNOWN': [] + } + + for user in all_users: + + user_role = user.get('role', None) + user_id = user.get('user_id', None) + + if user_role is not None: + + if user_role not in all_bookings_by_role: + all_bookings_by_role[user_role] = [] + + user_bookings = [ + {k: v for k, v in booking.to_dict().items() if k != 'user_id'} + for booking in all_bookings + if booking.user_id == user_id + ] + + all_bookings_by_role[user_role if user_role is not None else 'UNKNOWN'].extend(user_bookings) + + return all_bookings_by_role + + + \ No newline at end of file diff --git a/src/modules/get_all_bookings_grouped_by_role/app/get_all_bookings_grouped_by_role_viewmodel.py b/src/modules/get_all_bookings_grouped_by_role/app/get_all_bookings_grouped_by_role_viewmodel.py new file mode 100644 index 0000000..521480a --- /dev/null +++ b/src/modules/get_all_bookings_grouped_by_role/app/get_all_bookings_grouped_by_role_viewmodel.py @@ -0,0 +1,16 @@ +from typing import Dict, List +from src.shared.domain.entities.booking import Booking + + +class GetAllBookingsGroupedByRoleViewmodel: + + def __init__(self, all_bookings_by_role: Dict[str, List[Booking]]): + + self.all_bookings_by_role = all_bookings_by_role + + def to_dict(self): + + return { + 'bookings': self.all_bookings_by_role, + 'message': "The bookings grouped by roles were retrieved." + } \ No newline at end of file diff --git a/src/modules/get_all_courts/app/get_all_courts_controller.py b/src/modules/get_all_courts/app/get_all_courts_controller.py index ef80113..e7d5ba9 100644 --- a/src/modules/get_all_courts/app/get_all_courts_controller.py +++ b/src/modules/get_all_courts/app/get_all_courts_controller.py @@ -1,7 +1,5 @@ -from typing import Any from .get_all_courts_usecase import GetAllCourtsUsecase from .get_all_courts_viewmodel import GetAllCourtsViewModel -from src.shared.helpers.errors.controller_errors import MissingParameters, WrongTypeParameter from src.shared.helpers.errors.domain_errors import EntityError from src.shared.helpers.external_interfaces.external_interface import IRequest from src.shared.helpers.external_interfaces.http_codes import BadRequest, OK, InternalServerError @@ -17,7 +15,6 @@ def __call__(self, request: IRequest): courts = self.usecase() viewmodel = GetAllCourtsViewModel(courts).to_dict() return OK(viewmodel) - except EntityError as err: return BadRequest(body=err.message) diff --git a/src/modules/get_booking/app/get_booking_controller.py b/src/modules/get_booking/app/get_booking_controller.py index 2939634..3d9d0a2 100644 --- a/src/modules/get_booking/app/get_booking_controller.py +++ b/src/modules/get_booking/app/get_booking_controller.py @@ -1,6 +1,6 @@ from .get_booking_viewmodel import GetBookingViewmodel from .get_booking_usecase import GetBookingUseCase -from src.shared.helpers.errors.controller_errors import MissingParameters, WrongTypeParameter, EmptyQueryParameters +from src.shared.helpers.errors.controller_errors import MissingParameters, WrongTypeParameter from src.shared.helpers.errors.domain_errors import EntityError from src.shared.helpers.errors.usecase_errors import NoItemsFound from src.shared.helpers.external_interfaces.external_interface import IRequest @@ -13,26 +13,25 @@ def __init__(self, usecase: GetBookingUseCase): def __call__(self, request: IRequest): try: - - booking_id = request.data.get('booking_id', None) + if request.data.get('booking_id') is None: + raise MissingParameters('booking_id') + + booking_id = request.data.get('booking_id') if booking_id is not None: - if not isinstance(booking_id, str): - raise WrongTypeParameter('booking_id', - fieldTypeReceived=type(booking_id).__name__, - fieldTypeExpected='str') - else: - raise MissingParameters('booking_id') + if type(booking_id) is not str: + raise WrongTypeParameter( + 'booking_id', + 'str', + (type(booking_id)).__name__ + ) booking = self.usecase( - booking_id=booking_id + booking_id = request.data.get('booking_id') ) booking_viewmodel = GetBookingViewmodel(booking) return OK(booking_viewmodel.to_dict()) - except EmptyQueryParameters as err: - return BadRequest(body=err.message) - except MissingParameters as err: return BadRequest(body=err.message) diff --git a/src/modules/get_booking/app/get_booking_usecase.py b/src/modules/get_booking/app/get_booking_usecase.py index c31b44b..ecc32e8 100644 --- a/src/modules/get_booking/app/get_booking_usecase.py +++ b/src/modules/get_booking/app/get_booking_usecase.py @@ -1,9 +1,7 @@ -from typing import Optional - from src.shared.domain.entities.booking import Booking from src.shared.domain.repositories.booking_repository_interface import IBookingRepository from src.shared.helpers.errors.domain_errors import EntityError -from src.shared.helpers.errors.usecase_errors import NoItemsFound, DependantFilter +from src.shared.helpers.errors.usecase_errors import NoItemsFound class GetBookingUseCase: repo: IBookingRepository @@ -11,15 +9,11 @@ class GetBookingUseCase: def __init__(self, repo: IBookingRepository): self.repo = repo - def __call__(self, - booking_id: str): - - if not Booking.validate_booking_id(booking_id): + def __call__(self, booking_id: str): + if not Booking.validate_booking_id(booking_id=booking_id): raise EntityError('booking_id') - - booking = self.repo.get_booking( - booking_id=booking_id - ) + + booking = self.repo.get_booking(booking_id=booking_id) if booking is None: raise NoItemsFound('booking_id') diff --git a/src/modules/get_booking/app/get_booking_viewmodel.py b/src/modules/get_booking/app/get_booking_viewmodel.py index 484e7d5..a046cab 100644 --- a/src/modules/get_booking/app/get_booking_viewmodel.py +++ b/src/modules/get_booking/app/get_booking_viewmodel.py @@ -6,7 +6,6 @@ class BookingViewmodel: end_date: int court_number: int sport: SPORT - user_id: str booking_id: str materials: list @@ -15,9 +14,9 @@ def __init__(self, booking: Booking): self.end_date = booking.end_date self.court_number = booking.court_number self.sport = booking.sport - self.user_id = booking.user_id self.booking_id = booking.booking_id self.materials = booking.materials + self.booking_type = booking.booking_type def to_dict(self): return { @@ -25,9 +24,9 @@ def to_dict(self): 'end_date': self.end_date, 'court_number': self.court_number, 'sport': self.sport.value, - 'user_id': self.user_id, 'booking_id': self.booking_id, - 'materials': self.materials + 'materials': self.materials, + "type": self.booking_type.value } class GetBookingViewmodel: diff --git a/src/modules/get_bookings/app/get_bookings_controller.py b/src/modules/get_bookings/app/get_bookings_controller.py index 5715e8d..10e7781 100644 --- a/src/modules/get_bookings/app/get_bookings_controller.py +++ b/src/modules/get_bookings/app/get_bookings_controller.py @@ -23,6 +23,7 @@ def __call__(self, request: IRequest): court_number = request.data.get('court_number', None) end_date = request.data.get('end_date', None) start_date = request.data.get('start_date', None) + booking_type = request.data.get('type', None) booking_id = booking_id if booking_id != "" else None user_id = user_id if user_id != "" else None @@ -30,10 +31,11 @@ def __call__(self, request: IRequest): court_number = court_number if court_number != "" else None end_date = end_date if end_date != "" else None start_date = start_date if start_date != "" else None + booking_type = booking_type if booking_type != "" else None - if not booking_id and not user_id and not sport and not court_number and not end_date and not start_date: + if not booking_id and not user_id and not sport and not court_number and not end_date and not start_date and not booking_type: raise EmptyQueryParameters( - 'At least one of the filters must be provided: booking_id, user_id, sport, court_number, end_date, start_date') + 'At least one of the filters must be provided: booking_id, user_id, sport, court_number, end_date, start_date, type') if court_number is not None: try: @@ -65,7 +67,8 @@ def __call__(self, request: IRequest): sport=sport, court_number=court_number, end_date=end_date, - start_date=start_date + start_date=start_date, + booking_type=booking_type ) booking_viewmodel = GetBookingsViewmodel(booking) return OK(booking_viewmodel.to_dict()) diff --git a/src/modules/get_bookings/app/get_bookings_usecase.py b/src/modules/get_bookings/app/get_bookings_usecase.py index 614b675..1290fda 100644 --- a/src/modules/get_bookings/app/get_bookings_usecase.py +++ b/src/modules/get_bookings/app/get_bookings_usecase.py @@ -2,6 +2,7 @@ from src.shared.domain.entities.booking import Booking from src.shared.domain.enums.sport import SPORT +from src.shared.domain.enums.type import BOOKING_TYPE from src.shared.domain.repositories.booking_repository_interface import IBookingRepository from src.shared.helpers.errors.domain_errors import EntityError from src.shared.helpers.errors.usecase_errors import NoItemsFound, DependantFilter @@ -18,7 +19,8 @@ def __call__(self, sport: Optional[str] = None, court_number: Optional[int] = None, end_date: Optional[int] = None, - start_date: Optional[int] = None): + start_date: Optional[int] = None, + booking_type: Optional[str] = None): if booking_id: if not Booking.validate_booking_id(booking_id): @@ -39,13 +41,18 @@ def __call__(self, if (start_date and not end_date) or (end_date and not start_date): raise DependantFilter('start_date and end_date') + if booking_type: + if not Booking.validate_booking_type(BOOKING_TYPE(booking_type)): + raise EntityError('type') + bookings = self.repo.get_bookings( booking_id=booking_id if booking_id else None, user_id=user_id if user_id else None, sport=SPORT(sport) if sport else None, court_number=court_number if court_number else None, end_date=end_date if end_date else None, - start_date=start_date if start_date else None) + start_date=start_date if start_date else None, + booking_type=BOOKING_TYPE(booking_type) if booking_type else None) if bookings is None or bookings == []: raise NoItemsFound('booking filters passed') diff --git a/src/modules/get_bookings/app/get_bookings_viewmodel.py b/src/modules/get_bookings/app/get_bookings_viewmodel.py index 94cdb07..fefc788 100644 --- a/src/modules/get_bookings/app/get_bookings_viewmodel.py +++ b/src/modules/get_bookings/app/get_bookings_viewmodel.py @@ -10,7 +10,9 @@ def __init__(self, bookings: list): def to_dict(self): return { - 'bookings': [booking.to_dict() for booking in self.bookings], + 'bookings': [ + {k: v for k, v in booking.to_dict().items() if k != 'user_id'} + for booking in self.bookings + ], 'message': 'the bookings were retrieved' } - diff --git a/src/modules/get_court/app/get_court_controller.py b/src/modules/get_court/app/get_court_controller.py index 35427bd..ec8d652 100644 --- a/src/modules/get_court/app/get_court_controller.py +++ b/src/modules/get_court/app/get_court_controller.py @@ -15,21 +15,20 @@ def __call__(self, request: IRequest): try: if request.data.get('number') is None: raise MissingParameters('number') - + number = request.data.get('number') if number is not None: - try: number = int(number) - except ValueError: raise WrongTypeParameter('number', 'int', type(number).__name__) - court = self.usecase( + + court = self.usecase( number=number - ) - court_viewmodel = GetCourtViewmodel(court=court) + ) + court_viewmodel = GetCourtViewmodel(court= court) return OK(court_viewmodel.to_dict()) @@ -48,3 +47,9 @@ def __call__(self, request: IRequest): except Exception as err: return InternalServerError(body=err.args[0]) + + + + + + diff --git a/src/modules/update_booking/app/update_booking_controller.py b/src/modules/update_booking/app/update_booking_controller.py index ecdd232..62e07a4 100644 --- a/src/modules/update_booking/app/update_booking_controller.py +++ b/src/modules/update_booking/app/update_booking_controller.py @@ -1,11 +1,12 @@ +from src.shared.domain.enums.type import BOOKING_TYPE from .update_booking_usecase import UpdateBookingUsecase from .update_booking_viewmodel import UpdateBookingViewmodel from src.shared.domain.enums.sport import SPORT from src.shared.helpers.errors.controller_errors import MissingParameters, WrongTypeParameter from src.shared.helpers.errors.domain_errors import EntityError from src.shared.helpers.external_interfaces.external_interface import IRequest, IResponse -from src.shared.helpers.external_interfaces.http_codes import OK, BadRequest, InternalServerError, NotFound -from src.shared.helpers.errors.usecase_errors import NoItemsFound +from src.shared.helpers.external_interfaces.http_codes import OK, BadRequest, Forbidden, InternalServerError, NotFound +from src.shared.helpers.errors.usecase_errors import ForbiddenAction, NoItemsFound class UpdateBookingController: def __init__(self, update_booking_use_case: UpdateBookingUsecase): @@ -16,14 +17,18 @@ def __call__(self, request: IRequest) -> IResponse: if request.data.get('booking_id') is None: raise MissingParameters('booking_id') + if request.data.get('user_from_authorizer') is None: + raise MissingParameters('user authorizer') + booking_id = request.data.get('booking_id') start_date = request.data.get('start_date') end_date = request.data.get('end_date') court_number = request.data.get('court_number') sport_value = request.data.get('sport') materials = request.data.get('materials') - - + booking_type = request.data.get('type') + user = request.data.get('user_from_authorizer') + if not isinstance(booking_id, str): raise WrongTypeParameter('booking_id', 'str', type(booking_id).__name__) @@ -45,18 +50,30 @@ def __call__(self, request: IRequest) -> IResponse: raise EntityError('sport') sport = SPORT(sport_value) + + if booking_type is not None: + if not isinstance(booking_type, str): + raise WrongTypeParameter('type', 'str', type(booking_type).__name__) + if booking_type not in [type.value for type in BOOKING_TYPE]: + raise EntityError('type') + + booking_type = BOOKING_TYPE(booking_type) + if materials is not None and not isinstance(materials, list): raise WrongTypeParameter('materials', 'list', type(materials).__name__) + booking = self.UpdateBookingUsecase( booking_id=booking_id, + user=user, start_date=start_date, end_date=end_date, court_number=court_number, sport=sport, - materials=materials + materials=materials, + booking_type=booking_type ) viewmodel = UpdateBookingViewmodel(booking=booking) @@ -75,5 +92,8 @@ def __call__(self, request: IRequest) -> IResponse: except NoItemsFound as err: return NotFound(body=f"Booking not found: {err.message}") + except ForbiddenAction as err: + return Forbidden(body=err.message) + except Exception as err: return InternalServerError(body=err.args[0]) \ No newline at end of file diff --git a/src/modules/update_booking/app/update_booking_presenter.py b/src/modules/update_booking/app/update_booking_presenter.py index 19b0404..17fd9d3 100644 --- a/src/modules/update_booking/app/update_booking_presenter.py +++ b/src/modules/update_booking/app/update_booking_presenter.py @@ -1,3 +1,4 @@ +import json from .update_booking_controller import UpdateBookingController from .update_booking_usecase import UpdateBookingUsecase from src.shared.environments import Environments @@ -10,6 +11,14 @@ def lambda_handler(event, context): httpRequest = LambdaHttpRequest(data=event) + + user_info_string = event.get('requestContext', {}).get('authorizer', {}).get('user') + + if user_info_string: + httpRequest.data['user_from_authorizer'] = json.loads(user_info_string).get('user') + else: + httpRequest.data['user_from_authorizer'] = None + response = controller(request=httpRequest) httpResponse = LambdaHttpResponse(status_code=response.status_code, body=response.body, headers=response.headers) diff --git a/src/modules/update_booking/app/update_booking_usecase.py b/src/modules/update_booking/app/update_booking_usecase.py index 5c3e7c4..1a5cfda 100644 --- a/src/modules/update_booking/app/update_booking_usecase.py +++ b/src/modules/update_booking/app/update_booking_usecase.py @@ -1,9 +1,10 @@ -from typing import List, Optional +from typing import List from src.shared.domain.entities.booking import Booking from src.shared.domain.enums.sport import SPORT +from src.shared.domain.enums.type import BOOKING_TYPE from src.shared.domain.repositories.booking_repository_interface import IBookingRepository from src.shared.helpers.errors.domain_errors import EntityError, EntityParameterOrderDatesError -from src.shared.helpers.errors.usecase_errors import NoItemsFound +from src.shared.helpers.errors.usecase_errors import ForbiddenAction, NoItemsFound, InvalidSchedule class UpdateBookingUsecase: def __init__(self, booking_repo: IBookingRepository): @@ -11,12 +12,17 @@ def __init__(self, booking_repo: IBookingRepository): def __call__(self, booking_id: str, + user: dict, start_date: int = None, end_date: int = None, court_number: int = None, sport: SPORT = None, materials: List[str] = None, - user_id: str = None): + new_user_id: str = None, + booking_type: BOOKING_TYPE = None): + + user_id = user.get('user_id') + user_role = user.get('role') if Booking.validate_booking_id(booking_id) is False: raise EntityError('booking_id') @@ -25,13 +31,18 @@ def __call__(self, if existing_booking is None: raise NoItemsFound('booking') + if user_role != 'ADMIN': + + booking_user_id = existing_booking.user_id + if booking_user_id != user_id: + raise ForbiddenAction('user id') start_date = start_date if start_date is not None else existing_booking.start_date end_date = end_date if end_date is not None else existing_booking.end_date court_number = court_number if court_number is not None else existing_booking.court_number sport = sport if sport is not None else existing_booking.sport materials = materials if materials is not None else existing_booking.materials - + booking_type = booking_type if booking_type is not None else existing_booking.booking_type if Booking.validate_dates(start_date, end_date) is False: raise EntityError("date") @@ -47,6 +58,24 @@ def __call__(self, if Booking.validate_materials(materials) is False: raise EntityError("materials") + + if Booking.validate_booking_type(booking_type) is False: + raise EntityError("type") + + all_bookings = self.booking_repo.get_all_bookings() + + for booking in all_bookings: + if ( + (booking.booking_id != booking_id) + and ( + ( + booking.start_date < end_date + (15 * 60 * 1000) #15 minutes in mseconds + and booking.end_date > start_date - (15 * 60 * 1000) #15 minutes in mseconds + and booking.court_number == court_number + ) + ) + ): + raise InvalidSchedule() booking = self.booking_repo.update_booking( booking_id=booking_id, @@ -54,7 +83,8 @@ def __call__(self, end_date=end_date, court_number=court_number, sport=sport, - materials=materials + materials=materials, + booking_type=booking_type ) if booking.user_id != existing_booking.user_id: diff --git a/src/modules/update_booking/app/update_booking_viewmodel.py b/src/modules/update_booking/app/update_booking_viewmodel.py index e86874a..137a88c 100644 --- a/src/modules/update_booking/app/update_booking_viewmodel.py +++ b/src/modules/update_booking/app/update_booking_viewmodel.py @@ -4,13 +4,6 @@ class BookingViewmodel: - start_date: int - end_date: int - court_number: int - sport: SPORT - user_id: str - booking_id: str - materials: List[str] def __init__(self, booking: Booking): self.booking = booking @@ -23,7 +16,8 @@ def to_dict(self) -> Booking: 'sport': self.booking.sport.value, 'user_id': self.booking.user_id, 'booking_id': self.booking.booking_id, - 'materials': self.booking.materials + 'materials': self.booking.materials, + 'type': self.booking.booking_type.value } class UpdateBookingViewmodel: diff --git a/src/modules/update_court/app/update_court_controller.py b/src/modules/update_court/app/update_court_controller.py index e6b8fb7..1a50614 100644 --- a/src/modules/update_court/app/update_court_controller.py +++ b/src/modules/update_court/app/update_court_controller.py @@ -2,7 +2,7 @@ from .update_court_usecase import UpdateCourtUsecase from .update_court_viewmodel import UpdateCourtViewmodel from src.shared.helpers.errors.controller_errors import MissingParameters, WrongTypeParameter -from src.shared.helpers.errors.usecase_errors import NoItemsFound +from src.shared.helpers.errors.usecase_errors import NoItemsFound, ForbiddenAction from src.shared.helpers.errors.domain_errors import EntityError from src.shared.helpers.external_interfaces.external_interface import IRequest from src.shared.helpers.external_interfaces.http_codes import BadRequest, OK, InternalServerError, NotFound @@ -35,11 +35,14 @@ def __call__(self, request: IRequest): if type(photo_str) is not str: raise WrongTypeParameter(fieldName='photo', fieldTypeExpected=str, fieldTypeReceived=type(request.data.get('photo'))) + + user = request.data.get("user_from_authorizer") court = self.usecase( number=request.data.get('number'), status=STATUS[status_str] if status_str is not None else None, - photo=photo_str + photo=photo_str, + role=user.get("role") ) viewmodel = UpdateCourtViewmodel(court=court) @@ -57,6 +60,9 @@ def __call__(self, request: IRequest): except EntityError as err: return BadRequest(body=err.message) + + except ForbiddenAction as err: + return BadRequest(body=err.message) except Exception as err: return InternalServerError(body=err.args[0]) \ No newline at end of file diff --git a/src/modules/update_court/app/update_court_presenter.py b/src/modules/update_court/app/update_court_presenter.py index b0dfe9d..38a4fae 100644 --- a/src/modules/update_court/app/update_court_presenter.py +++ b/src/modules/update_court/app/update_court_presenter.py @@ -2,6 +2,7 @@ from .update_court_usecase import UpdateCourtUsecase from src.shared.environments import Environments from src.shared.helpers.external_interfaces.http_lambda_requests import LambdaHttpRequest, LambdaHttpResponse +import json repo = Environments.get_reservation_repo()() usecase = UpdateCourtUsecase(repo) @@ -9,7 +10,16 @@ def lambda_handler(event, context): + httpRequest = LambdaHttpRequest(data=event) + + user_info_string = event.get('requestContext', {}).get('authorizer', {}).get('user') + + if user_info_string: + httpRequest.data['user_from_authorizer'] = json.loads(user_info_string).get('user') + else: + httpRequest.data['user_from_authorizer'] = None + response = controller(request=httpRequest) httpResponse = LambdaHttpResponse(status_code=response.status_code, body=response.body, headers=response.headers) diff --git a/src/modules/update_court/app/update_court_usecase.py b/src/modules/update_court/app/update_court_usecase.py index b26bef4..b2220bc 100644 --- a/src/modules/update_court/app/update_court_usecase.py +++ b/src/modules/update_court/app/update_court_usecase.py @@ -2,7 +2,7 @@ from src.shared.domain.entities.court import Court from src.shared.domain.enums.status_enum import STATUS from src.shared.helpers.errors.domain_errors import EntityError -from src.shared.helpers.errors.usecase_errors import NoItemsFound +from src.shared.helpers.errors.usecase_errors import NoItemsFound, ForbiddenAction from src.shared.domain.repositories.reservation_repository_interface import IReservationRepository @@ -12,6 +12,7 @@ def __init__(self, repo: IReservationRepository): def __call__(self, number: int, + role: str, status: Optional[STATUS] = None, photo: Optional[str] = None): @@ -24,6 +25,9 @@ def __call__(self, if self.repo.get_court(number) is None: raise NoItemsFound(f'number: {number}') + + if role != "ADMIN": + raise ForbiddenAction("user, only admin can update courts") court = self.repo.update_court(number=number, status=status, diff --git a/src/shared/authorizer/__init__.py b/src/shared/authorizer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/functions/graph_authorizer/graph_authorizer.py b/src/shared/authorizer/user_mss_authorizer.py similarity index 73% rename from src/functions/graph_authorizer/graph_authorizer.py rename to src/shared/authorizer/user_mss_authorizer.py index d771966..b250dc1 100644 --- a/src/functions/graph_authorizer/graph_authorizer.py +++ b/src/shared/authorizer/user_mss_authorizer.py @@ -1,8 +1,10 @@ import os -import re import json import urllib3 +from src.shared.environments import Environments + + def lambda_handler(event, context): """ This function is used to authorize the user to access the API Gateway. @@ -17,10 +19,11 @@ def lambda_handler(event, context): """ try: - # Fetching the Microsoft Graph endpoint from the environment variables - GRAPH_MICROSOFT_ENDPOINT = os.environ.get("GRAPH_MICROSOFT_ENDPOINT", None) - if not GRAPH_MICROSOFT_ENDPOINT: - raise Exception("MS_GRAPH_ENDPOINT environment variable not set") + + # Fetch the User Mss enpoint from the environment variables + MSS_USER_API_ENDPOINT = os.environ.get("USER_API_URL") + if not MSS_USER_API_ENDPOINT: + raise Exception("MSS_USER_ENDPOINT environment variable not set") # Creating a HTTP client http = urllib3.PoolManager() @@ -28,14 +31,10 @@ def lambda_handler(event, context): # Extracting the token from the event data token = event["authorizationToken"].replace("Bearer ", "") - print(f"token: {token}") - print(f"graph_endpoint: {GRAPH_MICROSOFT_ENDPOINT}") - - # Fetching the user information from the Microsoft Graph API - graph_endpoint = GRAPH_MICROSOFT_ENDPOINT + # Fetching the user information from the user mss methodArn = event["methodArn"] headers = {"Authorization": f"Bearer {token}"} - response = http.request("GET", graph_endpoint, headers=headers) + response = http.request("GET", MSS_USER_API_ENDPOINT + "/reservation-mss-user/get-user", headers=headers) # Checking if the request was successful if response.status != 200: @@ -46,16 +45,7 @@ def lambda_handler(event, context): print("CHECK BEFORE REGEX") print(user_data) - - # Checking if the user is from Maua - email_regex = r"[\d]{2}\.[\d]{5}-[\d]@maua\.br" # Regex to match the Maua email - if not re.match(email_regex, user_data.get("mail", "")): - return generate_policy("user", "Deny", methodArn) - - print("USER_ID: ", user_data.get("id", "did not find id")) - - print("CHECK PASSED REGEX AND GET USER") - + policy = generate_policy( user_data.get("id", "user"), "Allow", methodArn, {"user": json.dumps(user_data)} ) diff --git a/src/shared/clients/__init__.py b/src/shared/clients/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/shared/clients/s3_client.py b/src/shared/clients/s3_client.py new file mode 100644 index 0000000..0d2dbf9 --- /dev/null +++ b/src/shared/clients/s3_client.py @@ -0,0 +1,68 @@ +import boto3 + +from src.shared.environments import Environments + + +class s3_client: + + def __init__(self): + self.__envs = Environments.get_envs() + stage = self.__envs.stage.value + if stage == "TEST": + self.s3 = boto3.client( + "s3", + aws_access_key_id=self.__envs.client_id, + aws_secret_access_key=self.__envs.client_secret, + endpoint_url=self.__envs.bucket_endpoint_url, + region_name=self.__envs.region, + config=boto3.session.Config(signature_version="s3v4"), + ) + else: + self.s3 = boto3.client("s3") + + def upload_file(self, key, file_type, decode_string): + print(f"DEBUG upload_file - key: {key}") + print(f"DEBUG upload_file - file_type: {file_type}") + print(f"DEBUG upload_file - decode_string type: {type(decode_string)}") + print(f"DEBUG upload_file - decode_string length: {len(decode_string)}") + + # Content type correto para Excel + content_types = { + '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + '.xls': 'application/vnd.ms-excel', + } + + content_type = content_types.get(file_type, 'application/octet-stream') + print(f"DEBUG upload_file - content_type: {content_type}") + + try: + print("DEBUG upload_file - Chamando s3.put_object...") + response = self.s3.put_object( + Bucket=self.__envs.s3_bucket_name, + Key=key, + Body=decode_string, # Este deve ser bytes + ContentType=content_type, + ) + print("DEBUG upload_file - put_object executado com sucesso") + + return { + 's3_response': response, + 'key': key + } + + except Exception as e: + print(f"DEBUG upload_file - ERRO no put_object: {str(e)}") + print(f"DEBUG upload_file - Tipo do erro: {type(e)}") + print(f"DEBUG upload_file - Bucket: {self.__envs.s3_bucket_name}") + raise e + + def delete_file(self): + pass + def get_all_files(self): + pass + def get_file(self): + pass + def generate_presigned_url(self, expiration_time: int, object_name: str, method: str, content_type: str): + pass + + diff --git a/src/shared/clients/user_api_client.py b/src/shared/clients/user_api_client.py new file mode 100644 index 0000000..0ba2d41 --- /dev/null +++ b/src/shared/clients/user_api_client.py @@ -0,0 +1,87 @@ +import json +import os +import requests + +from src.shared.environments import Environments + +class UserAPIClient: + + def __init__(self): + self.all_users = self.retrieve_users() + + def get_user_name(self, user_id): + user = next((user for user in self.all_users if user["user_id"] == user_id), None) + return user["name"] if user is not None else None + + def authenticate_user(self, token): + return self._auth_user(token=token) + + @staticmethod + def retrieve_users(): + + api_url= os.environ.get("USER_API_URL") + try: + response = requests.get(api_url + '/reservation-mss-user/get-all-users') + users = response.json().get("users") + return users + except: + raise Exception('Couldn\'t retrieve users') + + #TODO adciionar os parametros nas request, testar se funciona mesmo + + @staticmethod + def _auth_user(token): + + ''' + UNUSED AND DEPRECATED DO NOT USE + ''' + + #this will only get the user info if the user is already created in db, else it will delete the user shortly after + + #UNUSED DO NOT USE THIS, ITS DEPRECATED + + api_url = os.environ.get("USER_API_URL") + + #untested funtion + def delete_user(token): + + try: + + headers = { + "Authorization": f"Bearer{token}" + } + + response = requests.delete(api_url + 'reservation-mss-user/delete-user', headers=headers) + + return response.json().get("message") + + except: + + raise Exception('Could not delete user') + + try: + + headers = { + "Authorization": f"Bearer {token}" + } + + response = requests.get(api_url + 'reservation-mss-user/auth-user', headers=headers) + + user = response.json().get("user") + + identifier = user.get("message", None) + + #TODO test this part + + if identifier == "the user was created successfully": + + #untested funtion + delete_user(token=token) + + raise Exception('User was not registered') + + return user + + except: + raise Exception('Could not authenticate user') + diff --git a/src/shared/domain/entities/booking.py b/src/shared/domain/entities/booking.py index d9c78f0..d517c67 100644 --- a/src/shared/domain/entities/booking.py +++ b/src/shared/domain/entities/booking.py @@ -3,6 +3,7 @@ from typing import Optional, List from src.shared.domain.entities.court import Court from src.shared.domain.enums.sport import SPORT +from src.shared.domain.enums.type import BOOKING_TYPE from src.shared.helpers.errors.domain_errors import EntityError, EntityParameterOrderDatesError class Booking(abc.ABC): @@ -13,9 +14,10 @@ class Booking(abc.ABC): user_id: str booking_id: str materials: List[str] + booking_type: BOOKING_TYPE - def __init__(self, start_date: int, end_date: int, court_number: int, sport: SPORT, user_id: str, booking_id: str, materials: List[str]): + def __init__(self, start_date: int, end_date: int, court_number: int, sport: SPORT, user_id: str, booking_id: str, materials: List[str], booking_type: BOOKING_TYPE): if not Booking.validate_dates(start_date, end_date): raise EntityError("dates") self.start_date = start_date @@ -44,6 +46,14 @@ def __init__(self, start_date: int, end_date: int, court_number: int, sport: SPO raise EntityError("materials") self.materials = materials + if not Booking.validate_booking_type(booking_type): + raise EntityError("type") + + if booking_type == BOOKING_TYPE.MAINTENCE and self.sport != SPORT.NA: + raise EntityError("sport") + + self.booking_type = booking_type + @staticmethod def validate_dates(start_date: int, end_date: int) -> bool: @@ -95,6 +105,12 @@ def validate_materials(materials: List[str]) -> bool: return False return True + @staticmethod + def validate_booking_type(booking_type: BOOKING_TYPE) -> bool: + if not isinstance(booking_type, BOOKING_TYPE): + return False + return True + def to_dict(self): return { "start_date": self.start_date, @@ -103,7 +119,8 @@ def to_dict(self): "sport": self.sport.value, "user_id": self.user_id, "booking_id": self.booking_id, - "materials": self.materials + "materials": self.materials, + "type": self.booking_type.value } def __eq__(self, other): diff --git a/src/shared/domain/enums/sport.py b/src/shared/domain/enums/sport.py index cabb69e..a0ef3ff 100644 --- a/src/shared/domain/enums/sport.py +++ b/src/shared/domain/enums/sport.py @@ -8,5 +8,8 @@ class SPORT(Enum): HANDBALL = "Handball" FUTSAL = "Futsal" RUGBY = "Rugby" - PING_PONG = "Ping Pong" + PING_PONG= "Ping Pong" BEACH_TENNIS = "Beach Tennis" + NATACAO = "Natacao" + CORRIDA = "Corrida" + NA = "NA" diff --git a/src/shared/domain/enums/type.py b/src/shared/domain/enums/type.py new file mode 100644 index 0000000..d1fc9d1 --- /dev/null +++ b/src/shared/domain/enums/type.py @@ -0,0 +1,7 @@ +from enum import Enum + +class BOOKING_TYPE(Enum): + MAINTENCE = 'Maintence' + TRAINING = 'Training' + COMMON = 'Common' + UNDEFINED = 'Undefined' diff --git a/src/shared/domain/repositories/booking_repository_interface.py b/src/shared/domain/repositories/booking_repository_interface.py index afb26a5..cc1a199 100644 --- a/src/shared/domain/repositories/booking_repository_interface.py +++ b/src/shared/domain/repositories/booking_repository_interface.py @@ -4,6 +4,7 @@ from src.shared.domain.entities.booking import Booking from src.shared.domain.enums.sport import SPORT +from src.shared.domain.enums.type import BOOKING_TYPE class IBookingRepository(ABC): @@ -12,6 +13,9 @@ class IBookingRepository(ABC): def create_booking(self, booking: Booking) -> Optional[Booking]: ''' If booking does not exist, creates it and returns it + + !!!Must do time validations outside this method!!!! + Keep it simples, only appending or putting into repostitory ''' pass @@ -22,15 +26,17 @@ def update_booking(self, end_date: int = None, court_number: int = None, sport: SPORT = None, - materials: List[str] = None) -> Optional[Booking]: + materials: List[str] = None, + booking_type: BOOKING_TYPE = None) -> Optional[Booking]: ''' If booking exists, updates it and returns it + + !!!Must do time validations outside this method!!!! ''' pass @abstractmethod - def get_booking(self, - booking_id: str) -> Optional[Booking]: + def get_booking(self, booking_id: str) -> Optional[Booking]: ''' If the booking exists, returns it, else returns None ''' @@ -40,18 +46,20 @@ def get_booking(self, def get_bookings(self, booking_id: Optional[str] = None, user_id: Optional[str] = None, - sport: Optional[SPORT] = None, + sport: Optional[str] = None, court_number: Optional[int] = None, + booking_type: Optional[str] = None, end_date: Optional[int] = None, - start_date: Optional[int] = None) -> List[Optional[Booking]]: + start_date: Optional[int] = None + ) -> List[Optional[Booking]]: ''' If the booking exists, returns it, else returns None ''' pass + # TODO: arrumar a lógica para receber as infos do usuário enviar e-mail @abstractmethod - def delete_booking(self, - booking_id: str) -> Optional[Booking]: + def delete_booking(self, booking_id: int, user) -> Optional[Booking]: ''' If booking exists, deletes it and returns it else returns None @@ -64,3 +72,26 @@ def get_all_bookings(self): Returns all bookings ''' pass + + @abstractmethod + def get_all_bookings_by_date_range(self, initial_date: int, final_date: int) -> Optional[List[Booking]]: + ''' + Returns all bookings, filtered by date range + ''' + pass + + @abstractmethod + def get_all_users(self) -> List[str]: + ''' + Returns name users by user id + ''' + pass + + # TODO: método para enviar e-mail para o usuário + @abstractmethod + def send_user_email(self, user, deleted_booking: Booking) -> bool: + ''' + Send user an e-mail + ''' + pass + \ No newline at end of file diff --git a/src/shared/domain/repositories/reservation_repository_interface.py b/src/shared/domain/repositories/reservation_repository_interface.py index 584f24f..4d30fe2 100644 --- a/src/shared/domain/repositories/reservation_repository_interface.py +++ b/src/shared/domain/repositories/reservation_repository_interface.py @@ -1,4 +1,6 @@ from abc import ABC, abstractmethod +from typing import Optional +from src.shared.domain.entities.booking import Booking from src.shared.domain.entities.court import Court from src.shared.domain.enums.status_enum import STATUS @@ -42,4 +44,4 @@ def get_all_courts(self): ''' Returns all courts ''' - pass + pass \ No newline at end of file diff --git a/src/shared/environments.py b/src/shared/environments.py index af15aa1..bfe295c 100644 --- a/src/shared/environments.py +++ b/src/shared/environments.py @@ -31,6 +31,9 @@ class Environments: dynamo_sort_key: str cloud_front_distribution_domain: str mss_name: str + from_email: str + hidden_copy: str + s3_assets: str def _configure_local(self): from dotenv import load_dotenv @@ -52,6 +55,9 @@ def load_envs(self): self.dynamo_partition_key = "PK" self.dynamo_sort_key = "SK" self.cloud_front_distribution_domain = "https://d3q9q9q9q9q9q9.cloudfront.net" + self.client_id = "root" #change to what is inside minio compose + self.client_secret = "root1234" #change to what is inside minio compose + self.bucket_endpoint_url = "http://localhost:9000" else: self.s3_bucket_name = os.environ.get("S3_BUCKET_NAME") @@ -61,6 +67,9 @@ def load_envs(self): self.dynamo_partition_key = os.environ.get("DYNAMO_PARTITION_KEY") self.dynamo_sort_key = os.environ.get("DYNAMO_SORT_KEY") self.cloud_front_distribution_domain = os.environ.get("CLOUD_FRONT_DISTRIBUTION_DOMAIN") + self.from_email = os.environ.get("FROM_EMAIL") + self.hidden_copy = os.environ.get("HIDDEN_COPY") + self.s3_assets = os.environ.get("S3_ASSETS_CDN") # @staticmethod # def get_user_repo() -> IUserRepository: diff --git a/src/shared/helpers/errors/usecase_errors.py b/src/shared/helpers/errors/usecase_errors.py index 75d75b1..1c17218 100644 --- a/src/shared/helpers/errors/usecase_errors.py +++ b/src/shared/helpers/errors/usecase_errors.py @@ -16,7 +16,20 @@ class DependantFilter(BaseError): def __init__(self, message: str): super().__init__(f'Filters have to be provided together: {message}') +class DynamoDBBaseError(BaseError): + def __init__(self, message: str): + super().__init__(f'Error extracting bookings from dynamo: {message}') + class InvalidSchedule(BaseError): def __init__(self): - super().__init__('Court is already booked for the selected time slot') + super().__init__('Court is already booked for the selected time slot or has to have 15 min tolerance') +class NoAdminFound(BaseError): + + def __init__(self): + super().__init__("No user with role admin was found in user mss") + +class NoAdminBookingsFound(BaseError): + + def __init__(self): + super().__init__("Admin does not have any bookings") diff --git a/src/shared/helpers/functions/compose_delete_booking_email.py b/src/shared/helpers/functions/compose_delete_booking_email.py new file mode 100644 index 0000000..6054eaf --- /dev/null +++ b/src/shared/helpers/functions/compose_delete_booking_email.py @@ -0,0 +1,267 @@ +from datetime import datetime +from src.shared.domain.entities.booking import Booking +from src.shared.environments import Environments + +def compose_deleted_user_email(user, deleted_booking: Booking): + name = user.get('name') + email = user.get('email') + data_hora_inicio = datetime.fromtimestamp((deleted_booking.start_date - 3 * 60 * 60 * 1000)/1000) + data_hora_fim = datetime.fromtimestamp((deleted_booking.end_date - 3 * 60 * 60 * 1000)/1000) + + s3_assets_endpoint = Environments.get_envs().s3_assets + '/assets' + + message = f""" + + + + + + Reserva Cancelada + + + +
+
+

Reserva Cancelada!

+
+
+ Quadra +
+
+
Olá {name},
+ +
Lamentamos informar que sua reserva:
+ +
+
+ Quadra: + {deleted_booking.court_number} +
+
+ Data: + {data_hora_inicio.strftime("%d/%m/%Y")} +
+
+ Horário: + {data_hora_inicio.strftime("%H:%M")} - {data_hora_fim.strftime("%H:%M")} +
+
+ +
foi CANCELADA
+ +
+
Infelizmente sua reserva foi cancelada, por motivos de manutenção, condições climáticas, ou exceções. Dúvidas? Entre em contato pelo nosso e-mail ceaf@maua.br
+
+ +
+ + + +
+ + + + + + """ + + return message \ No newline at end of file diff --git a/src/shared/infra/dto/booking_dynamo_dto.py b/src/shared/infra/dto/booking_dynamo_dto.py index b8a0bd0..38ad9fb 100644 --- a/src/shared/infra/dto/booking_dynamo_dto.py +++ b/src/shared/infra/dto/booking_dynamo_dto.py @@ -2,6 +2,7 @@ from src.shared.domain.entities.booking import Booking from src.shared.domain.enums.sport import SPORT +from src.shared.domain.enums.type import BOOKING_TYPE class BookingDynamoDTO: @@ -12,8 +13,9 @@ class BookingDynamoDTO: user_id: str booking_id: str materials: List[str] + booking_type: BOOKING_TYPE - def __init__(self, start_date: int, end_date: int, court_number: int, sport: SPORT, user_id: str, booking_id: str, materials: List[str]): + def __init__(self, start_date: int, end_date: int, court_number: int, sport: SPORT, user_id: str, booking_id: str, materials: List[str], booking_type: BOOKING_TYPE): self.start_date = start_date self.end_date = end_date self.court_number = court_number @@ -21,6 +23,7 @@ def __init__(self, start_date: int, end_date: int, court_number: int, sport: SPO self.user_id = user_id self.booking_id = booking_id self.materials = materials + self.booking_type = booking_type @staticmethod def from_entity(booking: Booking) -> 'BookingDynamoDTO': @@ -34,7 +37,8 @@ def from_entity(booking: Booking) -> 'BookingDynamoDTO': sport = booking.sport, user_id = booking.user_id, booking_id = booking.booking_id, - materials = booking.materials + materials = booking.materials, + booking_type= booking.booking_type ) def to_dynamo(self) -> dict: @@ -49,7 +53,8 @@ def to_dynamo(self) -> dict: "sport": self.sport.value, "user_id": self.user_id, "booking_id": self.booking_id, - "materials": self.materials + "materials": self.materials or [], + "booking_type": self.booking_type.value } booking_without_none_values = {k: v for k, v in data.items() if v is not None} @@ -68,7 +73,8 @@ def from_dynamo(booking_data: dict) -> "BookingDynamoDTO": sport = SPORT(booking_data["sport"]), user_id = booking_data["user_id"], booking_id = booking_data["booking_id"], - materials = booking_data["materials"] + materials = booking_data.get("materials") or [], + booking_type= BOOKING_TYPE(booking_data.get("booking_type", None)) if booking_data.get("booking_type", None) is not None else BOOKING_TYPE.UNDEFINED ) def to_entity(self) -> Booking: @@ -82,11 +88,12 @@ def to_entity(self) -> Booking: sport=self.sport, user_id=self.user_id, booking_id=self.booking_id, - materials=self.materials + materials=self.materials, + booking_type=self.booking_type ) def __repr__(self): - return f"BookingDynamoDTO(start_date={self.start_date}, end_date={self.end_date}, court_number={self.court_number}, sport={self.sport}, user_id={self.user_id}, booking_id={self.booking_id}, materials={self.materials})" + return f"BookingDynamoDTO(start_date={self.start_date}, end_date={self.end_date}, court_number={self.court_number}, sport={self.sport.value}, user_id={self.user_id}, booking_id={self.booking_id}, materials={self.materials}, booking_type={self.booking_type.value})" def __eq__(self, other): return self.__dict__ == other.__dict__ diff --git a/src/shared/infra/repositories/booking_repository_dynamo.py b/src/shared/infra/repositories/booking_repository_dynamo.py index b9b6e85..b811e5e 100644 --- a/src/shared/infra/repositories/booking_repository_dynamo.py +++ b/src/shared/infra/repositories/booking_repository_dynamo.py @@ -1,11 +1,16 @@ +import os from typing import Optional, List +import boto3 from boto3.dynamodb.conditions import Key from src.shared.domain.entities.booking import Booking from src.shared.domain.enums.sport import SPORT +from src.shared.domain.enums.type import BOOKING_TYPE from src.shared.domain.repositories.booking_repository_interface import IBookingRepository from src.shared.environments import Environments +from src.shared.helpers.errors.usecase_errors import ForbiddenAction +from src.shared.helpers.functions.compose_delete_booking_email import compose_deleted_user_email from src.shared.infra.dto.booking_dynamo_dto import BookingDynamoDTO from src.shared.infra.external.dynamo.datasources.dynamo_datasource import DynamoDatasource @@ -48,7 +53,8 @@ def update_booking(self, end_date: int = None, court_number: int = None, sport: SPORT = None, - materials: List[str] = None) -> Optional[Booking]: + materials: List[str] = None, + booking_type: BOOKING_TYPE = None) -> Optional[Booking]: booking_to_update = self.get_booking(booking_id) @@ -63,6 +69,7 @@ def update_booking(self, "materials": materials if materials is not None else booking_to_update.materials, "user_id": booking_to_update.user_id, "booking_id": booking_to_update.booking_id, + "booking_type": booking_type.value if booking_type is not None else booking_to_update.booking_type.value } resp = self.dynamo.update_item(update_dict=update_dict, @@ -77,10 +84,12 @@ def update_booking(self, def get_bookings(self, booking_id: Optional[str] = None, user_id: Optional[str] = None, - sport: Optional[SPORT] = None, + sport: Optional[str] = None, court_number: Optional[int] = None, + booking_type: Optional[str] = None, end_date: Optional[int] = None, - start_date: Optional[int] = None) -> List[Optional[Booking]]: + start_date: Optional[int] = None + ) -> List[Optional[Booking]]: filters = locals().copy() filters.pop('self') @@ -118,23 +127,104 @@ def get_booking(self, booking_id: str) -> Optional[Booking]: return BookingDynamoDTO.from_dynamo(dynamo_object['Item']).to_entity() - def delete_booking(self, booking_id: str) -> Optional[Booking]: + def delete_booking(self, booking_id: str, user) -> Optional[Booking]: - delete_booking = self.dynamo.delete_item(partition_key=self.booking_partition_key_format(), - sort_key=self.booking_sort_key_format(booking_id)) - if "Attributes" not in delete_booking: + booking = self.get_booking(booking_id) + user_role = user.get('role') + user_id = user.get('user_id') + + if not booking: return None - return BookingDynamoDTO.from_dynamo(delete_booking['Attributes']).to_entity() + is_admin = user_role == 'ADMIN' + is_owner = user_role == 'STUDENT' and booking.user_id == user_id + + if is_admin or is_owner: + deleted = self.dynamo.delete_item( + partition_key=self.booking_partition_key_format(), + sort_key=self.booking_sort_key_format(booking_id) + ) + + deleted_booking = BookingDynamoDTO.from_dynamo(deleted['Attributes']).to_entity() + + self.send_user_email(user, deleted_booking) + + return deleted_booking + + if user_role == 'STUDENT': + raise ForbiddenAction('user id') + + return None + def get_all_bookings(self) -> Optional[List[Booking]]: all_bookings = [] - all_items = self.dynamo.get_all_items().get('Items') + all_items = self.dynamo.get_all_items().get('Items') or [] for item in all_items: if item.get('entity') == 'booking': all_bookings.append(BookingDynamoDTO.from_dynamo(item).to_entity()) + + return all_bookings + + def get_all_bookings_by_date_range(self, initial_date: int, final_date: int) -> Optional[List[Booking]]: + + all_bookings = [] + all_items = self.dynamo.get_all_items().get('Items') or [] + + for item in all_items: + if item.get('entity') == 'booking': + booking = BookingDynamoDTO.from_dynamo(item).to_entity() + if initial_date <= booking.start_date <= final_date: + all_bookings.append(booking) return all_bookings + + def get_all_users(self): + return super().get_all_users() + + + def send_user_email(self, user, deleted_booking: Booking) -> bool: + try: + + client_ses = boto3.client('ses', region_name=os.environ.get('AWS_REGION')) + + email_to_send = compose_deleted_user_email(user, deleted_booking) + + print('7 mensagem email criada -> ' + email_to_send) + + response = client_ses.send_email( + Destination={ + 'ToAddresses': [ + user.get('email'), + ], + 'BccAddresses': + [ + Environments.get_envs().hidden_copy + ] + }, + Message={ + 'Body': { + 'Html': { + 'Charset': "UTF-8", + 'Data': email_to_send, + }, + }, + 'Subject': { + 'Charset': "UTF-8", + 'Data': 'Mauá Reservation - Reserva Cancelada', + }, + }, + Source = Environments.get_envs().from_email, + ) + + print('EMAIL ENVIADO') + + return True + except Exception as err: + print(err) + return False + + diff --git a/src/shared/infra/repositories/booking_repository_mock.py b/src/shared/infra/repositories/booking_repository_mock.py index 3440029..1f75237 100644 --- a/src/shared/infra/repositories/booking_repository_mock.py +++ b/src/shared/infra/repositories/booking_repository_mock.py @@ -1,7 +1,9 @@ from typing import List, Optional from src.shared.domain.entities.booking import Booking from src.shared.domain.enums.sport import SPORT +from src.shared.domain.enums.type import BOOKING_TYPE from src.shared.domain.repositories.booking_repository_interface import IBookingRepository +from src.shared.helpers.errors.usecase_errors import ForbiddenAction class BookingRepositoryMock(IBookingRepository): @@ -14,9 +16,10 @@ def __init__(self): end_date=1634583365000, court_number=1, sport=SPORT.TENNIS, - user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', + user_id='1f25448b-3429-4c19-8287-d9e64f17bc3a', booking_id='b1d3bebf-dc0d-4fc1-861c-506a40cc2925', - materials=['Raquete', 'Bola', 'Rede', 'Tenis'] + materials=['Raquete', 'Bola', 'Rede', 'Tenis'], + booking_type=BOOKING_TYPE.TRAINING ), Booking( @@ -24,9 +27,10 @@ def __init__(self): end_date=1634567400000, court_number=2, sport=SPORT.FOOTBALL, - user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', + user_id='c07e0862-3c07-4227-ab0f-511a267cb7ff', booking_id='b2d3bebf-dc0d-4fc1-861c-506a40cc2925', - materials=['Bola', 'Chuteira'] + materials=['Bola', 'Chuteira'], + booking_type=BOOKING_TYPE.TRAINING ), Booking( @@ -34,9 +38,10 @@ def __init__(self): end_date=1634571000000, court_number=3, sport=SPORT.BASKETBALL, - user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', + user_id='d351a9b1-937f-423c-a9d1-9929b5795be1', booking_id='b3d3bebf-dc0d-4fc1-861c-506a40cc2925', - materials=['Bola'] + materials=['Bola'], + booking_type=BOOKING_TYPE.COMMON ), Booking( @@ -46,7 +51,8 @@ def __init__(self): sport=SPORT.VOLLEYBALL, user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', booking_id='b4d3bebf-dc0d-4fc1-861c-506a40cc2925', - materials=['Bola', 'Rede'] + materials=['Bola', 'Rede'], + booking_type=BOOKING_TYPE.COMMON ), Booking( @@ -56,7 +62,8 @@ def __init__(self): sport=SPORT.HANDBALL, user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', booking_id='b5d3bebf-dc0d-4fc1-861c-506a40cc2925', - materials=['Bola'] + materials=['Bola'], + booking_type=BOOKING_TYPE.TRAINING ), Booking( @@ -66,7 +73,8 @@ def __init__(self): sport=SPORT.FUTSAL, user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', booking_id='b6d3bebf-dc0d-4fc1-861c-506a40cc2925', - materials=['Bola', 'Chuteira'] + materials=['Bola', 'Chuteira'], + booking_type=BOOKING_TYPE.TRAINING ), Booking( @@ -76,7 +84,8 @@ def __init__(self): sport=SPORT.RUGBY, user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', booking_id='b7d3bebf-dc0d-4fc1-861c-506a40cc2925', - materials=['Bola', 'Tenis', 'Capacete'] + materials=['Bola', 'Tenis', 'Capacete'], + booking_type=BOOKING_TYPE.TRAINING ), Booking( @@ -86,7 +95,8 @@ def __init__(self): sport=SPORT.PING_PONG, user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', booking_id='b8d3bebf-dc0d-4fc1-861c-506a40cc2925', - materials=['Raquete', 'Bola'] + materials=['Raquete', 'Bola'], + booking_type=BOOKING_TYPE.TRAINING ), ] @@ -100,7 +110,8 @@ def update_booking(self, end_date: int = None, court_number: int = None, sport: SPORT = None, - materials: List[str] = None + materials: List[str] = None, + booking_type: BOOKING_TYPE = None ) -> Booking: booking = self.get_booking(booking_id) @@ -118,16 +129,24 @@ def update_booking(self, booking.sport = sport if materials is not None: booking.materials = materials - + if booking_type is not None: + booking.booking_type = booking_type return booking + def get_booking(self, booking_id: str): + for booking in self.bookings: + if booking.booking_id == booking_id: + return booking + return None + def get_bookings(self, booking_id: Optional[str] = None, user_id: Optional[str] = None, sport: Optional[str] = None, court_number: Optional[int] = None, end_date: Optional[int] = None, - start_date: Optional[int] = None) -> List[Optional[Booking]]: + start_date: Optional[int] = None, + booking_type: Optional[str] = None) -> List[Optional[Booking]]: filters = locals().copy() filters.pop('self') @@ -153,19 +172,46 @@ def get_bookings(self, return bookings - def get_booking(self, - booking_id: str) -> Optional[Booking]: + def delete_booking(self, booking_id: str, user): + booking = self.get_booking(booking_id) - for booking in self.bookings: - if booking.booking_id == booking_id: - return booking + user_role = user.get('role') - def delete_booking(self, booking_id: str): - booking = self.get_booking(booking_id) if booking is not None: - self.bookings.remove(booking) - return booking + + if user_role == 'ADMIN': + self.bookings.remove(booking) + return booking + + elif user_role == 'STUDENT': + + if booking.user_id == user.get('user_id'): + + self.bookings.remove(booking) + return booking + + else: + raise ForbiddenAction('user id') + return None def get_all_bookings(self) -> List[Booking]: return self.bookings + + def get_all_bookings_by_date_range(self, initial_date, final_date): + + all_bookings = [] + for booking in self.bookings: + if initial_date <= booking.start_date <= final_date: + all_bookings.append(booking) + + return all_bookings + + def get_all_users(self) -> List[str]: + users_id = list(set([booking.user_id for booking in self.bookings])) + return users_id + + def send_user_email(self, user) -> bool: + print('ENVIAR E-MAIL PARA O USUÁRIO') + + return True diff --git a/src/shared/infra/repositories/reservation_repository_dynamo.py b/src/shared/infra/repositories/reservation_repository_dynamo.py index 5a65f38..6f2422f 100644 --- a/src/shared/infra/repositories/reservation_repository_dynamo.py +++ b/src/shared/infra/repositories/reservation_repository_dynamo.py @@ -5,7 +5,6 @@ from typing import List, Optional from src.shared.infra.external.dynamo.datasources.dynamo_datasource import DynamoDatasource from src.shared.domain.enums.status_enum import STATUS -from boto3.dynamodb.conditions import Key class ReservationRepositoryDynamo(IReservationRepository): diff --git a/src/shared/infra/repositories/util/S3Manager.py b/src/shared/infra/repositories/util/S3Manager.py new file mode 100644 index 0000000..318e02e --- /dev/null +++ b/src/shared/infra/repositories/util/S3Manager.py @@ -0,0 +1,46 @@ +import boto3 + +from src.shared.environments import Environments + + +class S3Manager: + + def __init__(self): + self.__envs = Environments.get_envs() + stage = self.__envs.stage.value + if stage == "TEST": + self.s3 = boto3.client( + "s3", + aws_access_key_id=self.__envs.client_id, + aws_secret_access_key=self.__envs.client_secret, + endpoint_url=self.__envs.bucket_endpoint_url, + region_name=self.__envs.region, + config=boto3.session.Config(signature_version="s3v4"), + ) + else: + self.s3 = boto3.client("s3") + + def upload_file(self, key, file_type, decode_string): + + response = self.s3.put_object( + Bucket=self.__envs.s3_bucket_name, + Key=key, + Body=decode_string, + ContentType=file_type.replace(".", ""), + ) + + return { + 's3_response': response, + 'key': key + } + + def delete_file(self): + pass + def get_all_files(self): + pass + def get_file(self): + pass + def generate_presigned_url(self, expiration_time: int, object_name: str, method: str, content_type: str): + pass + + diff --git a/tests/clients/__init__.py b/tests/clients/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/clients/test_user_api_client.py b/tests/clients/test_user_api_client.py new file mode 100644 index 0000000..1d415e0 --- /dev/null +++ b/tests/clients/test_user_api_client.py @@ -0,0 +1,37 @@ +import pytest +from src.shared.clients.user_api_client import UserAPIClient + + +class TestUserAPIClient: + + # to test this, you must declare in your .env the user mss endpoint as USER_API_URL without + # any route at the end (the user api client already adds the routing) + # ex: https://api-url/reservation-mss-user + + @pytest.mark.skip("Can't run test in github actions") + def test_get_user(self): + + user_client = UserAPIClient() + user_name = user_client.get_user_name('1f25448b-3429-4c19-8287-d9e64f17bc3a') + + assert user_name == 'GUSTAVO ALVES GOMES' + + @pytest.mark.skip("Can't run test in github actions") + def test_auth_user(self): + + #this test will only pass if user is already created in db, as method checks for this + + user_client = UserAPIClient() + # the token in the next line must be updated for testing, it is not last longing + # it must be a valid microsoft graph authentication token (jwt format) + + # you can get it via azure cli via the following prompts + # az login + # az account get-access-token --scope https://graph.microsoft.com/.default + user_token = "eyJ0eXAiOiJKV1QiLCJub25jZSI6Ii1abjloOFZOOEZuNVMxS0gyUnBmTFZJeExIbWVmUVpoaVRGd0ZwRXJ3MVEiLCJhbGciOiJSUzI1NiIsIng1dCI6Il9qTndqZVNudlRUSzhYRWRyNVFVUGtCUkxMbyIsImtpZCI6Il9qTndqZVNudlRUSzhYRWRyNVFVUGtCUkxMbyJ9.eyJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jNDllMTkzOS00YjUzLTQ3MzgtYmI2NC00MWZiMjk5MGU0MWMvIiwiaWF0IjoxNzUyMzQ4MjIxLCJuYmYiOjE3NTIzNDgyMjEsImV4cCI6MTc1MjQzNDkyMSwiYWNjdCI6MCwiYWNyIjoiMSIsImFpbyI6IkFVUUF1LzhaQUFBQXh4MnlPNFgySmp2S0VYMFpsbmNXS292RnI1WGxhRnlqUndCWU4wUkkzcEFLd3ZpSmdnK1ZpM09NSXQyTklseVNRVjZXQXQ0cHZWSUZzNWYzZWhDU1R3PT0iLCJhbXIiOlsicHdkIl0sImFwcF9kaXNwbGF5bmFtZSI6Ik1pY3Jvc29mdCBBenVyZSBDTEkiLCJhcHBpZCI6IjA0YjA3Nzk1LThkZGItNDYxYS1iYmVlLTAyZjllMWJmN2I0NiIsImFwcGlkYWNyIjoiMCIsImZhbWlseV9uYW1lIjoiTFVJWiBTRUlYQVMgSU9SSU8iLCJnaXZlbl9uYW1lIjoiTEVPTkFSRE8iLCJpZHR5cCI6InVzZXIiLCJpcGFkZHIiOiIxODkuMTIwLjcyLjU4IiwibmFtZSI6IkxFT05BUkRPIExVSVogU0VJWEFTIElPUklPIiwib2lkIjoiZDM1MWE5YjEtOTM3Zi00MjNjLWE5ZDEtOTkyOWI1Nzk1YmUxIiwib25wcmVtX3NpZCI6IlMtMS01LTIxLTEyNTEwOTIzMjUtMTAwNzM4MzU3OC05Mjk3MDEwMDAtNjQ2NTYiLCJwbGF0ZiI6IjUiLCJwdWlkIjoiMTAwMzIwMDI1NzkwRTY2MyIsInJoIjoiMS5BUThBT1JtZXhGTkxPRWU3WkVIN0taRGtIQU1BQUFBQUFBQUF3QUFBQUFBQUFBQVBBR3NQQUEuIiwic2NwIjoiQXBwbGljYXRpb24uUmVhZFdyaXRlLkFsbCBBcHBSb2xlQXNzaWdubWVudC5SZWFkV3JpdGUuQWxsIEF1ZGl0TG9nLlJlYWQuQWxsIERlbGVnYXRlZFBlcm1pc3Npb25HcmFudC5SZWFkV3JpdGUuQWxsIERpcmVjdG9yeS5BY2Nlc3NBc1VzZXIuQWxsIGVtYWlsIEdyb3VwLlJlYWRXcml0ZS5BbGwgb3BlbmlkIHByb2ZpbGUgVXNlci5SZWFkLkFsbCBVc2VyLlJlYWRXcml0ZS5BbGwiLCJzaWQiOiIwMDZjYWM2OS0wMzFjLWY5ZTktZTMyOC05MjlhMTJhN2E5Y2EiLCJzdWIiOiJmeG9TLVRTaGpjRHlDQmVId1N0enozQ0RmTTNJdXV3aTJYemYta1JncTFFIiwidGVuYW50X3JlZ2lvbl9zY29wZSI6IlNBIiwidGlkIjoiYzQ5ZTE5MzktNGI1My00NzM4LWJiNjQtNDFmYjI5OTBlNDFjIiwidW5pcXVlX25hbWUiOiIyMy4wMDg0Ny00QG1hdWEuYnIiLCJ1cG4iOiIyMy4wMDg0Ny00QG1hdWEuYnIiLCJ1dGkiOiJVNG5oQ0VSdGdVZU12RUlKUXRJaUFBIiwidmVyIjoiMS4wIiwid2lkcyI6WyJiNzlmYmY0ZC0zZWY5LTQ2ODktODE0My03NmIxOTRlODU1MDkiXSwieG1zX2NjIjpbIkNQMSJdLCJ4bXNfZnRkIjoiSnpLckRZeVpydU1FT3k0UHJRWW1MNUJ2WVNvTmp5bG5kRTFmVmZkSkJPa0JkWE56YjNWMGFDMWtjMjF6IiwieG1zX2lkcmVsIjoiMjAgMSIsInhtc19zc20iOiIxIiwieG1zX3N0Ijp7InN1YiI6ImoyeS05b0NleWdEeTJqVUI2QUZSTjluVDk0RVAxNWgtbDc1WFBaTHpjZnMifSwieG1zX3RjZHQiOjE0NDM0ODcxNTV9.ZJXmjXXXFX0PMiGexOEoJ-BiatTRaCeL0FBeoFbvUCaVDtCv2cRUE1yxO_82Rdhn3JTcjIb7uQ5GokkA19gM65D_aOKdelxJUP7ADlW-h5IInoPX_fISnZzpTF_gYWF0e4toGQP0RHCPuXOwrvPVJnMtRJvMf0rSuBgpvL2DQnLvFC5QMzlHRLkJamcCx3CzQ4hQ4SiRZU5BB1o8-TTFUK29hcLhPv-V0FsDPA2hcD8flTP70Ivvp1Ir17Q3U9Y-FS_MXmieYs21J6Sz77rNjwQJ2kVZ0XM70aMolR2g9tZ3In8ZQueLkzTTddBjPxaUda8He_cyftaoi6lK6rvpUA" + + user_client = UserAPIClient() + + user_info = user_client.authenticate_user(token=user_token) + + print(user_info) \ No newline at end of file diff --git a/tests/modules/create_booking/app/test_create_booking_controller.py b/tests/modules/create_booking/app/test_create_booking_controller.py index c349251..610992c 100644 --- a/tests/modules/create_booking/app/test_create_booking_controller.py +++ b/tests/modules/create_booking/app/test_create_booking_controller.py @@ -17,13 +17,14 @@ def test_create_booking_controller(self): "end_date": 1630003600, "court_number": 1, "sport": "Tennis", - "materials": ["racket", "balls"] + "materials": ["racket", "balls"], + "type": "Training" }, headers={ "user_from_authorizer": { "displayName": 'Lebron James', "mail": 'lbj@maua.br', - "id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' + "user_id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' } } ) @@ -48,7 +49,7 @@ def test_create_booking_controller_missing_start_date(self): "user_from_authorizer": { "displayName": 'Lebron James', "mail": 'lbj@maua.br', - "id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' + "user_id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' } }) @@ -72,7 +73,7 @@ def test_create_booking_controller_wrong_type_start_date(self): "user_from_authorizer": { "displayName": 'Lebron James', "mail": 'lbj@maua.br', - "id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' + "user_id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' } }) @@ -95,7 +96,7 @@ def test_create_booking_controller_missing_end_date(self): "user_from_authorizer": { "displayName": 'Lebron James', "mail": 'lbj@maua.br', - "id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' + "user_id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' } }) @@ -119,7 +120,7 @@ def test_create_booking_controller_wrong_type_end_date(self): "user_from_authorizer": { "displayName": 'Lebron James', "mail": 'lbj@maua.br', - "id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' + "user_id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' } }) @@ -143,7 +144,7 @@ def test_create_booking_wrong_type_court_number(self): "user_from_authorizer": { "displayName": 'Lebron James', "mail": 'lbj@maua.br', - "id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' + "user_id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' } }) @@ -166,7 +167,7 @@ def test_create_booking_controller_missing_court_number(self): "user_from_authorizer": { "displayName": 'Lebron James', "mail": 'lbj@maua.br', - "id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' + "user_id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' } }) @@ -189,7 +190,7 @@ def test_create_booking_controller_missing_sport(self): "user_from_authorizer": { "displayName": 'Lebron James', "mail": 'lbj@maua.br', - "id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' + "user_id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' } }) @@ -213,7 +214,7 @@ def test_create_booking_controller_wrong_type_sport(self): "user_from_authorizer": { "displayName": 'Lebron James', "mail": 'lbj@maua.br', - "id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' + "user_id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' } }) @@ -236,7 +237,7 @@ def test_create_booking_controller_missing_materials(self): "user_from_authorizer": { "displayName": 'Lebron James', "mail": 'lbj@maua.br', - "id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' + "user_id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' } }) @@ -260,7 +261,7 @@ def test_create_booking_controller_wrong_type_materials(self): "user_from_authorizer": { "displayName": 'Lebron James', "mail": 'lbj@maua.br', - "id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' + "user_id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' } }) @@ -279,12 +280,13 @@ def test_create_booking_controller_wrong_type_inside_list(self): "end_date": 1630003600, "court_number": 1, "sport": "Tennis", - "materials": ["racket", 1] + "materials": ["racket", 1], + "type": 'Common' }, headers={ "user_from_authorizer": { "displayName": 'Lebron James', "mail": 'lbj@maua.br', - "id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' + "user_id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' } }) diff --git a/tests/modules/create_booking/app/test_create_booking_presenter.py b/tests/modules/create_booking/app/test_create_booking_presenter.py index 0a6ea41..b236058 100644 --- a/tests/modules/create_booking/app/test_create_booking_presenter.py +++ b/tests/modules/create_booking/app/test_create_booking_presenter.py @@ -28,11 +28,12 @@ def test_create_booking_presenter(self): "apiId": "", "authentication": None, "authorizer": { - "user": { - "displayName": 'Lebron James', - "mail": 'lbj@maua.br', - "id": 'c8435c66-13a4-4641-9d54-773b4b8ccc98' - } + "user": json.dumps({ + "user": { + "user_id": "c8435c66-13a4-4641-9d54-773b4b8ccc98", + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", @@ -49,7 +50,7 @@ def test_create_booking_presenter(self): "time": "12/Mar/2020:19:03:58 +0000", "timeEpoch": 1583348638390 }, - "body": '{"start_date": 1610617200, "end_date": 1610620800, "court_number": 1, "sport": "Tennis", "materials": ["ball", "racket"]}', + "body": {'start_date': 1610617200, 'end_date': 1610620800, 'court_number': 1, 'sport': 'Tennis', 'materials': ['ball', 'racket'], 'type': 'Training'}, "pathParameters": None, "isBase64Encoded": None, "stageVariables": None @@ -58,5 +59,5 @@ def test_create_booking_presenter(self): response = lambda_handler(event, None) print(response) - assert response['statusCode'] == 201 - assert json.loads(response['body'])['message'] == 'Booking created successfully' \ No newline at end of file + assert json.loads(response['body'])['message'] == 'Booking created successfully' + assert response['statusCode'] == 201 \ No newline at end of file diff --git a/tests/modules/create_booking/app/test_create_booking_usecase.py b/tests/modules/create_booking/app/test_create_booking_usecase.py index 2781dee..84caa90 100644 --- a/tests/modules/create_booking/app/test_create_booking_usecase.py +++ b/tests/modules/create_booking/app/test_create_booking_usecase.py @@ -3,6 +3,7 @@ from src.modules.create_booking.app.create_booking_usecase import CreateBookingUsecase from src.shared.domain.entities.booking import Booking from src.shared.domain.enums.sport import SPORT +from src.shared.domain.enums.type import BOOKING_TYPE from src.shared.helpers.errors.usecase_errors import InvalidSchedule from src.shared.infra.repositories.booking_repository_mock import BookingRepositoryMock @@ -18,7 +19,8 @@ def test_create_booking_usecase(self): sport=SPORT.TENNIS, user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', materials=['Raquete', 'Bola', 'Rede', 'Tenis'], - booking_id='c8435c66-13a4-4641-9d54-773b4b8ccc98' + booking_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', + booking_type=BOOKING_TYPE.TRAINING ) booking_repository = BookingRepositoryMock() @@ -31,7 +33,8 @@ def test_create_booking_usecase(self): court_number=1, sport="Tennis", user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', - materials=['Raquete', 'Bola', 'Rede', 'Tenis'] + materials=['Raquete', 'Bola', 'Rede', 'Tenis'], + booking_type='Training' ) assert response is not None @@ -41,6 +44,7 @@ def test_create_booking_usecase(self): assert response.sport == booking.sport assert response.user_id == booking.user_id assert response.booking_id != booking.booking_id + assert response.booking_type == booking.booking_type def test_create_booking_usecase_invalid_sport(self): @@ -56,7 +60,8 @@ def test_create_booking_usecase_invalid_sport(self): court_number=1, sport="Invalid sport but string", user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', - materials=['Raquete', 'Bola', 'Rede', 'Tenis'] + materials=['Raquete', 'Bola', 'Rede', 'Tenis'], + booking_type='Training' ) def test_create_booking_usecase_invalid_materials(self): @@ -73,10 +78,11 @@ def test_create_booking_usecase_invalid_materials(self): court_number=1, sport="Tennis", user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', - materials=[1, 2, 3] + materials=[1, 2, 3], + booking_type='Training' ) - def test_create_booking_usecase_invalid_schedule(self): + def test_create_booking_usecase_invalid_schedule_overlap(self): with pytest.raises(InvalidSchedule) as e: @@ -90,5 +96,57 @@ def test_create_booking_usecase_invalid_schedule(self): court_number=1, sport="Tennis", user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', - materials=["colete"] + materials=["colete"], + booking_type='Training' + ) + + assert e.value == "Court is already booked for the selected time slot or has to have 15 min tolerance" + + + def test_create_booking_usecase_invalid_schedule_15min(self): + + with pytest.raises(InvalidSchedule) as e: + + booking_repository = BookingRepositoryMock() + + usecase = CreateBookingUsecase(repo=booking_repository) + + booking_repository.create_booking( + booking=Booking( + start_date=20000000, + end_date=30000000, + court_number=5, + sport=SPORT.FUTSAL, + user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', + materials=['Bola', 'Chuteira'], + booking_id='ddd35c66-13a4-4641-9d54-773b4b8ccc98', + booking_type=BOOKING_TYPE.TRAINING + ) + ) + + response = usecase( + start_date=10000000, + end_date=19100001, #15 minutos de intolerância na criação + court_number=5, + sport=SPORT.FUTSAL, + user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', + materials=['Bola', 'Chuteira'], + booking_type='Training' ) + + assert e.value == "Court is already booked for the selected time slot or has to have 15 min tolerance" + + with pytest.raises(InvalidSchedule) as e2: + + + response2 = usecase( + start_date=30899999, # 15 minutos de intolerância na criação + end_date=40000000, + court_number=5, + sport=SPORT.FUTSAL, + user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', + materials=['Bola', 'Chuteira'], + booking_type='Training' + ) + + assert e2.value == "Court is already booked for the selected time slot or has to have 15 min tolerance" diff --git a/tests/modules/create_booking/app/test_create_booking_viewmodel.py b/tests/modules/create_booking/app/test_create_booking_viewmodel.py index 9783677..ceb50b0 100644 --- a/tests/modules/create_booking/app/test_create_booking_viewmodel.py +++ b/tests/modules/create_booking/app/test_create_booking_viewmodel.py @@ -1,6 +1,7 @@ from src.modules.create_booking.app.create_booking_viewmodel import CreateBookingViewmodel from src.shared.domain.entities.booking import Booking from src.shared.domain.enums.sport import SPORT +from src.shared.domain.enums.type import BOOKING_TYPE class TestCreateBookingViewmodel: @@ -14,7 +15,8 @@ def test_create_booking_viewmodel(self): sport=SPORT.TENNIS, user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', booking_id='b1d3bebf-dc0d-4fc1-861c-506a40cc2925', - materials=['Raquete', 'Bola', 'Rede', 'Tenis'] + materials=['Raquete', 'Bola', 'Rede', 'Tenis'], + booking_type=BOOKING_TYPE.TRAINING ) expected = { @@ -25,7 +27,8 @@ def test_create_booking_viewmodel(self): "sport": "Tennis", "user_id": "c8435c66-13a4-4641-9d54-773b4b8ccc98", "booking_id": "b1d3bebf-dc0d-4fc1-861c-506a40cc2925", - "materials": ["Raquete", "Bola", "Rede", "Tenis"] + "materials": ["Raquete", "Bola", "Rede", "Tenis"], + "type": 'Training' }, "message": "Booking created successfully" } diff --git a/tests/modules/create_court/app/test_create_court_controller.py b/tests/modules/create_court/app/test_create_court_controller.py index 5932464..df2de6a 100644 --- a/tests/modules/create_court/app/test_create_court_controller.py +++ b/tests/modules/create_court/app/test_create_court_controller.py @@ -12,7 +12,10 @@ def test_create_court_controller(self): "number": 7, "status": "AVAILABLE", "is_field": False, - "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/" + "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/", + "user_from_authorizer": { + "role": "ADMIN" + } }) response = controller(request) @@ -30,7 +33,10 @@ def test_create_court_controller_missing_number(self): request = HttpRequest(body={ "status": "AVAILABLE", "is_field": False, - "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/" + "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/", + "user_from_authorizer": { + "role": "ADMIN" + } }) response = controller(request) @@ -45,7 +51,10 @@ def test_create_court_controller_with_missing_status(self): request = HttpRequest(body={ "number": 7, "is_field": False, - "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/" + "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/", + "user_from_authorizer": { + "role": "ADMIN" + } }) response = controller(request) @@ -60,7 +69,10 @@ def test_create_court_controller_wrong_type_number(self): "number": "cavalo", "status": "AVAILABLE", "is_field": False, - "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/" + "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/", + "user_from_authorizer": { + "role": "ADMIN" + } }) reponse = controller(request) @@ -76,7 +88,10 @@ def test_create_court_controller_with_wrong_type_status(self): "number": 7, "status": 123, "is_field": False, - "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/" + "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/", + "user_from_authorizer": { + "role": "ADMIN" + } }) response = controller(request) @@ -91,7 +106,10 @@ def test_create_court_controllerwith_duplicated_item(self): "number": 1, "status": "AVAILABLE", "is_field": False, - "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/" + "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/", + "user_from_authorizer": { + "role": "ADMIN" + } }) response = controller(request) @@ -107,7 +125,10 @@ def test_create_court_controller_number_entity_error(self): "number": 0, "status": "AVAILABLE", "is_field": False, - "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/" + "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/", + "user_from_authorizer": { + "role": "ADMIN" + } }) reponse = controller(request) @@ -123,7 +144,10 @@ def test_create_court_controller_status_entity_error(self): "number": 7, "status": "INVALID", "is_field": False, - "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/" + "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/", + "user_from_authorizer": { + "role": "ADMIN" + } }) response = controller(request) diff --git a/tests/modules/create_court/app/test_create_court_presenter.py b/tests/modules/create_court/app/test_create_court_presenter.py index bf74fa5..eb05390 100644 --- a/tests/modules/create_court/app/test_create_court_presenter.py +++ b/tests/modules/create_court/app/test_create_court_presenter.py @@ -25,6 +25,12 @@ def test_create_court_presenter(self): "apiId": "", "authentication": None, "authorizer": { + "user": json.dumps({ + "user": { + "role": "ADMIN", + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", @@ -77,6 +83,12 @@ def test_create_court_presenter_missing_number(self): "apiId": "", "authentication": None, "authorizer": { + "user": json.dumps({ + "user": { + "role": "ADMIN", + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", diff --git a/tests/modules/create_court/app/test_create_court_usecase.py b/tests/modules/create_court/app/test_create_court_usecase.py index f81659f..70993b2 100644 --- a/tests/modules/create_court/app/test_create_court_usecase.py +++ b/tests/modules/create_court/app/test_create_court_usecase.py @@ -1,7 +1,7 @@ import pytest from src.modules.create_court.app.create_court_usecase import CreateCourtUsecase from src.shared.infra.repositories.reservation_repository_mock import ReservationRepositoryMock -from src.shared.helpers.errors.usecase_errors import DuplicatedItem +from src.shared.helpers.errors.usecase_errors import DuplicatedItem, ForbiddenAction from src.shared.domain.enums.status_enum import STATUS class TestCreateCourtUsecase: @@ -13,7 +13,8 @@ def test_create_court_usecase(self): number= 8, status= STATUS.AVAILABLE, is_field= False, - photo = "https://super.abril.com.br/mundo-estranho/os-poneis-sao-cavalos-anoes" + photo = "https://super.abril.com.br/mundo-estranho/os-poneis-sao-cavalos-anoes", + role="ADMIN" ) assert repo.courts[-1] == court @@ -28,7 +29,7 @@ def test_create_court_usecase_duplicated_item(self): usecase = CreateCourtUsecase(repo=repo) with pytest.raises(DuplicatedItem): - court = usecase(number= 2, status= STATUS.AVAILABLE, is_field= False, photo = None) + court = usecase(number= 2, status= STATUS.AVAILABLE, is_field= False, photo = None, role="ADMIN") def test_create_court_usecase_no_photo(self): repo = ReservationRepositoryMock() @@ -38,11 +39,26 @@ def test_create_court_usecase_no_photo(self): number= 7, status= STATUS.UNAVAILABLE, is_field = False, - photo = None + photo = None, + role="ADMIN" ) assert court.photo is None assert repo.courts[-1] == court assert court.number == 7 + def test_create_court_usecase_not_admin(self): + + repo = ReservationRepositoryMock() + Usecase = CreateCourtUsecase(repo=repo) + + with pytest.raises(ForbiddenAction): + court = Usecase( + number= 7, + status= STATUS.UNAVAILABLE, + is_field = False, + photo = None, + role="STUDENT" + ) + \ No newline at end of file diff --git a/tests/modules/delete_booking/app/test_delete_booking_controller.py b/tests/modules/delete_booking/app/test_delete_booking_controller.py index 226724f..e25446c 100644 --- a/tests/modules/delete_booking/app/test_delete_booking_controller.py +++ b/tests/modules/delete_booking/app/test_delete_booking_controller.py @@ -3,19 +3,18 @@ from src.shared.helpers.external_interfaces.http_models import HttpRequest from src.shared.infra.repositories.booking_repository_mock import BookingRepositoryMock - class TestDeleteBookingController: def test_delete_booking_controller(self): repo = BookingRepositoryMock() usecase = DeleteBookingUsecase(repo=repo) controller = DeleteBookingController(usecase=usecase) - request = HttpRequest(body={ - "booking_id": 'b1d3bebf-dc0d-4fc1-861c-506a40cc2925' - }, headers={ - 'user_from_authorizer': { - 'id': 'c8435c66-13a4-4641-9d54-773b4b8ccc98', - 'displayName': 'John Doe', - 'mail': 'JD@maua.br' + request = HttpRequest(body= { + "booking_id": 'b1d3bebf-dc0d-4fc1-861c-506a40cc2925', + "user_from_authorizer": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' } }) @@ -25,15 +24,15 @@ def test_delete_booking_controller(self): def test_delete_booking_controller_missing_booking_id(self): repo = BookingRepositoryMock() - usecase = DeleteBookingUsecase(repo=repo) + usecase = DeleteBookingUsecase(repo= repo) controller = DeleteBookingController(usecase=usecase) request = HttpRequest(body={ "booking_id": None }, headers={ 'user_from_authorizer': { - 'id': 'c8435c66-13a4-4641-9d54-773b4b8ccc98', - 'displayName': 'John Doe', - 'mail': 'JD@maua.br' + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'email': 'user@email.com', + 'role': 'STUDENT' } }) @@ -43,18 +42,19 @@ def test_delete_booking_controller_missing_booking_id(self): def test_delete_booking_controller_booking_id_entity_error(self): repo = BookingRepositoryMock() - usecase = DeleteBookingUsecase(repo=repo) + usecase = DeleteBookingUsecase(repo= repo) controller = DeleteBookingController(usecase=usecase) request = HttpRequest(body={ "booking_id": 0 }, headers={ 'user_from_authorizer': { - 'id': 'c8435c66-13a4-4641-9d54-773b4b8ccc98', - 'displayName': 'John Doe', - 'mail': 'JD@maua.br' + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' } }) - + reponse = controller(request) assert reponse.status_code == 400 assert reponse.body == "Field booking_id is not valid" @@ -67,9 +67,10 @@ def test_delete_booking_controller_wrong_type_parameter(self): "booking_id": "not an id" }, headers={ 'user_from_authorizer': { - 'id': 'c8435c66-13a4-4641-9d54-773b4b8ccc98', - 'displayName': 'John Doe', - 'mail': 'JD@maua.br' + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' } }) @@ -85,9 +86,10 @@ def test_delete_booking_controller_not_found(self): "booking_id": 'b1d3bebf-dc0d-4fc1-861c-506a40cc2926' }, headers={ 'user_from_authorizer': { - 'id': 'c8435c66-23a4-4641-9d54-773b4b8ccc99', - 'displayName': 'John Doe', - 'mail': 'JD@maua.br' + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' } }) @@ -103,10 +105,11 @@ def test_delete_bookings_controller_forbidden(self): "booking_id": 'b1d3bebf-dc0d-4fc1-861c-506a40cc2925' }, headers={ 'user_from_authorizer': { - 'id': 'c8435c66-23a4-4641-9d54-773b4b8ccc99', - 'displayName': 'John Doe', - 'mail': 'JD@maua.br' - } + 'user_id': '2f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } }) response = controller(request) diff --git a/tests/modules/delete_booking/app/test_delete_booking_presenter.py b/tests/modules/delete_booking/app/test_delete_booking_presenter.py index c81563e..a7d296a 100644 --- a/tests/modules/delete_booking/app/test_delete_booking_presenter.py +++ b/tests/modules/delete_booking/app/test_delete_booking_presenter.py @@ -25,11 +25,15 @@ def test_delete_booking_presenter(self): "apiId": "", "authentication": None, "authorizer": { - 'user': { - 'id': 'c8435c66-13a4-4641-9d54-773b4b8ccc98', - 'displayName': 'John Doe', - 'mail': 'JD@maua.br' - } + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", @@ -46,13 +50,16 @@ def test_delete_booking_presenter(self): "time": "12/Mar/2020:19:03:58 +0000", "timeEpoch": 1583348638390 }, - "body": '{"booking_id": "b1d3bebf-dc0d-4fc1-861c-506a40cc2925"}', + "body": { + "booking_id": "b1d3bebf-dc0d-4fc1-861c-506a40cc2925" + }, "pathParameters": None, "isBase64Encoded": None, "stageVariables": None } response = lambda_handler(event, None) + print(response) assert response['statusCode'] == 200 assert json.loads(response['body'])['message'] == 'the booking was deleted' @@ -79,11 +86,15 @@ def test_delete_booking_presenter_missing_booking_id(self): "apiId": "", "authentication": None, "authorizer": { - 'user': { - 'id': 'c8435c66-13a4-4641-9d54-773b4b8ccc98', - 'displayName': 'John Doe', - 'mail': 'JD@maua.br' - } + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", @@ -133,11 +144,15 @@ def test_delete_booking_not_found(self): "apiId": "", "authentication": None, "authorizer": { - 'user': { - 'id': 'c8435c66-13a4-4641-9d54-773b4b8ccc98', - 'displayName': 'John Doe', - 'mail': 'JD@maua.br' - } + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", @@ -187,11 +202,15 @@ def test_delete_booking_wrong_type(self): "apiId": "", "authentication": None, "authorizer": { - 'user': { - 'id': 'c8435c66-13a4-4641-9d54-773b4b8ccc98', - 'displayName': 'John Doe', - 'mail': 'JD@maua.br' - } + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", diff --git a/tests/modules/delete_booking/app/test_delete_booking_usecase.py b/tests/modules/delete_booking/app/test_delete_booking_usecase.py index 7fc7a34..b750459 100644 --- a/tests/modules/delete_booking/app/test_delete_booking_usecase.py +++ b/tests/modules/delete_booking/app/test_delete_booking_usecase.py @@ -1,34 +1,76 @@ import pytest -from src.shared.helpers.errors.domain_errors import EntityError +from src.shared.helpers.errors.usecase_errors import DuplicatedItem, ForbiddenAction from src.modules.delete_booking.app.delete_booking_usecase import DeleteBookingUsecase from src.shared.infra.repositories.booking_repository_mock import BookingRepositoryMock from src.shared.helpers.errors.usecase_errors import NoItemsFound +from src.shared.helpers.errors.domain_errors import EntityError class Test_DeleteBookingUsecase: def test_delete_booking_usecase(self): repo = BookingRepositoryMock() usecase = DeleteBookingUsecase(repo=repo) len_before = len(repo.bookings) - + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + booking = usecase(booking_id='b1d3bebf-dc0d-4fc1-861c-506a40cc2925', - user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98') + user=user) + + assert len(repo.bookings) == len_before - 1 + + def test_delete_booking_usecase_admin(self): + repo = BookingRepositoryMock() + usecase = DeleteBookingUsecase(repo=repo) + len_before = len(repo.bookings) + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + + booking = usecase(booking_id='b1d3bebf-dc0d-4fc1-861c-506a40cc2925', user=user) assert len(repo.bookings) == len_before - 1 + def test_delete_booking_usecase_invalid_student(self): + repo = BookingRepositoryMock() + usecase = DeleteBookingUsecase(repo=repo) + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + + with pytest.raises(ForbiddenAction): + usecase(booking_id='b2d3bebf-dc0d-4fc1-861c-506a40cc2925', user=user) + def test_delete_booking_usecase_no_items_found(self): repo = BookingRepositoryMock() usecase = DeleteBookingUsecase(repo=repo) + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } with pytest.raises(NoItemsFound): - booking = usecase(booking_id='b1d3bebf-dc0d-4fc1-861c-506a40cc2926', - user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98') - + booking = usecase(booking_id='b1d3bebf-dc0d-4fc1-861c-506a40cc2926', user = user) + def test_delete_booking_usecase_invalid_booking_id(self): repo = BookingRepositoryMock() usecase = DeleteBookingUsecase(repo=repo) + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } with pytest.raises(EntityError): - usecase(booking_id=-1, - user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98') - - with pytest.raises(EntityError): - usecase(booking_id=None, - user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98') \ No newline at end of file + usecase(booking_id=-1, user=user) + \ No newline at end of file diff --git a/tests/modules/delete_booking/app/test_delete_booking_viewmodel.py b/tests/modules/delete_booking/app/test_delete_booking_viewmodel.py index a8bab9a..fe074a8 100644 --- a/tests/modules/delete_booking/app/test_delete_booking_viewmodel.py +++ b/tests/modules/delete_booking/app/test_delete_booking_viewmodel.py @@ -8,8 +8,13 @@ class Test_DeleteBookingViewModel: def test_delete_booking_viewmodel(self): repo = BookingRepositoryMock() usecase = DeleteBookingUsecase(repo=repo) - booking = usecase(booking_id=repo.bookings[0].booking_id, - user_id=repo.bookings[0].user_id) + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + booking = usecase(booking_id=repo.bookings[0].booking_id, user=user) viewmodel = DeleteBookingViewModel(booking=booking).to_dict() expected = { @@ -18,9 +23,10 @@ def test_delete_booking_viewmodel(self): 'end_date': 1634583365000, 'court_number': 1, 'sport': 'Tennis', - 'user_id': 'c8435c66-13a4-4641-9d54-773b4b8ccc98', + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', 'booking_id': 'b1d3bebf-dc0d-4fc1-861c-506a40cc2925', - 'materials': ['Raquete', 'Bola', 'Rede', 'Tenis'] + 'materials': ['Raquete', 'Bola', 'Rede', 'Tenis'], + "type": 'Training' }, 'message': 'the booking was deleted' } diff --git a/tests/modules/delete_court/app/test_delete_court_controller.py b/tests/modules/delete_court/app/test_delete_court_controller.py index bdca3bf..c59da3b 100644 --- a/tests/modules/delete_court/app/test_delete_court_controller.py +++ b/tests/modules/delete_court/app/test_delete_court_controller.py @@ -9,7 +9,15 @@ def test_delete_court_controller(self): usecase = DeleteCourtUsecase(repo= repo) controller = DeleteCourtController(usecase=usecase) request = HttpRequest(body= { - "number": 2 + "number": 2, + "user_from_authorizer": { + "name": "Dummy", + "email": "admin@maua.br", + "user_id": "user_id", + "ra": None, + "role": "ADMIN", + "confirm_user": False + } }) response = controller(request) @@ -22,7 +30,15 @@ def test_delete_court_controller_missing_number(self): usecase = DeleteCourtUsecase(repo= repo) controller = DeleteCourtController(usecase=usecase) request = HttpRequest(body={ - "number": None + "number": None, + "user_from_authorizer": { + "name": "Dummy", + "email": "admin@maua.br", + "user_id": "user_id", + "ra": None, + "role": "ADMIN", + "confirm_user": False + } }) response = controller(request) @@ -34,7 +50,15 @@ def test_delete_court_controller_number_entity_error(self): usecase = DeleteCourtUsecase(repo= repo) controller = DeleteCourtController(usecase=usecase) request = HttpRequest(body={ - "number": 0 + "number": 0, + "user_from_authorizer": { + "name": "Dummy", + "email": "admin@maua.br", + "user_id": "user_id", + "ra": None, + "role": "ADMIN", + "confirm_user": False + } }) reponse = controller(request) @@ -46,19 +70,35 @@ def test_delete_court_controller_wrong_type_parameter(self): usecase = DeleteCourtUsecase(repo=repo) controller = DeleteCourtController(usecase=usecase) request = HttpRequest(body={ - "number": "wrong_type" + "number": "wrong_type", + "user_from_authorizer": { + "name": "Dummy", + "email": "admin@maua.br", + "user_id": "user_id", + "ra": None, + "role": "ADMIN", + "confirm_user": False + } }) response = controller(request) assert response.status_code == 400 - assert response.body == "Field number is not valid" + assert "Field number isn't in the right type" in response.body def test_delete_court_controller_not_found(self): repo = ReservationRepositoryMock() usecase = DeleteCourtUsecase(repo=repo) controller = DeleteCourtController(usecase=usecase) request = HttpRequest(body={ - "number": 10 + "number": 10, + "user_from_authorizer": { + "name": "Dummy", + "email": "admin@maua.br", + "user_id": "user_id", + "ra": None, + "role": "ADMIN", + "confirm_user": False + } }) response = controller(request) diff --git a/tests/modules/delete_court/app/test_delete_court_presenter.py b/tests/modules/delete_court/app/test_delete_court_presenter.py index a14b441..e9aa872 100644 --- a/tests/modules/delete_court/app/test_delete_court_presenter.py +++ b/tests/modules/delete_court/app/test_delete_court_presenter.py @@ -18,13 +18,22 @@ def test_delete_court_presenter(self): "header2": "value1,value2" }, "queryStringParameters": { - "parameter1": "1" + "number": "1" }, "requestContext": { "accountId": "123456789012", "apiId": "", "authentication": None, "authorizer": { + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'ADMIN' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", @@ -41,7 +50,7 @@ def test_delete_court_presenter(self): "time": "12/Mar/2020:19:03:58 +0000", "timeEpoch": 1583348638390 }, - "body": '{"number": 1}', + "body": {}, "pathParameters": None, "isBase64Encoded": None, "stageVariables": None @@ -74,6 +83,15 @@ def test_delete_court_presenter_missing_number(self): "apiId": "", "authentication": None, "authorizer": { + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'ADMIN' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", @@ -90,7 +108,7 @@ def test_delete_court_presenter_missing_number(self): "time": "12/Mar/2020:19:03:58 +0000", "timeEpoch": 1583348638390 }, - "body": '{}', + "body": {}, "pathParameters": None, "isBase64Encoded": None, "stageVariables": None @@ -116,13 +134,22 @@ def test_delete_court_not_found(self): "header2": "value1,value2" }, "queryStringParameters": { - "parameter1": "1" + "number": "10" }, "requestContext": { "accountId": "123456789012", "apiId": "", "authentication": None, "authorizer": { + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'ADMIN' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", @@ -139,7 +166,7 @@ def test_delete_court_not_found(self): "time": "12/Mar/2020:19:03:58 +0000", "timeEpoch": 1583348638390 }, - "body": '{"number": 10}', + "body": {}, "pathParameters": None, "isBase64Encoded": None, "stageVariables": None @@ -165,13 +192,22 @@ def test_delete_court_wrong_type(self): "header2": "value1,value2" }, "queryStringParameters": { - "parameter1": "1" + "number": "wrong_type" }, "requestContext": { "accountId": "123456789012", "apiId": "", "authentication": None, "authorizer": { + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'ADMIN' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", @@ -188,7 +224,7 @@ def test_delete_court_wrong_type(self): "time": "12/Mar/2020:19:03:58 +0000", "timeEpoch": 1583348638390 }, - "body": '{"number": "wrong_type"}', + "body": {}, "pathParameters": None, "isBase64Encoded": None, "stageVariables": None @@ -197,4 +233,4 @@ def test_delete_court_wrong_type(self): response = lambda_handler(event, None) assert response['statusCode'] == 400 - assert json.loads(response['body']) == 'Field number is not valid' \ No newline at end of file + assert "Field number isn't in the right type" in json.loads(response['body']) \ No newline at end of file diff --git a/tests/modules/delete_court/app/test_delete_court_usecase.py b/tests/modules/delete_court/app/test_delete_court_usecase.py index a58d416..40ab97d 100644 --- a/tests/modules/delete_court/app/test_delete_court_usecase.py +++ b/tests/modules/delete_court/app/test_delete_court_usecase.py @@ -6,16 +6,23 @@ from src.shared.helpers.errors.domain_errors import EntityError from src.modules.delete_court.app.delete_court_usecase import DeleteCourtUsecase from src.shared.infra.repositories.reservation_repository_mock import ReservationRepositoryMock -from src.shared.helpers.errors.usecase_errors import NoItemsFound +from src.shared.helpers.errors.usecase_errors import NoItemsFound, ForbiddenAction from src.shared.domain.repositories.reservation_repository_interface import IReservationRepository class Test_DeleteCourtUsecase: + + def test_delete_court_usecase_forbidden(self): + repo = ReservationRepositoryMock() + usecase = DeleteCourtUsecase(repo=repo) + with pytest.raises(ForbiddenAction): + court = usecase(number=1, role="STUDENT") + def test_delete_court_usecase(self): repo = ReservationRepositoryMock() usecase = DeleteCourtUsecase(repo=repo) len_before = len(repo.courts) - court = usecase(number=1) + court = usecase(number=1, role="ADMIN") assert len(repo.courts) == len_before - 1 assert court.number == 1 @@ -23,16 +30,25 @@ def test_delete_court_usecase_no_items_found(self): repo = ReservationRepositoryMock() usecase = DeleteCourtUsecase(repo=repo) with pytest.raises(NoItemsFound): - court = usecase(number=10) + court = usecase(number=10, role="ADMIN") def test_delete_court_usecase_invalid_number(self): repo = ReservationRepositoryMock() usecase = DeleteCourtUsecase(repo=repo) with pytest.raises(EntityError): - usecase(number=-1) + usecase(number=-1, role="ADMIN") with pytest.raises(EntityError): - usecase(number=None) + usecase(number=None, role="ADMIN") + + def test_delete_court_usecase_not_admin(self): + repo = ReservationRepositoryMock() + usecase = DeleteCourtUsecase(repo=repo) + + with pytest.raises(ForbiddenAction): + usecase(number=1, role="STUDENT") + + assert repo.get_court(1) != None \ No newline at end of file diff --git a/tests/modules/delete_court/app/test_delete_court_viewmodel.py b/tests/modules/delete_court/app/test_delete_court_viewmodel.py index 6abda91..a468098 100644 --- a/tests/modules/delete_court/app/test_delete_court_viewmodel.py +++ b/tests/modules/delete_court/app/test_delete_court_viewmodel.py @@ -8,7 +8,7 @@ class Test_DeleteCourtViewModel: def test_delete_court_viewmodel(self): repo = ReservationRepositoryMock() usecase = DeleteCourtUsecase(repo=repo) - court = usecase(number=repo.courts[0].number) + court = usecase(number=repo.courts[0].number, role="ADMIN") viewmodel = DeleteCourtViewModel(court=court).to_dict() expected = { diff --git a/tests/modules/generate_report/__init__.py b/tests/modules/generate_report/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/modules/generate_report/app/__init__.py b/tests/modules/generate_report/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/modules/generate_report/app/test_generate_report_aggregator.py b/tests/modules/generate_report/app/test_generate_report_aggregator.py new file mode 100644 index 0000000..6b2fcd3 --- /dev/null +++ b/tests/modules/generate_report/app/test_generate_report_aggregator.py @@ -0,0 +1,22 @@ +import pytest + +from src.modules.generate_report.app.generate_report_aggregator import GenerateReportAggregator +from src.modules.generate_report.app.generate_report_extractor import GenerateReportExtractor +from src.shared.infra.repositories.booking_repository_dynamo import BookingRepositoryDynamo + + +class TestGenerateReportAggregator: + + @pytest.mark.skip("Can't run test in gh actions") + def test_generate_report_aggregator(self): + + repo = BookingRepositoryDynamo() + + # Arrange + extractor = GenerateReportExtractor(booking_repository=repo) + aggregator = GenerateReportAggregator(extractor) + + # Act + result = aggregator(1577836800, 1767225600) + + print(result) \ No newline at end of file diff --git a/tests/modules/generate_report/app/test_generate_report_presenter.py b/tests/modules/generate_report/app/test_generate_report_presenter.py new file mode 100644 index 0000000..47c499b --- /dev/null +++ b/tests/modules/generate_report/app/test_generate_report_presenter.py @@ -0,0 +1,37 @@ +import pytest + +from src.modules.generate_report.app.generate_report_aggregator import GenerateReportAggregator +from src.modules.generate_report.app.generate_report_extractor import GenerateReportExtractor +from src.modules.generate_report.app.generate_report_presenter import lambda_handler +from src.modules.generate_report.app.generate_report_transformer import GenerateReportTransformer +from src.shared.infra.repositories.booking_repository_dynamo import BookingRepositoryDynamo + + +class TestGenerateReportPresenter: + + #WONT WORK WITH CURRENT MOCK, ALL DATES ARE OUTDATED AND DO NOT MATCH WITH CURRENT YEAR + + @pytest.mark.skip("Can't run test in github actions") + def test_generate_report_presenter(self): + + event_from_event_bridge = { + "version": "0", + "id": "12345678-1234-1234-1234-123456789012", + "detail-type": "EC2 Instance State-change Notification", + "source": "aws.ec2", + "account": "123456789012", + "time": "2023-11-11T12:00:00Z", + "region": "us-east-1", + "resources": [ + "arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0" + ], + "detail": { + "instance-id": "i-1234567890abcdef0", + "state": "running" + } + } + + sender = lambda_handler(event_from_event_bridge, None) + + #WONT WORK WITH CURRENT MOCK, ALL DATES ARE OUTDATED AND DO NOT MATCH WITH CURRENT YEAR + diff --git a/tests/modules/generate_report/app/test_generate_report_sender.py b/tests/modules/generate_report/app/test_generate_report_sender.py new file mode 100644 index 0000000..6c0d8ad --- /dev/null +++ b/tests/modules/generate_report/app/test_generate_report_sender.py @@ -0,0 +1,38 @@ +import pytest + +from src.modules.generate_report.app.generate_report_aggregator import GenerateReportAggregator +from src.modules.generate_report.app.generate_report_extractor import GenerateReportExtractor +from src.modules.generate_report.app.generate_report_presenter import lambda_handler +from src.modules.generate_report.app.generate_report_transformer import GenerateReportTransformer +from src.shared.infra.repositories.booking_repository_dynamo import BookingRepositoryDynamo + + +class TestGenerateReportPresenter: + + @pytest.mark.skip("Can't run test in gh actions") + + #WONT WORK WITH CURRENT MOCK, ALL DATES ARE OUTDATED AND DO NOT MATCH WITH CURRENT YEAR + + def test_generate_report_presenter(self): + + event_from_event_bridge = { + "version": "0", + "id": "12345678-1234-1234-1234-123456789012", + "detail-type": "EC2 Instance State-change Notification", + "source": "aws.ec2", + "account": "123456789012", + "time": "2023-11-11T12:00:00Z", + "region": "us-east-1", + "resources": [ + "arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0" + ], + "detail": { + "instance-id": "i-1234567890abcdef0", + "state": "running" + } + } + + sender = lambda_handler(event_from_event_bridge, None) + + #WONT WORK WITH CURRENT MOCK, ALL DATES ARE OUTDATED AND DO NOT MATCH WITH CURRENT YEAR + diff --git a/tests/modules/generate_report/app/test_generate_report_transformer.py b/tests/modules/generate_report/app/test_generate_report_transformer.py new file mode 100644 index 0000000..9a9e831 --- /dev/null +++ b/tests/modules/generate_report/app/test_generate_report_transformer.py @@ -0,0 +1,37 @@ +import platform +import subprocess +import pytest + +from src.modules.generate_report.app.generate_report_aggregator import GenerateReportAggregator +from src.modules.generate_report.app.generate_report_extractor import GenerateReportExtractor +from src.modules.generate_report.app.generate_report_transformer import GenerateReportTransformer +from src.shared.infra.repositories.booking_repository_dynamo import BookingRepositoryDynamo +import tempfile +import os + +class TestGenerateReportTransformer: + + @pytest.mark.skip("Can't run test in gh actions") + def test_generate_report_transformer(self): + + repo = BookingRepositoryDynamo() + extractor = GenerateReportExtractor(booking_repository=repo) + aggregator = GenerateReportAggregator(extractor=extractor) + + transformer = GenerateReportTransformer(aggregator=aggregator) + + output = transformer(1577836800, 1767225600) + + with tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") as tmp_file: + tmp_file.write(output.read()) + tmp_file_path = tmp_file.name + + # os.startfile(tmp_file_path) + + system = platform.system() + if system == 'Windows': + os.startfile(tmp_file_path) + elif system == 'Darwin': + subprocess.call(['open', tmp_file_path]) + else: + subprocess.call(['xdg-open', tmp_file_path]) \ No newline at end of file diff --git a/tests/modules/get_all_admin_bookings/__init__.py b/tests/modules/get_all_admin_bookings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/modules/get_all_admin_bookings/app/__init__.py b/tests/modules/get_all_admin_bookings/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/modules/get_all_admin_bookings/app/test_get_all_admin_bookings_controller.py b/tests/modules/get_all_admin_bookings/app/test_get_all_admin_bookings_controller.py new file mode 100644 index 0000000..04c3281 --- /dev/null +++ b/tests/modules/get_all_admin_bookings/app/test_get_all_admin_bookings_controller.py @@ -0,0 +1,16 @@ +from src.modules.get_all_admin_bookings.app.get_all_admin_bookings_controller import GetAllAdminBookingsController +from src.modules.get_all_admin_bookings.app.get_all_admin_bookings_usecase import GetAllAdminBookingsUsecase +from src.shared.helpers.external_interfaces.http_models import HttpRequest +from src.shared.infra.repositories.booking_repository_mock import BookingRepositoryMock +import pytest + +class TestGetAllAdminBookingsController: + + @pytest.mark.skip("needs user endpoint url to urn") + def test_get_all_admin_bookings_controller(self): + + repo = BookingRepositoryMock() + usecase = GetAllAdminBookingsUsecase(repo = repo) + controller = GetAllAdminBookingsController(usecase=usecase) + request = HttpRequest() + response = controller(request) \ No newline at end of file diff --git a/tests/modules/get_all_bookings/app/test_get_all_bookings_usecase.py b/tests/modules/get_all_bookings/app/test_get_all_bookings_usecase.py index 236704e..e9a8013 100644 --- a/tests/modules/get_all_bookings/app/test_get_all_bookings_usecase.py +++ b/tests/modules/get_all_bookings/app/test_get_all_bookings_usecase.py @@ -4,7 +4,7 @@ from src.shared.domain.entities.booking import Booking import pytest -class Test_GetAllBookingsUsecase: +class TestGetAllBookingsUsecase: def test_get_all_bookings_usecase(self): repo = BookingRepositoryMock() usecase = GetAllBookingsUsecase(repo = repo) @@ -15,6 +15,6 @@ def test_get_all_bookings_usecase(self): assert bookings[0].start_date == 1634576165000 assert bookings[0].end_date == 1634583365000 assert bookings[0].sport == SPORT.TENNIS - assert bookings[0].user_id == 'c8435c66-13a4-4641-9d54-773b4b8ccc98' + assert bookings[0].user_id == '1f25448b-3429-4c19-8287-d9e64f17bc3a' assert bookings[0].booking_id == 'b1d3bebf-dc0d-4fc1-861c-506a40cc2925' assert bookings[0].materials == ['Raquete', 'Bola', 'Rede', 'Tenis'] \ No newline at end of file diff --git a/tests/modules/get_all_bookings/app/test_get_all_bookings_viewmodel.py b/tests/modules/get_all_bookings/app/test_get_all_bookings_viewmodel.py index a4ca114..5967770 100644 --- a/tests/modules/get_all_bookings/app/test_get_all_bookings_viewmodel.py +++ b/tests/modules/get_all_bookings/app/test_get_all_bookings_viewmodel.py @@ -1,5 +1,5 @@ from src.modules.get_all_bookings.app.get_all_bookings_usecase import GetAllBookingsUsecase -from src.modules.get_all_bookings.app.get_all_bookings_viewmodel import GetAllBookingViewModel +from src.modules.get_all_bookings.app.get_all_bookings_viewmodel import GetAllBookingsViewModel from src.shared.infra.repositories.booking_repository_mock import BookingRepositoryMock from src.shared.domain.entities.booking import Booking @@ -7,57 +7,102 @@ class Test_GetAllBookingsViewmodel: def test_get_all_bookings_viewmodel(self): repo = BookingRepositoryMock() usecase = GetAllBookingsUsecase(repo = repo) - courts = usecase() - viewmodel = GetAllBookingViewModel(courts).to_dict() + bookings = usecase() + viewmodel = GetAllBookingsViewModel(bookings).to_dict() - excepted = { - 'courts': [ - { - 'start_date': 1634576165000, - 'end_date': 1634583365000, - 'court_number': 1, - 'sport': 'TENNIS', - 'user_id': 'c8435c66-13a4-4641-9d54-773b4b8ccc98', - 'booking_id': 'b1d3bebf-dc0d-4fc1-861c-506a40cc2925', - 'materials': ['Raquete', 'Bola', 'Rede', 'Tenis'] + expected = { + 'bookings': [ + { + 'booking': { + 'start_date': 1634576165000, + 'end_date': 1634583365000, + 'court_number': 1, + 'sport': 'Tennis', + 'booking_id': 'b1d3bebf-dc0d-4fc1-861c-506a40cc2925', + 'materials': ['Raquete', 'Bola', 'Rede', 'Tenis'], + 'type': 'Training' + } + }, + { + 'booking': { + 'start_date': 1634563800000, + 'end_date': 1634567400000, + 'court_number': 2, + 'sport': 'Football', + 'booking_id': 'b2d3bebf-dc0d-4fc1-861c-506a40cc2925', + 'materials': ['Bola', 'Chuteira'], + 'type': 'Training' + } + }, + { + 'booking': { + 'start_date': 1634569200000, + 'end_date': 1634571000000, + 'court_number': 3, + 'sport': 'Basketball', + 'booking_id': 'b3d3bebf-dc0d-4fc1-861c-506a40cc2925', + 'materials': ['Bola'], + 'type': 'Common' + } + }, + { + 'booking': { + 'start_date': 1634574600000, + 'end_date': 1634578200000, + 'court_number': 4, + 'sport': 'Volleyball', + 'booking_id': 'b4d3bebf-dc0d-4fc1-861c-506a40cc2925', + 'materials': ['Bola', 'Rede'], + 'type': 'Common' + } }, { - 'start_date': 1634563800000, - 'end_date': 1634567400000, - 'court_number': 2, - 'sport': 'FOOTBALL', - 'user_id': 'c8435c66-13a4-4641-9d54-773b4b8ccc98', - 'booking_id': 'b2d3bebf-dc0d-4fc1-861c-506a40cc2925', - 'materials': ['Bola', 'Chuteira'] + 'booking': { + 'start_date': 1634580000000, + 'end_date': 1634581800000, + 'court_number': 5, + 'sport': 'Handball', + 'booking_id': 'b5d3bebf-dc0d-4fc1-861c-506a40cc2925', + 'materials': ['Bola'], + 'type': 'Training' + } }, { - 'start_date': 1634569200000, - 'end_date': 1634571000000, - 'court_number': 3, - 'sport': 'BASKETBALL', - 'user_id': 'c8435c66-13a4-4641-9d54-773b4b8ccc98', - 'booking_id': 'b3d3bebf-dc0d-4fc1-861c-506a40cc2925', - 'materials': ['Bola'] + 'booking': { + 'start_date': 1634583600000, + 'end_date': 1634585400000, + 'court_number': 5, + 'sport': 'Futsal', + 'booking_id': 'b6d3bebf-dc0d-4fc1-861c-506a40cc2925', + 'materials': ['Bola', 'Chuteira'], + 'type': 'Training' + } }, { - 'start_date': 1634574600000, - 'end_date': 1634578200000, - 'court_number': 4, - 'sport': 'VOLLEYBALL', - 'user_id': 'c8435c66-13a4-4641-9d54-773b4b8ccc98', - 'booking_id': 'b4d3bebf-dc0d-4fc1-861c-506a40cc2925', - 'materials': ['Bola', 'Rede'] + 'booking': { + 'start_date': 1634587200000, + 'end_date': 1634589000000, + 'court_number': 5, + 'sport': 'Rugby', + 'booking_id': 'b7d3bebf-dc0d-4fc1-861c-506a40cc2925', + 'materials': ['Bola', 'Tenis', 'Capacete'], + 'type': 'Training' + } }, { - 'start_date': 1634580000000, - 'end_date': 1634581800000, - 'court_number': 5, - 'sport': 'HANDBALL', - 'user_id': 'c8435c66-13a4-4641-9d54-773b4b8ccc98', - 'booking_id': 'b5d3bebf-dc0d-4fc1-861c-506a40cc2925', - 'materials': ['Bola'] + 'booking': { + 'start_date': 1634590800000, + 'end_date': 1634592600000, + 'court_number': 5, + 'sport': 'Ping Pong', + 'booking_id': 'b8d3bebf-dc0d-4fc1-861c-506a40cc2925', + 'materials': ['Raquete', 'Bola'], + 'type': 'Training' + } } ], 'message': 'the bookings were retrieved' + } + - } \ No newline at end of file + assert viewmodel == expected diff --git a/tests/modules/get_all_bookings_grouped_by_role/__init__.py b/tests/modules/get_all_bookings_grouped_by_role/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/modules/get_all_courts/app/test_get_all_courts_presenter.py b/tests/modules/get_all_courts/app/test_get_all_courts_presenter.py index 2f214f4..61198b0 100644 --- a/tests/modules/get_all_courts/app/test_get_all_courts_presenter.py +++ b/tests/modules/get_all_courts/app/test_get_all_courts_presenter.py @@ -40,7 +40,7 @@ def test_lambda_handler(self): "time": "12/Mar/2020:19:03:58 +0000", "timeEpoch": 1583348638390 }, - "body": None, + "body": '{"number": 7, "status": "AVAILABLE", "is_field": false, "photo": "photo"}', "pathParameters": None, "isBase64Encoded": None, "stageVariables": None diff --git a/tests/modules/get_booking/app/test_get_booking_controller.py b/tests/modules/get_booking/app/test_get_booking_controller.py index bc5fdc5..ef6a1d2 100644 --- a/tests/modules/get_booking/app/test_get_booking_controller.py +++ b/tests/modules/get_booking/app/test_get_booking_controller.py @@ -19,7 +19,6 @@ def test_get_booking_controller(self): assert response.body['booking']['end_date'] == 1634567400000 assert response.body['booking']['court_number'] == 2 assert response.body['booking']['sport'] == 'Football' - assert response.body['booking']['user_id'] == 'c8435c66-13a4-4641-9d54-773b4b8ccc98' assert response.body['booking']['materials'] == ['Bola', 'Chuteira'] def test_get_booking_controller_missing_booking_id(self): @@ -32,6 +31,7 @@ def test_get_booking_controller_missing_booking_id(self): assert response.status_code == 400 assert response.body == 'Field booking_id is missing' + def test_get_booking_controller_wrong_type_booking_id(self): repo = BookingRepositoryMock() @@ -59,3 +59,4 @@ def test_get_booking_controller_booking_not_found(self): response = controller(request) assert response.status_code == 404 assert response.body == 'No items found for booking_id' + \ No newline at end of file diff --git a/tests/modules/get_booking/app/test_get_booking_presenter.py b/tests/modules/get_booking/app/test_get_booking_presenter.py index d54a475..eb2f0aa 100644 --- a/tests/modules/get_booking/app/test_get_booking_presenter.py +++ b/tests/modules/get_booking/app/test_get_booking_presenter.py @@ -57,7 +57,6 @@ def test_get_booking_presenter(self): assert json.loads(response['body'])['booking']['end_date'] == 1634583365000 assert json.loads(response['body'])['booking']['court_number'] == 1 assert json.loads(response['body'])['booking']['sport'] == 'Tennis' - assert json.loads(response['body'])['booking']['user_id'] == 'c8435c66-13a4-4641-9d54-773b4b8ccc98' assert json.loads(response['body'])['booking']['materials'] == ['Raquete', 'Bola', 'Rede', 'Tenis'] @@ -76,7 +75,7 @@ def test_get_booking_presenter_missing_parameters(self): "header2": "value1,value2" }, "queryStringParameters": { - "parameter1": "1" + }, "requestContext": { "accountId": "123456789012", @@ -107,7 +106,7 @@ def test_get_booking_presenter_missing_parameters(self): response = lambda_handler(event, None) assert response['statusCode'] == 400 - assert json.loads(response['body']) == "Field booking_id is missing" + assert json.loads(response['body']) == 'Field booking_id is missing' def test_get_booking_presenter_entity_error(self): event = { @@ -257,11 +256,4 @@ def test_get_booking_presenter_entity_not_found(self): assert response['statusCode'] == 404 assert json.loads(response['body']) == 'No items found for booking_id' - - - - - - - - + \ No newline at end of file diff --git a/tests/modules/get_booking/app/test_get_booking_viewmodel.py b/tests/modules/get_booking/app/test_get_booking_viewmodel.py index a3b80a6..4defb97 100644 --- a/tests/modules/get_booking/app/test_get_booking_viewmodel.py +++ b/tests/modules/get_booking/app/test_get_booking_viewmodel.py @@ -16,9 +16,9 @@ def test_get_booking_viewmodel(self): 'end_date': 1634583365000, 'court_number': 1, 'sport': 'Tennis', - 'user_id': 'c8435c66-13a4-4641-9d54-773b4b8ccc98', 'booking_id': 'b1d3bebf-dc0d-4fc1-861c-506a40cc2925', - 'materials': ['Raquete', 'Bola', 'Rede', 'Tenis'] + 'materials': ['Raquete', 'Bola', 'Rede', 'Tenis'], + 'type': 'Training' }, 'message': 'the booking was retrieved' } diff --git a/tests/modules/get_bookings/app/test_get_bookings_controller.py b/tests/modules/get_bookings/app/test_get_bookings_controller.py index c32de5b..8ccfa41 100644 --- a/tests/modules/get_bookings/app/test_get_bookings_controller.py +++ b/tests/modules/get_bookings/app/test_get_bookings_controller.py @@ -17,18 +17,17 @@ def test_get_bookings_controller(self): usecase = GetBookingsUseCase(repo=repo) controller = GetBookingsController(usecase=usecase) request = HttpRequest(query_params={ - 'booking_id': 'b2d3bebf-dc0d-4fc1-861c-506a40cc2925', + 'booking_id': 'b6d3bebf-dc0d-4fc1-861c-506a40cc2925', 'user_id': "c8435c66-13a4-4641-9d54-773b4b8ccc98" }) response = controller(request) assert response.status_code == 200 - assert response.body['bookings'][0]['booking_id'] == 'b2d3bebf-dc0d-4fc1-861c-506a40cc2925' - assert response.body['bookings'][0]['start_date'] == 1634563800000 - assert response.body['bookings'][0]['end_date'] == 1634567400000 - assert response.body['bookings'][0]['court_number'] == 2 - assert response.body['bookings'][0]['sport'] == 'Football' - assert response.body['bookings'][0]['user_id'] == 'c8435c66-13a4-4641-9d54-773b4b8ccc98' + assert response.body['bookings'][0]['booking_id'] == 'b6d3bebf-dc0d-4fc1-861c-506a40cc2925' + assert response.body['bookings'][0]['start_date'] == 1634583600000 + assert response.body['bookings'][0]['end_date'] == 1634585400000 + assert response.body['bookings'][0]['court_number'] == 5 + assert response.body['bookings'][0]['sport'] == 'Futsal' assert response.body['bookings'][0]['materials'] == ['Bola', 'Chuteira'] def test_get_bookings_controller_empty_query(self): @@ -40,7 +39,7 @@ def test_get_bookings_controller_empty_query(self): response = controller(request) assert response.status_code == 400 - assert response.body == 'Empty query parameters: At least one of the filters must be provided: booking_id, user_id, sport, court_number, end_date, start_date' + assert response.body == 'Empty query parameters: At least one of the filters must be provided: booking_id, user_id, sport, court_number, end_date, start_date, type' def test_get_bookings_controller_wrong_type_booking_id(self): repo = BookingRepositoryMock() @@ -105,8 +104,8 @@ def test_get_bookings_controller_user_id(self): assert response.status_code == 200 - for booking in response.body['bookings']: - assert booking['user_id'] == 'c8435c66-13a4-4641-9d54-773b4b8ccc98' + # for booking in response.body['bookings']: + # assert booking['user_id'] == 'c8435c66-13a4-4641-9d54-773b4b8ccc98' def test_get_bookings_controller_start_date_error(self): @@ -167,7 +166,6 @@ def test_get_bookings_controller_five_filters(self): assert len(bookings) == 1 b = bookings[0] assert b['booking_id'] == 'b6d3bebf-dc0d-4fc1-861c-506a40cc2925' - assert b['user_id'] == 'c8435c66-13a4-4641-9d54-773b4b8ccc98' assert b['sport'] == 'Futsal' assert b['court_number'] == 5 assert b['start_date'] >= int(start) and b['end_date'] <= int(end) @@ -188,7 +186,6 @@ def test_get_bookings_controller_four_filters(self): bookings = response.body['bookings'] assert len(bookings) == 1 b = bookings[0] - assert b['user_id'] == 'c8435c66-13a4-4641-9d54-773b4b8ccc98' assert b['sport'] == 'Volleyball' assert b['court_number'] == 4 assert b['start_date'] >= int(start) and b['end_date'] <= int(end) @@ -207,7 +204,6 @@ def test_get_bookings_controller_three_filters(self): b = bookings[0] assert b['sport'] == 'Rugby' assert b['court_number'] == 5 - assert b['user_id'] == 'c8435c66-13a4-4641-9d54-773b4b8ccc98' def test_get_bookings_user_id_not_valid(self): request = HttpRequest(query_params={ @@ -215,4 +211,3 @@ def test_get_bookings_user_id_not_valid(self): }) response = self.controller(request) assert response.status_code == 400 - diff --git a/tests/modules/get_bookings/app/test_get_bookings_presenter.py b/tests/modules/get_bookings/app/test_get_bookings_presenter.py index d150375..c58e4d7 100644 --- a/tests/modules/get_bookings/app/test_get_bookings_presenter.py +++ b/tests/modules/get_bookings/app/test_get_bookings_presenter.py @@ -2,7 +2,7 @@ from src.modules.get_bookings.app.get_bookings_presenter import lambda_handler from src.shared.infra.repositories.booking_repository_mock import BookingRepositoryMock -class Test_GetBookingPresenter: +class TestGetBookingsPresenter: def test_get_bookings_presenter(self): event = { @@ -32,7 +32,7 @@ def test_get_bookings_presenter(self): "authentication": None, "authorizer": { "user": { - "id": "c8435c66-13a4-4641-9d54-773b4b8ccc98", + "id": "1f25448b-3429-4c19-8287-d9e64f17bc3a", "displayName": "User", "mail": "lbj@maua.br" } @@ -67,7 +67,6 @@ def test_get_bookings_presenter(self): assert json.loads(response['body'])['bookings'][0]['end_date'] == 1634583365000 assert json.loads(response['body'])['bookings'][0]['court_number'] == 1 assert json.loads(response['body'])['bookings'][0]['sport'] == 'Tennis' - assert json.loads(response['body'])['bookings'][0]['user_id'] == 'c8435c66-13a4-4641-9d54-773b4b8ccc98' assert json.loads(response['body'])['bookings'][0]['materials'] == ['Raquete', 'Bola', 'Rede', 'Tenis'] @@ -122,7 +121,7 @@ def test_get_bookings_presenter_missing_parameters(self): response = lambda_handler(event, None) assert response['statusCode'] == 400 - assert json.loads(response['body']) == 'Empty query parameters: At least one of the filters must be provided: booking_id, user_id, sport, court_number, end_date, start_date' + assert json.loads(response['body']) == 'Empty query parameters: At least one of the filters must be provided: booking_id, user_id, sport, court_number, end_date, start_date, type' def test_get_bookings_presenter_entity_error(self): event = { @@ -287,11 +286,3 @@ def test_get_bookings_presenter_entity_not_found(self): assert response['statusCode'] == 404 assert json.loads(response['body']) == 'No items found for booking filters passed' - - - - - - - - diff --git a/tests/modules/get_bookings/app/test_get_bookings_viewmodel.py b/tests/modules/get_bookings/app/test_get_bookings_viewmodel.py index 27bcd57..d5bdb4f 100644 --- a/tests/modules/get_bookings/app/test_get_bookings_viewmodel.py +++ b/tests/modules/get_bookings/app/test_get_bookings_viewmodel.py @@ -16,9 +16,9 @@ def test_get_bookings_viewmodel(self): 'end_date': 1634583365000, 'court_number': 1, 'sport': 'Tennis', - 'user_id': 'c8435c66-13a4-4641-9d54-773b4b8ccc98', 'booking_id': 'b1d3bebf-dc0d-4fc1-861c-506a40cc2925', - 'materials': ['Raquete', 'Bola', 'Rede', 'Tenis'] + 'materials': ['Raquete', 'Bola', 'Rede', 'Tenis'], + 'type': 'Training' }], 'message': 'the bookings were retrieved' } diff --git a/tests/modules/get_court/app/test_get_court_controller.py b/tests/modules/get_court/app/test_get_court_controller.py index f6055a4..62fe2ca 100644 --- a/tests/modules/get_court/app/test_get_court_controller.py +++ b/tests/modules/get_court/app/test_get_court_controller.py @@ -11,7 +11,7 @@ def test_get_court_controller(self): usecase = GetCourtUsecase(repo=repo) controller = GetCourtController(usecase=usecase) request = HttpRequest(query_params={ - "number": 2 + "number": "2" }) response = controller(request) diff --git a/tests/modules/get_court/app/test_get_court_presenter.py b/tests/modules/get_court/app/test_get_court_presenter.py index 7f93dfb..46ee487 100644 --- a/tests/modules/get_court/app/test_get_court_presenter.py +++ b/tests/modules/get_court/app/test_get_court_presenter.py @@ -19,7 +19,7 @@ def test_get_court_presenter(self): "header2": "value1,value2" }, "queryStringParameters": { - "number": "1" #tem que passar ja que no querry sempre vem como string + "parameter1": "1" }, "requestContext": { "accountId": "123456789012", @@ -42,7 +42,7 @@ def test_get_court_presenter(self): "time": "12/Mar/2020:19:03:58 +0000", "timeEpoch": 1583348638390 }, - "body": {}, + "body": {"number":1}, "pathParameters": None, "isBase64Encoded": None, "stageVariables": None @@ -117,7 +117,7 @@ def test_get_court_presenter_entity_error(self): "header2": "value1,value2" }, "queryStringParameters": { - "number": 15 + "parameter1": "1" }, "requestContext": { "accountId": "123456789012", @@ -140,7 +140,7 @@ def test_get_court_presenter_entity_error(self): "time": "12/Mar/2020:19:03:58 +0000", "timeEpoch": 1583348638390 }, - "body": {}, + "body": {"number": 15}, "pathParameters": None, "isBase64Encoded": None, "stageVariables": None @@ -166,7 +166,7 @@ def test_get_court_presenter_wrong_type_parameter(self): "header2": "value1,value2" }, "queryStringParameters": { - "code": "1" + "parameter1": "1" }, "requestContext": { "accountId": "123456789012", @@ -189,7 +189,7 @@ def test_get_court_presenter_wrong_type_parameter(self): "time": "12/Mar/2020:19:03:58 +0000", "timeEpoch": 1583348638390 }, - "body": {}, + "body": {"code": "1"}, "pathParameters": None, "isBase64Encoded": None, "stageVariables": None @@ -216,7 +216,7 @@ def test_get_court_presenter_not_found(self): "header2": "value1,value2" }, "queryStringParameters": { - "number": 8 + "parameter1": "1" }, "requestContext": { "accountId": "123456789012", @@ -239,7 +239,7 @@ def test_get_court_presenter_not_found(self): "time": "12/Mar/2020:19:03:58 +0000", "timeEpoch": 1583348638390 }, - "body": {}, + "body": {"number": 8}, "pathParameters": None, "isBase64Encoded": None, "stageVariables": None diff --git a/tests/modules/update_booking/app/test_update_booking_controller.py b/tests/modules/update_booking/app/test_update_booking_controller.py index 02f02d2..fd19080 100644 --- a/tests/modules/update_booking/app/test_update_booking_controller.py +++ b/tests/modules/update_booking/app/test_update_booking_controller.py @@ -14,12 +14,18 @@ def test_update_booking_controller(self): controller = UpdateBookingController(update_booking_use_case=usecase) request = HttpRequest(body={ - "booking_id": booking_repo.bookings[0].booking_id, - "start_date": booking_repo.bookings[0].start_date, - "end_date": booking_repo.bookings[0].end_date, - "court_number": 1, - "sport": "Tennis", - "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'] + "booking_id": booking_repo.bookings[0].booking_id, + "start_date": booking_repo.bookings[0].start_date, + "end_date": booking_repo.bookings[0].end_date, + "court_number": 1, + "sport": "Tennis", + "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'], + "user_from_authorizer": { + 'user_id': 'qualquer-id', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'ADMIN' + } }) response = controller(request) @@ -27,17 +33,51 @@ def test_update_booking_controller(self): assert response.status_code == 200 assert response.body['message'] == "the booking was retrieved" + + def test_update_booking_controller_with_invalid_user_id(self): + booking_repo = BookingRepositoryMock() + usecase = UpdateBookingUsecase(booking_repo=booking_repo) + controller = UpdateBookingController(update_booking_use_case=usecase) + + request = HttpRequest(body={ + "booking_id": booking_repo.bookings[0].booking_id, + "start_date": booking_repo.bookings[0].start_date, + "end_date": booking_repo.bookings[0].end_date, + "court_number": 1, + "sport": "Tennis", + "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'], + "user_from_authorizer": { + 'user_id': 'invalid', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + }) + + response = controller(request) + + assert response.status_code == 403 + assert response.body == "That action is forbidden for this user id" + + def test_update_booking_controller_booking_id_missing(self): booking_repo = BookingRepositoryMock() usecase = UpdateBookingUsecase(booking_repo=booking_repo) controller = UpdateBookingController(update_booking_use_case=usecase) request = HttpRequest(body={ - "start_date": booking_repo.bookings[0].start_date, - "end_date": booking_repo.bookings[0].end_date, - "court_number": 1, - "sport": "Tennis", - "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'] + "start_date": booking_repo.bookings[0].start_date, + "end_date": booking_repo.bookings[0].end_date, + "court_number": 1, + "sport": "Tennis", + "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'], + "user_from_authorizer": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + }) response = controller(request) @@ -51,12 +91,19 @@ def test_update_booking_controller_booking_id_wrong_type(self): controller = UpdateBookingController(update_booking_use_case=usecase) request = HttpRequest(body={ - "booking_id": 123, - "start_date": booking_repo.bookings[0].start_date, - "end_date": booking_repo.bookings[0].end_date, - "court_number": 1, - "sport": "Tennis", - "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'] + "booking_id": 123, + "start_date": booking_repo.bookings[0].start_date, + "end_date": booking_repo.bookings[0].end_date, + "court_number": 1, + "sport": "Tennis", + "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'], + "user_from_authorizer": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + }) response = controller(request) @@ -71,12 +118,18 @@ def test_update_booking_controller_start_date_wrong_type(self): controller = UpdateBookingController(update_booking_use_case=usecase) request = HttpRequest(body={ - "booking_id": booking_repo.bookings[0].booking_id, - "start_date": "123", - "end_date": booking_repo.bookings[0].end_date, - "court_number": 1, - "sport": "Tennis", - "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'] + "booking_id": booking_repo.bookings[0].booking_id, + "start_date": "123", + "end_date": booking_repo.bookings[0].end_date, + "court_number": 1, + "sport": "Tennis", + "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'], + "user_from_authorizer": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } }) response = controller(request) @@ -91,12 +144,18 @@ def test_update_booking_controller_end_date_wrong_type(self): controller = UpdateBookingController(update_booking_use_case=usecase) request = HttpRequest(body={ - "booking_id": booking_repo.bookings[0].booking_id, - "start_date": booking_repo.bookings[0].start_date, - "end_date": "123", - "court_number": 1, - "sport": "Tennis", - "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'] + "booking_id": booking_repo.bookings[0].booking_id, + "start_date": booking_repo.bookings[0].start_date, + "end_date": "123", + "court_number": 1, + "sport": "Tennis", + "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'], + "user_from_authorizer": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } }) response = controller(request) @@ -111,12 +170,18 @@ def test_update_booking_controller_court_number_wrong_type(self): controller = UpdateBookingController(update_booking_use_case=usecase) request = HttpRequest(body={ - "booking_id": booking_repo.bookings[0].booking_id, - "start_date": booking_repo.bookings[0].start_date, - "end_date": booking_repo.bookings[0].end_date, - "court_number": "1", - "sport": "Tennis", - "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'] + "booking_id": booking_repo.bookings[0].booking_id, + "start_date": booking_repo.bookings[0].start_date, + "end_date": booking_repo.bookings[0].end_date, + "court_number": "1", + "sport": "Tennis", + "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'], + "user_from_authorizer": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } }) response = controller(request) @@ -131,12 +196,18 @@ def test_update_booking_controller_sport_wrong_type(self): controller = UpdateBookingController(update_booking_use_case=usecase) request = HttpRequest(body={ - "booking_id": booking_repo.bookings[0].booking_id, - "start_date": booking_repo.bookings[0].start_date, - "end_date": booking_repo.bookings[0].end_date, - "court_number": 1, - "sport": 123, - "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'] + "booking_id": booking_repo.bookings[0].booking_id, + "start_date": booking_repo.bookings[0].start_date, + "end_date": booking_repo.bookings[0].end_date, + "court_number": 1, + "sport": 123, + "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'], + "user_from_authorizer": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } }) response = controller(request) @@ -151,12 +222,18 @@ def test_update_booking_controller_materials_wrong_type(self): controller = UpdateBookingController(update_booking_use_case=usecase) request = HttpRequest(body={ - "booking_id": booking_repo.bookings[0].booking_id, - "start_date": booking_repo.bookings[0].start_date, - "end_date": booking_repo.bookings[0].end_date, - "court_number": 1, - "sport": "Tennis", - "materials": "Raquete, Bola, Rede, Tenis" + "booking_id": booking_repo.bookings[0].booking_id, + "start_date": booking_repo.bookings[0].start_date, + "end_date": booking_repo.bookings[0].end_date, + "court_number": 1, + "sport": "Tennis", + "materials": "Raquete, Bola, Rede, Tenis", + "user_from_authorizer": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } }) response = controller(request) @@ -174,7 +251,13 @@ def test_update_booking_controller_only_booking_id(self): booking_id = original_booking.booking_id request = HttpRequest(body={ - "booking_id": booking_id + "booking_id": booking_id, + "user_from_authorizer": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } }) response = controller(request) @@ -192,13 +275,19 @@ def test_update_booking_controller_user_id_ignored(self): new_user_id = "novo-user-id-que-nao-deve-ser-usado" request = HttpRequest(body={ - "booking_id": original_booking.booking_id, - "start_date": original_booking.start_date, - "end_date": original_booking.end_date, - "court_number": 1, - "sport": "Tennis", - "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'], - "user_id": new_user_id + "booking_id": original_booking.booking_id, + "start_date": original_booking.start_date, + "end_date": original_booking.end_date, + "court_number": 1, + "sport": "Tennis", + "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'], + "user_id": new_user_id, + "user_from_authorizer": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } }) response = controller(request) @@ -224,12 +313,18 @@ def get_booking_mock(booking_id): booking_repo.get_booking = get_booking_mock request = HttpRequest(body={ - "booking_id": non_existent_booking_id, - "start_date": 1634576165000, - "end_date": 1634583365000, - "court_number": 1, - "sport": "Tennis", - "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'] + "booking_id": non_existent_booking_id, + "start_date": 1634576165000, + "end_date": 1634583365000, + "court_number": 1, + "sport": "Tennis", + "materials": ['Raquete', 'Bola', 'Rede', 'Tenis'], + "user_from_authorizer": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } }) response = controller(request) @@ -251,7 +346,13 @@ def test_update_booking_controller_partial_update(self): request = HttpRequest(body={ "booking_id": booking_id, - "court_number": new_court_number + "court_number": new_court_number, + "user_from_authorizer": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } }) response = controller(request) diff --git a/tests/modules/update_booking/app/test_update_booking_presenter.py b/tests/modules/update_booking/app/test_update_booking_presenter.py index 9887f18..6fe8ca3 100644 --- a/tests/modules/update_booking/app/test_update_booking_presenter.py +++ b/tests/modules/update_booking/app/test_update_booking_presenter.py @@ -26,6 +26,15 @@ def test_lambda_handler(self): "apiId": "", "authentication": None, "authorizer": { + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", diff --git a/tests/modules/update_booking/app/test_update_booking_usecase.py b/tests/modules/update_booking/app/test_update_booking_usecase.py index 2b6a3d4..f853693 100644 --- a/tests/modules/update_booking/app/test_update_booking_usecase.py +++ b/tests/modules/update_booking/app/test_update_booking_usecase.py @@ -3,7 +3,7 @@ from src.modules.update_booking.app.update_booking_usecase import UpdateBookingUsecase from src.shared.domain.enums.sport import SPORT from src.shared.helpers.errors.domain_errors import EntityError, EntityParameterOrderDatesError, EntityParameterTimeError -from src.shared.helpers.errors.usecase_errors import NoItemsFound +from src.shared.helpers.errors.usecase_errors import ForbiddenAction, InvalidSchedule from src.shared.infra.repositories.booking_repository_mock import BookingRepositoryMock @@ -14,7 +14,15 @@ def test_update_booking_usecase(self): booking_id = booking_repo.bookings[0].booking_id + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + booking = usecase(booking_id=booking_id, + user=user, court_number=2, start_date=1634576165000, end_date=1634583365000, @@ -29,12 +37,97 @@ def test_update_booking_usecase(self): assert booking_repo.bookings[0].sport == booking.sport assert booking_repo.bookings[0].materials == booking.materials + def test_update_booking_usecase_with_invalid_user_id(self): + booking_repo = BookingRepositoryMock() + usecase = UpdateBookingUsecase(booking_repo=booking_repo) + + booking_id = booking_repo.bookings[0].booking_id + + user = { + 'user_id': 'c07e0862-3c07-4227-ab0f-511a267cb7ff', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + + with pytest.raises(ForbiddenAction): + usecase(booking_id=booking_id, + user=user, + court_number=2, + start_date=1634576165000, + end_date=1634583365000, + sport=SPORT.TENNIS, + materials=['Raquete', 'Bola', 'Rede', 'Tenis'] + ) + + def test_update_booking_usecase_invalid_schedule_overlap(self): + + with pytest.raises(InvalidSchedule) as e: + + booking_repo = BookingRepositoryMock() + usecase = UpdateBookingUsecase(booking_repo=booking_repo) + + booking_id = booking_repo.bookings[0].booking_id + + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + + booking = usecase(booking_id=booking_id, + user=user, + court_number=3, + start_date=1634569200000, + end_date=1634570000000, + sport=SPORT.TENNIS, + materials=['Raquete', 'Bola', 'Rede', 'Tenis'] + ) + + assert e.value == "Court is already booked for the selected time slot or has to have 15 min tolerance" + + def test_update_booking_usecase_invalid_schedule_15min_intolerance(self): + + with pytest.raises(InvalidSchedule) as e: + + booking_repo = BookingRepositoryMock() + usecase = UpdateBookingUsecase(booking_repo=booking_repo) + + booking_id = booking_repo.bookings[0].booking_id + + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + + booking = usecase(booking_id=booking_id, + user=user, + court_number=3, + start_date=1634571899999, + end_date=1734571000000, + sport=SPORT.TENNIS, + materials=['Raquete', 'Bola', 'Rede', 'Tenis'] + ) + + assert e.value == "Court is already booked for the selected time slot or has to have 15 min tolerance" + def test_update_booking_usecase_invalid_booking_id(self): booking_repo = BookingRepositoryMock() usecase = UpdateBookingUsecase(booking_repo=booking_repo) + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + with pytest.raises(EntityError): booking = usecase(booking_id=111, + user=user, court_number=2, start_date=1634576165000, end_date=1634583365000, @@ -46,8 +139,16 @@ def test_update_booking_usecase_invalid_dates(self): booking_repo = BookingRepositoryMock() usecase = UpdateBookingUsecase(booking_repo=booking_repo) + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + with pytest.raises(EntityError): booking = usecase(booking_id=booking_repo.bookings[0].booking_id, + user=user, court_number=2, start_date="1634583365000", end_date="1634576165000", @@ -59,8 +160,16 @@ def test_update_booking_usecase_invalid_sport(self): booking_repo = BookingRepositoryMock() usecase = UpdateBookingUsecase(booking_repo=booking_repo) + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + with pytest.raises(EntityError): - booking = usecase(booking_id=booking_repo.bookings[0].booking_id, + booking = usecase(booking_id=booking_repo.bookings[0].booking_id, + user=user, court_number=2, start_date=1634576165000, end_date=1634583365000, @@ -72,8 +181,16 @@ def test_update_booking_usecase_invalid_materials(self): booking_repo = BookingRepositoryMock() usecase = UpdateBookingUsecase(booking_repo=booking_repo) + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + with pytest.raises(EntityError): - booking = usecase(booking_id=booking_repo.bookings[0].booking_id, + booking = usecase(booking_id=booking_repo.bookings[0].booking_id, + user=user, court_number=2, start_date=1634576165000, end_date=1634583365000, @@ -85,8 +202,16 @@ def test_update_booking_usecase_none_booking_id(self): booking_repo = BookingRepositoryMock() usecase = UpdateBookingUsecase(booking_repo=booking_repo) + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + with pytest.raises(EntityError): - booking = usecase(booking_id=None, + booking = usecase(booking_id=None, + user=user, court_number=2, start_date=1634576165000, end_date=1634583365000, @@ -97,12 +222,20 @@ def test_update_booking_usecase_none_booking_id(self): def test_update_booking_usecase_none_court_number(self): booking_repo = BookingRepositoryMock() usecase = UpdateBookingUsecase(booking_repo=booking_repo) - + + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + original_booking = booking_repo.bookings[0] original_court_number = original_booking.court_number booking = usecase( - booking_id=original_booking.booking_id, + booking_id=original_booking.booking_id, + user=user, court_number=None, start_date=1634576165000, end_date=1634583365000, @@ -118,9 +251,17 @@ def test_update_booking_usecase_none_start_date(self): original_booking = booking_repo.bookings[0] original_start_date = original_booking.start_date - + + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + booking = usecase( - booking_id=original_booking.booking_id, + booking_id=original_booking.booking_id, + user=user, court_number=2, start_date=None, end_date=1634583365000, @@ -133,12 +274,20 @@ def test_update_booking_usecase_none_start_date(self): def test_update_booking_usecase_none_end_date(self): booking_repo = BookingRepositoryMock() usecase = UpdateBookingUsecase(booking_repo=booking_repo) - + + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + original_booking = booking_repo.bookings[0] original_end_date = original_booking.end_date booking = usecase( - booking_id=original_booking.booking_id, + booking_id=original_booking.booking_id, + user=user, court_number=2, start_date=1634576165000, end_date=None, @@ -152,8 +301,16 @@ def test_update_booking_usecase_order_dates_incorrect(self): booking_repo = BookingRepositoryMock() usecase = UpdateBookingUsecase(booking_repo=booking_repo) + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + with pytest.raises(EntityParameterOrderDatesError): - booking = usecase(booking_id=booking_repo.bookings[0].booking_id, + booking = usecase(booking_id=booking_repo.bookings[0].booking_id, + user=user, court_number=2, start_date=1634583365000, end_date=1634576165000, @@ -164,12 +321,20 @@ def test_update_booking_usecase_order_dates_incorrect(self): def test_update_booking_usecase_none_sport(self): booking_repo = BookingRepositoryMock() usecase = UpdateBookingUsecase(booking_repo=booking_repo) - + + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + original_booking = booking_repo.bookings[0] original_sport = original_booking.sport booking = usecase( - booking_id=original_booking.booking_id, + booking_id=original_booking.booking_id, + user=user, court_number=2, start_date=1634576165000, end_date=1634583365000, @@ -182,12 +347,20 @@ def test_update_booking_usecase_none_sport(self): def test_update_booking_usecase_none_materials(self): booking_repo = BookingRepositoryMock() usecase = UpdateBookingUsecase(booking_repo=booking_repo) - + + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + original_booking = booking_repo.bookings[0] original_materials = original_booking.materials booking = usecase( - booking_id=original_booking.booking_id, + booking_id=original_booking.booking_id, + user=user, court_number=2, start_date=1634576165000, end_date=1634583365000, @@ -201,18 +374,26 @@ def test_update_booking_usecase_cannot_update_user_id(self): booking_repo = BookingRepositoryMock() usecase = UpdateBookingUsecase(booking_repo=booking_repo) + user = { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'STUDENT' + } + original_booking = booking_repo.bookings[0] booking_id = original_booking.booking_id original_user_id = original_booking.user_id booking = usecase( - booking_id=booking_id, + booking_id=booking_id, + user=user, court_number=2, start_date=1634576165000, end_date=1634583365000, sport=SPORT.TENNIS, materials=['Raquete', 'Bola', 'Rede', 'Tenis'], - user_id="novo-user-id-que-nao-deve-ser-usado" + new_user_id="novo-user-id-que-nao-deve-ser-usado" ) assert booking.user_id == original_user_id \ No newline at end of file diff --git a/tests/modules/update_booking/app/test_update_booking_viewmodel.py b/tests/modules/update_booking/app/test_update_booking_viewmodel.py index 6e9e708..1458306 100644 --- a/tests/modules/update_booking/app/test_update_booking_viewmodel.py +++ b/tests/modules/update_booking/app/test_update_booking_viewmodel.py @@ -1,6 +1,7 @@ from src.modules.update_booking.app.update_booking_viewmodel import UpdateBookingViewmodel from src.shared.domain.entities.booking import Booking from src.shared.domain.enums.sport import SPORT +from src.shared.domain.enums.type import BOOKING_TYPE class Test_UpdateBookingViewmodel: @@ -12,7 +13,8 @@ def test_update_booking_view_model(self): sport=SPORT.FOOTBALL, user_id='c8435c66-13a4-4641-9d54-773b4b8ccc98', booking_id='b1d3bebf-dc0d-4fc1-861c-506a40cc2925', - materials=['Bola']) + materials=['Bola'], + booking_type=BOOKING_TYPE.TRAINING) viewmodel = UpdateBookingViewmodel(booking=booking).to_dict() @@ -24,7 +26,8 @@ def test_update_booking_view_model(self): 'sport': 'Football', 'user_id': 'c8435c66-13a4-4641-9d54-773b4b8ccc98', 'booking_id': 'b1d3bebf-dc0d-4fc1-861c-506a40cc2925', - 'materials': ['Bola'] + 'materials': ['Bola'], + 'type': 'Training' }, 'message': 'the booking was retrieved' } diff --git a/tests/modules/update_court/app/test_update_court_controller.py b/tests/modules/update_court/app/test_update_court_controller.py index 118c53f..bd048be 100644 --- a/tests/modules/update_court/app/test_update_court_controller.py +++ b/tests/modules/update_court/app/test_update_court_controller.py @@ -14,7 +14,10 @@ def test_update_court_controller_two_params(self): request = HttpRequest(body={ "number": court_number, "status": "MAINTENANCE", - "photo": "https://www.linkedin.com/in/leonardo-iorio-b83360279/" + "photo": "https://www.linkedin.com/in/leonardo-iorio-b83360279/", + "user_from_authorizer": { + "role": "ADMIN" + } }) response = controller(request) @@ -34,6 +37,9 @@ def test_update_court_controller_only_status_param(self): request = HttpRequest(body={ "number": court_number, "status": "MAINTENANCE", + "user_from_authorizer": { + "role": "ADMIN" + } }) response = controller(request) @@ -52,7 +58,10 @@ def test_update_court_controller_only_photo_param(self): request = HttpRequest(body={ "number": court_number, - "photo": "photostr" + "photo": "photostr", + "user_from_authorizer": { + "role": "ADMIN" + } }) response = controller(request) @@ -71,7 +80,10 @@ def test_update_court_controller_missing_number(self): request = HttpRequest(body={ "status": "AVAILABLE", "is_field": False, - "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/" + "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/", + "user_from_authorizer": { + "role": "ADMIN" + } }) response = controller(request) @@ -87,7 +99,10 @@ def test_update_court_controller_wrong_type_number(self): "number": "cavalo", "status": "AVAILABLE", "is_field": False, - "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/" + "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/", + "user_from_authorizer": { + "role": "ADMIN" + } }) reponse = controller(request) @@ -104,7 +119,10 @@ def test_update_court_controller_with_wrong_type_status(self): "number": 7, "status": 123, "is_field": False, - "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/" + "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/", + "user_from_authorizer": { + "role": "ADMIN" + } }) response = controller(request) @@ -120,7 +138,10 @@ def test_update_court_controller_status_entity_error(self): "number": 7, "status": "INVALID", "is_field": False, - "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/" + "photo": "https://www.linkedin.com/in/vinicius-berti-a80354209/", + "user_from_authorizer": { + "role": "ADMIN" + } }) response = controller(request) @@ -136,7 +157,10 @@ def test_update_court_controller_photo_type_error(self): "number": 7, "status": "AVAILABLE", "is_field": False, - "photo": 1337 + "photo": 1337, + "user_from_authorizer": { + "role": "ADMIN" + } }) response = controller(request) @@ -152,7 +176,10 @@ def test_update_court_controller_number_not_found(self): "number": 9, "status": "AVAILABLE", "is_field": False, - "photo": "1234" + "photo": "1234", + "user_from_authorizer": { + "role": "ADMIN" + } }) response = controller(request) @@ -168,7 +195,10 @@ def test_update_court_controller_invalid_number(self): "number": -999, "status": "AVAILABLE", "is_field": False, - "photo": "1234" + "photo": "1234", + "user_from_authorizer": { + "role": "ADMIN" + } }) response = controller(request) diff --git a/tests/modules/update_court/app/test_update_court_presenter.py b/tests/modules/update_court/app/test_update_court_presenter.py index 6fa0f82..1ec1697 100644 --- a/tests/modules/update_court/app/test_update_court_presenter.py +++ b/tests/modules/update_court/app/test_update_court_presenter.py @@ -25,6 +25,15 @@ def test_update_court_presenter_one_param(self): "apiId": "", "authentication": None, "authorizer": { + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'ADMIN' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", @@ -77,6 +86,15 @@ def test_update_court_presenter(self): "apiId": "", "authentication": None, "authorizer": { + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'ADMIN' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", @@ -130,6 +148,15 @@ def test_update_court_presenter_missing_number(self): "apiId": "", "authentication": None, "authorizer": { + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'ADMIN' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", @@ -179,6 +206,15 @@ def test_update_court_presenter_wrong_type_number(self): "apiId": "", "authentication": None, "authorizer": { + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'ADMIN' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", @@ -228,6 +264,15 @@ def test_update_court_presenter_wrong_type_status(self): "apiId": "", "authentication": None, "authorizer": { + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'ADMIN' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", @@ -278,6 +323,15 @@ def test_update_court_presenter_wrong_status_entity(self): "apiId": "", "authentication": None, "authorizer": { + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'ADMIN' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", @@ -327,6 +381,15 @@ def test_update_court_presenter_wrong_photo_type(self): "apiId": "", "authentication": None, "authorizer": { + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'ADMIN' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", @@ -376,6 +439,15 @@ def test_update_court_presenter_number_not_found(self): "apiId": "", "authentication": None, "authorizer": { + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'ADMIN' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", @@ -425,6 +497,15 @@ def test_update_court_presenter_invalid_number(self): "apiId": "", "authentication": None, "authorizer": { + "user": json.dumps({ + "user": { + 'user_id': '1f25448b-3429-4c19-8287-d9e64f17bc3a', + 'name': 'Nome', + 'email': 'user@email.com', + 'role': 'ADMIN' + }, + "message": "the user was retrieved" + }) }, "domainName": ".lambda-url.us-west-2.on.aws", "domainPrefix": "", diff --git a/tests/modules/update_court/app/test_update_court_usecase.py b/tests/modules/update_court/app/test_update_court_usecase.py index 4322ee6..4706b2a 100644 --- a/tests/modules/update_court/app/test_update_court_usecase.py +++ b/tests/modules/update_court/app/test_update_court_usecase.py @@ -2,7 +2,7 @@ from src.modules.update_court.app.update_court_usecase import UpdateCourtUsecase from src.shared.helpers.errors.domain_errors import EntityError from src.shared.infra.repositories.reservation_repository_mock import ReservationRepositoryMock -from src.shared.helpers.errors.usecase_errors import NoItemsFound +from src.shared.helpers.errors.usecase_errors import NoItemsFound, ForbiddenAction from src.shared.domain.enums.status_enum import STATUS @@ -16,7 +16,8 @@ def test_update_court_usecase(self): court = usecase( number=court_number, status=STATUS.MAINTENANCE, - photo="https://super.abril.com.br/mundo-estranho/os-poneis-sao-cavalos-anoes" + photo="https://super.abril.com.br/mundo-estranho/os-poneis-sao-cavalos-anoes", + role="ADMIN" ) assert repo.get_court(court_number).number == court.number @@ -32,7 +33,8 @@ def test_update_court_usecase_not_found_number(self): court = usecase( number=9, status=STATUS.MAINTENANCE, - photo="http://foto" + photo="http://foto", + role="ADMIN" ) def test_update_court_usecase_invalid_court_number(self): @@ -44,7 +46,8 @@ def test_update_court_usecase_invalid_court_number(self): court = usecase( number=-999, status=STATUS.MAINTENANCE, - photo="https://super.abril.com.br/mundo-estranho/os-poneis-sao-cavalos-anoes" + photo="https://super.abril.com.br/mundo-estranho/os-poneis-sao-cavalos-anoes", + role="ADMIN" ) def test_update_court_usecase_court_number_not_found(self): @@ -56,5 +59,19 @@ def test_update_court_usecase_court_number_not_found(self): court = usecase( number=9, status=STATUS.MAINTENANCE, - photo="https://super.abril.com.br/mundo-estranho/os-poneis-sao-cavalos-anoes" + photo="https://super.abril.com.br/mundo-estranho/os-poneis-sao-cavalos-anoes", + role="ADMIN" + ) + + def test_update_court_usecase_not_admin(self): + + repo = ReservationRepositoryMock() + usecase = UpdateCourtUsecase(repo=repo) + + with pytest.raises(ForbiddenAction): + court = usecase( + number=1, + status=STATUS.MAINTENANCE, + photo="https://super.abril.com.br/mundo-estranho/os-poneis-sao-cavalos-anoes", + role="STUDENT" ) diff --git a/tests/shared/domain/entities/test_booking.py b/tests/shared/domain/entities/test_booking.py index d508dae..dc7e366 100644 --- a/tests/shared/domain/entities/test_booking.py +++ b/tests/shared/domain/entities/test_booking.py @@ -2,6 +2,7 @@ from src.shared.domain.entities.booking import Booking from src.shared.domain.enums.sport import SPORT +from src.shared.domain.enums.type import BOOKING_TYPE from src.shared.helpers.errors.domain_errors import EntityError, EntityParameterOrderDatesError @@ -16,7 +17,8 @@ def test_booking(self): sport= SPORT.FOOTBALL, user_id= "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", booking_id= "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", - materials= ["ball"] + materials= ["ball"], + booking_type=BOOKING_TYPE.TRAINING ) assert type(booking) == Booking @@ -27,6 +29,7 @@ def test_booking(self): assert booking.user_id == "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5" assert booking.booking_id == "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1" assert booking.materials == ["ball"] + assert booking.booking_type.value == 'Training' def test_order_dates_incorrect(self): with pytest.raises(EntityParameterOrderDatesError): @@ -37,7 +40,8 @@ def test_order_dates_incorrect(self): sport= SPORT.FOOTBALL, user_id= "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", booking_id= "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", - materials= ["ball"] + materials= ["ball"], + booking_type=BOOKING_TYPE.TRAINING ) def test_invalid_dates(self): @@ -49,7 +53,8 @@ def test_invalid_dates(self): sport= SPORT.FOOTBALL, user_id= "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", booking_id= "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", - materials= ["ball"] + materials= ["ball"], + booking_type=BOOKING_TYPE.TRAINING ) def test_invalid_court_number(self): @@ -61,7 +66,8 @@ def test_invalid_court_number(self): sport= SPORT.FOOTBALL, user_id= "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", booking_id= "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", - materials= ["ball"] + materials= ["ball"], + booking_type=BOOKING_TYPE.TRAINING ) def test_invalid_sport(self): @@ -73,7 +79,8 @@ def test_invalid_sport(self): sport= "football", user_id= "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", booking_id= "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", - materials= ["ball"] + materials= ["ball"], + booking_type=BOOKING_TYPE.TRAINING ) def test_invalid_user_id(self): @@ -85,7 +92,8 @@ def test_invalid_user_id(self): sport= SPORT.FOOTBALL, user_id= "123", booking_id= "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", - materials= ["ball"] + materials= ["ball"], + booking_type=BOOKING_TYPE.TRAINING ) def test_invalid_booking_id(self): @@ -97,7 +105,8 @@ def test_invalid_booking_id(self): sport= SPORT.FOOTBALL, user_id= "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", booking_id= "123", - materials= ["ball"] + materials= ["ball"], + booking_type=BOOKING_TYPE.TRAINING ) def test_invalid_materials(self): @@ -109,7 +118,8 @@ def test_invalid_materials(self): sport= SPORT.FOOTBALL, user_id= "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", booking_id= "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", - materials= "ball" + materials= "ball", + booking_type=BOOKING_TYPE.TRAINING ) def test_invalid_materials2(self): @@ -121,7 +131,8 @@ def test_invalid_materials2(self): sport= SPORT.FOOTBALL, user_id= "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", booking_id= "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", - materials= [1] + materials= [1], + booking_type=BOOKING_TYPE.TRAINING ) def test_invalid_materials3(self): @@ -133,7 +144,8 @@ def test_invalid_materials3(self): sport= SPORT.FOOTBALL, user_id= "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", booking_id= "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", - materials= ["ball", 2] + materials= ["ball", 2], + booking_type=BOOKING_TYPE.TRAINING ) def test_booking_none_dates(self): @@ -145,7 +157,8 @@ def test_booking_none_dates(self): sport= SPORT.FOOTBALL, user_id= "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", booking_id= "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", - materials= ["ball"] + materials= ["ball"], + booking_type=BOOKING_TYPE.TRAINING ) def test_booking_none_court_number(self): @@ -157,7 +170,8 @@ def test_booking_none_court_number(self): sport= SPORT.FOOTBALL, user_id= "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", booking_id= "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", - materials= ["ball"] + materials= ["ball"], + booking_type=BOOKING_TYPE.TRAINING ) def test_booking_none_sport(self): @@ -169,7 +183,8 @@ def test_booking_none_sport(self): sport= None, user_id= "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", booking_id= "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", - materials= ["ball"] + materials= ["ball"], + booking_type=BOOKING_TYPE.TRAINING ) def test_booking_none_user_id(self): @@ -181,7 +196,8 @@ def test_booking_none_user_id(self): sport= SPORT.FOOTBALL, user_id= None, booking_id= "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", - materials= ["ball"] + materials= ["ball"], + booking_type=BOOKING_TYPE.TRAINING ) def test_booking_none_booking_id(self): @@ -193,7 +209,8 @@ def test_booking_none_booking_id(self): sport= SPORT.FOOTBALL, user_id= "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", booking_id= None, - materials= ["ball"] + materials= ["ball"], + booking_type=BOOKING_TYPE.TRAINING ) def test_booking_none_materials(self): @@ -205,29 +222,60 @@ def test_booking_none_materials(self): sport= SPORT.FOOTBALL, user_id= "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", booking_id= "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", - materials= None + materials= None, + booking_type=BOOKING_TYPE.TRAINING ) + def test_booking_with_invalid_type(self): + + with pytest.raises(EntityError): + Booking( + start_date= 1738940138, + end_date= 1838940138, + court_number= 1, + sport= SPORT.FOOTBALL, + user_id= "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", + booking_id= "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", + materials= ["ball"], + booking_type='invalid' + ) + + def test_booking_with_maintance_invalid_sport(self): + + with pytest.raises(EntityError): + Booking( + start_date= 1738940138, + end_date= 1838940138, + court_number= 1, + sport= SPORT.TENNIS, + user_id= "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", + booking_id= "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", + materials= ["ball"], + booking_type=BOOKING_TYPE.MAINTENCE + ) + def test_booking_to_dict(self): booking = Booking( start_date= 1738940138, end_date= 1838940138, court_number= 1, - sport= SPORT.FOOTBALL, + sport= SPORT.NA, user_id= "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", booking_id= "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", - materials= ["ball"] + materials= ["ball"], + booking_type=BOOKING_TYPE.MAINTENCE ) expected = { "start_date": 1738940138, "end_date": 1838940138, "court_number": 1, - "sport": "Football", + "sport": "NA", "user_id": "d3b07384-d9a1-4e8a-b3ef-4f1d2a87c6f5", "booking_id": "a1f5e2c3-7d8b-4c9e-b012-34f6a789d0e1", - "materials": ["ball"] + "materials": ["ball"], + "type": BOOKING_TYPE.MAINTENCE.value } assert booking.to_dict() == expected \ No newline at end of file diff --git a/tests/shared/infra/repositories/test_booking_repository_dynamo.py b/tests/shared/infra/repositories/test_booking_repository_dynamo.py index 59c0241..1a2a31d 100644 --- a/tests/shared/infra/repositories/test_booking_repository_dynamo.py +++ b/tests/shared/infra/repositories/test_booking_repository_dynamo.py @@ -5,13 +5,16 @@ from src.shared.domain.entities.booking import Booking class TestBookingRepositoryDynamo: - + @pytest.mark.skip("Can't run test in github actions") def test_dynamo_get_all_bookings(self): dynamo_repo = BookingRepositoryDynamo() mock_repo = BookingRepositoryMock() dynamo_bookings = dynamo_repo.get_all_bookings() mock_bookings = mock_repo.get_all_bookings() + + print( dynamo_bookings[0].start_date ) + assert len(dynamo_bookings) == len(mock_bookings) @@ -33,6 +36,9 @@ def test_dynamo_get_bookings_sport(self): assert len(dynamo_bookings) == len(mock_bookings) for d_booking, m_booking in zip(dynamo_bookings, mock_bookings): + + + assert d_booking.start_date == m_booking.start_date assert d_booking.end_date == m_booking.end_date assert d_booking.court_number == m_booking.court_number @@ -57,7 +63,6 @@ def test_get_bookings_by_user_id(self): for booking in dynamo_bookings: assert booking.user_id == test_user_id - @pytest.mark.skip("Can't run test in github actions") def test_get_bookings_by_court_number(self): diff --git a/tests/shared/infra/repositories/test_booking_repository_mock.py b/tests/shared/infra/repositories/test_booking_repository_mock.py index c42e320..8039bc2 100644 --- a/tests/shared/infra/repositories/test_booking_repository_mock.py +++ b/tests/shared/infra/repositories/test_booking_repository_mock.py @@ -1,3 +1,4 @@ +from src.shared.domain.enums.type import BOOKING_TYPE from src.shared.infra.repositories.booking_repository_mock import BookingRepositoryMock from src.shared.domain.enums.sport import SPORT from src.shared.domain.entities.booking import Booking @@ -6,7 +7,7 @@ class TestBookingRepositoryMock: def test_create_booking(self): repo_mock = BookingRepositoryMock() - new_booking = Booking(start_date=1634576165000, end_date=1634583365000, court_number=1,sport=SPORT.TENNIS, user_id='c8435c66-13a4-4641-9d54-773b4b8ccd09', booking_id='c2d3bebf-dc0d-4fc1-861c-506a40cc2036', materials=['Raquete', 'Bola', 'Rede', 'Tenis']) + new_booking = Booking(start_date=1634576165000, end_date=1634583365000, court_number=1,sport=SPORT.TENNIS, user_id='c8435c66-13a4-4641-9d54-773b4b8ccd09', booking_id='c2d3bebf-dc0d-4fc1-861c-506a40cc2036', materials=['Raquete', 'Bola', 'Rede', 'Tenis'], booking_type=BOOKING_TYPE.TRAINING) len_before = len(repo_mock.bookings) response = repo_mock.create_booking(new_booking) @@ -40,7 +41,7 @@ def test_get_booking(self): assert booking is not None assert booking.booking_id == booking_id - assert booking.user_id == 'c8435c66-13a4-4641-9d54-773b4b8ccc98' + assert booking.user_id == '1f25448b-3429-4c19-8287-d9e64f17bc3a' assert booking.sport == SPORT.TENNIS assert booking.court_number == 1 assert booking.start_date == 1634576165000 @@ -58,9 +59,14 @@ def test_get_all_bookings(self): def test_delete_booking(self): repo_mock = BookingRepositoryMock() booking_id = 'b1d3bebf-dc0d-4fc1-861c-506a40cc2925' + user = { + 'user_id': 'd351a9b1-937f-423c-a9d1-9929b5795be1', + 'email': 'user@email.com', + 'role': 'ADMIN' + } len_before = len(repo_mock.bookings) - deleted_booking = repo_mock.delete_booking(booking_id) + deleted_booking = repo_mock.delete_booking(booking_id, user) len_after = len(repo_mock.bookings) assert deleted_booking is not None diff --git a/tests/shared/infra/repositories/test_reservation_repository_dynamo.py b/tests/shared/infra/repositories/test_reservation_repository_dynamo.py index b486e55..e465106 100644 --- a/tests/shared/infra/repositories/test_reservation_repository_dynamo.py +++ b/tests/shared/infra/repositories/test_reservation_repository_dynamo.py @@ -10,7 +10,7 @@ class TestReservationRepositoryDynamo: - + @pytest.mark.skip("Can't run test in github actions") def test_dynamo_delete_court(self): dynamo_repo = ReservationRepositoryDynamo() @@ -30,7 +30,7 @@ def test_dynamo_delete_court_not_found(self): @pytest.mark.skip("Can't run test in github actions") def test_update_court(self): repo = ReservationRepositoryDynamo() - resp = repo.update_court(number=3, photo="https://www.linkedin.com/in/giovanna-albuquerque-16917a245/") + resp = repo.update_court(number=3, new_photo="https://www.linkedin.com/in/giovanna-albuquerque-16917a245/") assert resp.number == 3 assert resp.photo == "https://www.linkedin.com/in/giovanna-albuquerque-16917a245/" @@ -62,6 +62,6 @@ def test_dynamo_create_court(self): new_court = Court(number=6, status=STATUS.MAINTENANCE, is_field=False, photo=None) size = len(dynamo_repo.get_all_courts()) - assert new_court.__dict__ == dynamo_repo.create_court(new_court).__dict__ - assert dynamo_repo.get_court(6).__dict__ == new_court.__dict__ + assert new_court == dynamo_repo.create_court(new_court) + assert dynamo_repo.get_court(6) == new_court