From 753c4be4da7a2193795c710dac84bdaf72b67554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Rafael=20Siqueira?= Date: Fri, 16 Jan 2026 19:47:41 -0300 Subject: [PATCH] add subscription test --- api.saladeaula.digital/app/app.py | 1 + api.saladeaula.digital/app/exceptions.py | 6 ++ .../app/routes/orgs/__init__.py | 2 + .../app/routes/orgs/admins.py | 8 +-- .../app/routes/orgs/billing.py | 15 +--- .../app/routes/orgs/subscription.py | 71 +++++++++++++++++++ .../app/routes/orgs/users/add.py | 4 +- .../tests/routes/orgs/test_subscription.py | 58 +++++++++++++++ 8 files changed, 142 insertions(+), 23 deletions(-) create mode 100644 api.saladeaula.digital/app/routes/orgs/subscription.py create mode 100644 api.saladeaula.digital/tests/routes/orgs/test_subscription.py diff --git a/api.saladeaula.digital/app/app.py b/api.saladeaula.digital/app/app.py index dee4d8e..ffb5932 100644 --- a/api.saladeaula.digital/app/app.py +++ b/api.saladeaula.digital/app/app.py @@ -57,6 +57,7 @@ app.include_router(orgs.billing, prefix='/orgs') app.include_router(orgs.custom_pricing, prefix='/orgs') app.include_router(orgs.scheduled, prefix='/orgs') app.include_router(orgs.submissions, prefix='/orgs') +app.include_router(orgs.subscription, prefix='/orgs') app.include_router(orgs.seats, prefix='/orgs') app.include_router(orgs.users, prefix='/orgs') app.include_router(orgs.batch_jobs, prefix='/orgs') diff --git a/api.saladeaula.digital/app/exceptions.py b/api.saladeaula.digital/app/exceptions.py index 4c22f80..07a76b1 100644 --- a/api.saladeaula.digital/app/exceptions.py +++ b/api.saladeaula.digital/app/exceptions.py @@ -11,6 +11,12 @@ class ConflictError(ServiceError): super().__init__(HTTPStatus.CONFLICT, msg) +class OrgNotFoundError(NotFoundError): ... + + +class MemberNotFoundError(NotFoundError): ... + + class OrderNotFoundError(NotFoundError): ... diff --git a/api.saladeaula.digital/app/routes/orgs/__init__.py b/api.saladeaula.digital/app/routes/orgs/__init__.py index ddec51c..d2056a0 100644 --- a/api.saladeaula.digital/app/routes/orgs/__init__.py +++ b/api.saladeaula.digital/app/routes/orgs/__init__.py @@ -6,6 +6,7 @@ from .custom_pricing import router as custom_pricing from .enrollments.scheduled import router as scheduled from .enrollments.submissions import router as submissions from .seats import router as seats +from .subscription import router as subscription from .users.add import router as users from .users.batch_jobs import router as batch_jobs @@ -18,6 +19,7 @@ __all__ = [ 'scheduled', 'submissions', 'seats', + 'subscription', 'users', 'batch_jobs', ] diff --git a/api.saladeaula.digital/app/routes/orgs/admins.py b/api.saladeaula.digital/app/routes/orgs/admins.py index 8b49634..b2157eb 100644 --- a/api.saladeaula.digital/app/routes/orgs/admins.py +++ b/api.saladeaula.digital/app/routes/orgs/admins.py @@ -2,7 +2,6 @@ from http import HTTPStatus from typing import Annotated from aws_lambda_powertools.event_handler.api_gateway import Router -from aws_lambda_powertools.event_handler.exceptions import NotFoundError from aws_lambda_powertools.event_handler.openapi.params import Body from layercake.dateutils import now from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair @@ -12,17 +11,12 @@ from pydantic import UUID4, BaseModel, EmailStr from api_gateway import JSONResponse from boto3clients import dynamodb_client from config import USER_TABLE +from exceptions import MemberNotFoundError, OrgNotFoundError router = Router() dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) -class OrgNotFoundError(NotFoundError): ... - - -class MemberNotFoundError(NotFoundError): ... - - @router.get('//admins') def admins(org_id: str): return dyn.collection.query( diff --git a/api.saladeaula.digital/app/routes/orgs/billing.py b/api.saladeaula.digital/app/routes/orgs/billing.py index 2235fc4..56643d8 100644 --- a/api.saladeaula.digital/app/routes/orgs/billing.py +++ b/api.saladeaula.digital/app/routes/orgs/billing.py @@ -7,7 +7,7 @@ from aws_lambda_powertools.event_handler.openapi.params import Query from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair from boto3clients import dynamodb_client -from config import ORDER_TABLE, USER_TABLE +from config import ORDER_TABLE router = Router() dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) @@ -16,19 +16,6 @@ dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) class BillingNotFoundError(NotFoundError): ... -@router.get('//subscription') -def subscription(org_id: str): - return dyn.collection.get_item( - KeyPair( - pk=org_id, - sk='METADATA#SUBSCRIPTION', - table_name=USER_TABLE, - ), - raise_on_error=False, - default={}, - ) - - @router.get('//billing') def billing( org_id: str, diff --git a/api.saladeaula.digital/app/routes/orgs/subscription.py b/api.saladeaula.digital/app/routes/orgs/subscription.py new file mode 100644 index 0000000..c07341a --- /dev/null +++ b/api.saladeaula.digital/app/routes/orgs/subscription.py @@ -0,0 +1,71 @@ +from enum import Enum +from http import HTTPStatus +from typing import Annotated + +from aws_lambda_powertools.event_handler.api_gateway import Router +from aws_lambda_powertools.event_handler.openapi.params import Body +from layercake.dateutils import now +from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair + +from api_gateway import JSONResponse +from boto3clients import dynamodb_client +from config import USER_TABLE +from exceptions import OrgNotFoundError + +router = Router() +dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) + + +class PaymentMethod(str, Enum): + PIX = 'PIX' + BANK_SLIP = 'BANK_SLIP' + MANUAL = 'MANUAL' + + +@router.get('//subscription') +def subscription(org_id: str): + return dyn.collection.get_item( + KeyPair( + pk=org_id, + sk='METADATA#SUBSCRIPTION', + ), + raise_on_error=False, + default={}, + ) + + +@router.post('//subscription') +def add( + org_id: str, + name: Annotated[str, Body(embed=True)], + billing_day: Annotated[int, Body(embed=True, ge=1)], + payment_method: Annotated[PaymentMethod, Body(embed=True)], +): + now_ = now() + + with dyn.transact_writer() as transact: + transact.condition( + key=KeyPair(org_id, '0'), + cond_expr='attribute_exists(sk)', + exc_cls=OrgNotFoundError, + ) + transact.put( + item={ + 'id': 'SUBSCRIPTION', + 'sk': f'ORG#{org_id}', + 'name': name, + 'created_at': now_, + }, + cond_expr='attribute_not_exists(sk)', + ) + transact.put( + item={ + 'id': org_id, + 'sk': 'METADATA#SUBSCRIPTION', + 'billing_day': billing_day, + 'payment_method': payment_method.value, + 'created_at': now_, + } + ) + + return JSONResponse(status_code=HTTPStatus.CREATED) diff --git a/api.saladeaula.digital/app/routes/orgs/users/add.py b/api.saladeaula.digital/app/routes/orgs/users/add.py index dcb8b38..4abe527 100644 --- a/api.saladeaula.digital/app/routes/orgs/users/add.py +++ b/api.saladeaula.digital/app/routes/orgs/users/add.py @@ -3,7 +3,6 @@ from typing import Annotated from uuid import uuid4 from aws_lambda_powertools.event_handler.api_gateway import Router -from aws_lambda_powertools.event_handler.exceptions import NotFoundError from aws_lambda_powertools.event_handler.openapi.params import Body from layercake.dateutils import now, ttl from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey @@ -16,6 +15,7 @@ from config import INTERNAL_EMAIL_DOMAIN, USER_TABLE from exceptions import ( CPFConflictError, EmailConflictError, + OrgNotFoundError, UserConflictError, UserNotFoundError, ) @@ -37,7 +37,7 @@ class User(BaseModel): email: EmailStr -class OrgNotFoundError(NotFoundError): ... +# class OrgNotFoundError(NotFoundError): ... @router.post('//users') diff --git a/api.saladeaula.digital/tests/routes/orgs/test_subscription.py b/api.saladeaula.digital/tests/routes/orgs/test_subscription.py new file mode 100644 index 0000000..1c47568 --- /dev/null +++ b/api.saladeaula.digital/tests/routes/orgs/test_subscription.py @@ -0,0 +1,58 @@ +from http import HTTPMethod, HTTPStatus + +from layercake.dynamodb import ( + DynamoDBPersistenceLayer, + KeyPair, +) + +from ...conftest import HttpApiProxy, LambdaContext + + +def test_subscription( + app, + seeds, + http_api_proxy: HttpApiProxy, + dynamodb_persistence_layer: DynamoDBPersistenceLayer, + lambda_context: LambdaContext, +): + r = app.lambda_handler( + http_api_proxy( + raw_path='/orgs/2a8963fc-4694-4fe2-953a-316d1b10f1f5/subscription', + method=HTTPMethod.GET, + ), + lambda_context, + ) + assert r['statusCode'] == HTTPStatus.OK + + +def test_add_subscription( + app, + seeds, + http_api_proxy: HttpApiProxy, + dynamodb_persistence_layer: DynamoDBPersistenceLayer, + lambda_context: LambdaContext, +): + org_id = 'f6000f79-6e5c-49a0-952f-3bda330ef278' + r = app.lambda_handler( + http_api_proxy( + raw_path=f'/orgs/{org_id}/subscription', + method=HTTPMethod.POST, + body={ + 'name': 'Banco do Brasil', + 'billing_day': 1, + 'payment_method': 'MANUAL', + }, + ), + lambda_context, + ) + assert r['statusCode'] == HTTPStatus.CREATED + + r = dynamodb_persistence_layer.collection.get_items( + KeyPair(org_id, '0') + + KeyPair(org_id, 'METADATA#SUBSCRIPTION', rename_key='metadata') + + KeyPair('SUBSCRIPTION', f'ORG#{org_id}', rename_key='subscription') + ) + + assert r['metadata']['billing_day'] == 1 + assert r['metadata']['payment_method'] == 'MANUAL' + assert r['subscription']['name'] == 'Banco do Brasil'