diff --git a/api.saladeaula.digital/app/app.py b/api.saladeaula.digital/app/app.py index ca62fa4..5a3948a 100644 --- a/api.saladeaula.digital/app/app.py +++ b/api.saladeaula.digital/app/app.py @@ -68,7 +68,6 @@ def health(): @app.exception_handler(ServiceError) def exc_error(exc: ServiceError): logger.exception(exc) - return JSONResponse( body={ 'type': type(exc).__name__, diff --git a/api.saladeaula.digital/app/routes/orders/checkout.py b/api.saladeaula.digital/app/routes/orders/checkout.py index 92dfc58..6e5f3a5 100644 --- a/api.saladeaula.digital/app/routes/orders/checkout.py +++ b/api.saladeaula.digital/app/routes/orders/checkout.py @@ -1,13 +1,17 @@ import re from decimal import Decimal +from functools import reduce from http import HTTPStatus from typing import Any, Literal from uuid import uuid4 from aws_lambda_powertools.event_handler.api_gateway import Router -from layercake.dateutils import now -from layercake.dynamodb import DynamoDBPersistenceLayer -from layercake.extra_types import CnpjStr, CpfStr, NameStr +from aws_lambda_powertools.event_handler.exceptions import ( + NotFoundError, +) +from layercake.dateutils import now, ttl +from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair +from layercake.extra_types import CnpjStr, CpfStr, CreditCard, NameStr from pydantic import ( UUID4, BaseModel, @@ -22,12 +26,14 @@ from api_gateway import JSONResponse from boto3clients import dynamodb_client from config import ORDER_TABLE from routes.enrollments.enroll import Enrollment -from routes.orgs.address import address router = Router() dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) +class CouponNotFoundError(NotFoundError): ... + + class User(BaseModel): id: UUID4 | str name: NameStr @@ -71,13 +77,15 @@ class Checkout(BaseModel): address: Address payment_method: Literal['PIX', 'CREDIT_CARD', 'BANK_SLIP', 'MANUAL'] items: tuple[Item, ...] - enrollments: tuple[Enrollment, ...] | None = None + enrollments: tuple[Enrollment, ...] = tuple() coupon: Coupon | None = None org_id: UUID4 | str | None = None user_id: UUID4 | str | None = None cnpj: CnpjStr | None = None cpf: CpfStr | None = None created_by: User | None = None + credit_card: CreditCard | None = None + installments: int | None = Field(None, ge=1, le=12) @model_validator(mode='after') def verify_fields(self): @@ -99,7 +107,14 @@ class Checkout(BaseModel): def model_dump(self, **kwargs) -> dict[str, Any]: return super().model_dump( exclude_none=True, - exclude={'items', 'address', 'created_by'}, + exclude={ + 'items', + 'address', + 'created_by', + 'coupon', + 'credit_card', + 'enrollments', + }, **kwargs, ) @@ -107,28 +122,43 @@ class Checkout(BaseModel): @router.post('/') def checkout(payload: Checkout): now_ = now() - order_id = str(payload.id) + order_id = payload.id address = payload.address + credit_card = payload.credit_card + items = payload.items + enrollments = payload.enrollments coupon = payload.coupon + subtotal = _sum_items(items) + discount = ( + _apply_discount(subtotal, coupon.amount, coupon.type) * -1 + if coupon + else Decimal('0') + ) + total = subtotal + discount if subtotal > Decimal('0') else Decimal('0') with dyn.transact_writer() as transact: transact.put( item={ 'id': order_id, 'sk': '0', - 'total': '', - 'discount': '', + 'status': 'PENDING', + 'subtotal': subtotal, + 'total': total, + 'discount': discount, + # Post-migration (orders): rename `create_date` to `created_at` + 'create_date': now_, 'due_date': '', - 'created_at': now_, } | ({'coupon': coupon.code} if coupon else {}) + | ({'installments': payload.installments} if payload.installments else {}) | payload.model_dump() ) + transact.put( item={ 'id': order_id, 'sk': 'ITEMS', - 'items': [], + 'items': [item.model_dump() for item in items], 'created_at': now_, } ) @@ -141,14 +171,78 @@ def checkout(payload: Checkout): | address.model_dump() ) + if credit_card: + transact.put( + item={ + 'id': order_id, + 'sk': 'CREDIT_CARD', + 'ttl': ttl(start_dt=now_, minutes=5), + 'created_at': now_, + } + | credit_card.model_dump(), + ) + if coupon: transact.put( item={ 'id': order_id, - 'sk': 'COUPON', + 'sk': 'METADATA#COUPON', 'created_at': now_, } | coupon.model_dump() ) + transact.condition( + key=KeyPair('COUPON', coupon.code), + cond_expr='attribute_exists(sk) \ + AND discount_type = :type \ + AND discount_amount = :amount', + expr_attr_values={ + ':type': coupon.type, + ':amount': coupon.amount, + }, + exc_cls=CouponNotFoundError, + ) + + for enrollment in enrollments: + transact.put( + item={ + 'id': order_id, + 'sk': f'ENROLLMENT#{enrollment.id}', + 'status': 'UNPROCESSED', + 'created_at': now_, + } + | enrollment.model_dump(exclude={'id'}) + ) return JSONResponse(body={'id': order_id}, status_code=HTTPStatus.CREATED) + + +def _sum_items(items: tuple[Item, ...]): + def sum(total: Decimal, item: Item) -> Decimal: + return total + item.unit_price * item.quantity + + return reduce(sum, items, Decimal(0)) + + +def _calc_interest(total, installments: int) -> Decimal: + rate2to6 = 0.055 + rate7to12 = 0.0608 + rate = rate7to12 if installments >= 7 else rate2to6 + return total * Decimal((1 - 0.0382) / (1 - rate)) + + +def _apply_discount( + subtotal: Decimal, + discount_amount: Decimal, + discount_type: Literal['FIXED', 'PERCENT'], +) -> Decimal: + if subtotal <= Decimal('0'): + return Decimal('0') + + amount = ( + (subtotal * discount_amount) / Decimal('100') + if discount_type == 'PERCENT' + else discount_amount + ) + + return min(amount, subtotal) diff --git a/api.saladeaula.digital/template.yaml b/api.saladeaula.digital/template.yaml index 959b181..2aa74a2 100644 --- a/api.saladeaula.digital/template.yaml +++ b/api.saladeaula.digital/template.yaml @@ -26,7 +26,7 @@ Globals: Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:103 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:104 Environment: Variables: TZ: America/Sao_Paulo diff --git a/api.saladeaula.digital/tests/routes/orders/test_checkout.py b/api.saladeaula.digital/tests/routes/orders/test_checkout.py index 7fdfb3e..819f713 100644 --- a/api.saladeaula.digital/tests/routes/orders/test_checkout.py +++ b/api.saladeaula.digital/tests/routes/orders/test_checkout.py @@ -7,7 +7,7 @@ from layercake.dynamodb import DynamoDBPersistenceLayer, PartitionKey from ...conftest import HttpApiProxy, LambdaContext -def test_checkout( +def test_checkout_coupon( app, seeds, http_api_proxy: HttpApiProxy, @@ -23,7 +23,15 @@ def test_checkout( 'cnpj': '00000000000191', 'name': 'Branco do Brasil', 'email': 'bb@users.noreply.saladeaula.digital', - 'payment_method': 'BANK_SLIP', + 'payment_method': 'CREDIT_CARD', + 'installments': 12, + 'credit_card': { + 'holder_name': 'Sergio R Siqueira', + 'number': '4111111111111111', + 'exp_month': '01', + 'exp_year': '2026', + 'cvv': '123', + }, 'created_by': { 'id': '15bacf02-1535-4bee-9022-19d106fd7518', 'name': 'Sérgio R Siqueira', @@ -33,17 +41,59 @@ def test_checkout( 'postcode': '81280350', 'neighborhood': 'Cidade Industrial', 'address1': 'Rua Monsenhor Ivo Zanlorenzi', - 'address2': 'nº 5190, ap 1802', + 'address2': '5190, ap 1802', 'state': 'PR', }, 'items': [ { - 'id': 'e1c44881-2fe3-484e-ada2-12b6bf5b9398', - 'name': 'NR-35 Segurança nos Trabalhos em Altura', - 'quantity': 2, - 'unit_price': 119, - } + 'name': 'CIPA Grau de Risco 1', + 'id': '3c27ea9c-9464-46a1-9717-8c1441793186', + 'quantity': 1, + 'unit_price': 99, + }, + { + 'name': 'CIPA Grau de Risco 2', + 'id': '99bb3b60-4ded-4a8e-937c-ba2d78ec6454', + 'quantity': 1, + 'unit_price': 99, + }, ], + 'enrollments': [ + { + 'user': { + 'name': 'Sérgio Rafael de Siqueira', + 'cpf': '07879819908', + 'id': '5OxmMjL-ujoR5IMGegQz', + 'email': 'sergio@somosbeta.com.br', + }, + 'course': { + 'name': 'CIPA Grau de Risco 1', + 'id': '3c27ea9c-9464-46a1-9717-8c1441793186', + 'access_period': 365, + }, + 'id': '2f026d38-1edc-44ea-abf3-60c10bc58909', + }, + { + 'course': { + 'name': 'CIPA Grau de Risco 2', + 'id': '99bb3b60-4ded-4a8e-937c-ba2d78ec6454', + 'access_period': 365, + }, + 'scheduled_for': '2026-01-20', + 'id': '1f0931ad-7dd4-4ca1-bce2-a2e89efa5b56', + 'user': { + 'name': 'Maitê L Siqueira', + 'cpf': '02186829991', + 'id': '87606a7f-de56-4198-a91d-b6967499d382', + 'email': 'osergiosiqueira+maite@gmail.com', + }, + }, + ], + 'coupon': { + 'code': '10OFF', + 'type': 'PERCENT', + 'amount': 10, + }, }, ), lambda_context, @@ -56,42 +106,44 @@ def test_checkout( pprint(r['items']) -# def test_checkout_from_user( -# app, -# seeds, -# http_api_proxy: HttpApiProxy, -# dynamodb_persistence_layer: DynamoDBPersistenceLayer, -# lambda_context: LambdaContext, -# ): -# r = app.lambda_handler( -# http_api_proxy( -# raw_path='/orders', -# method=HTTPMethod.POST, -# body={ -# 'user_id': '15bacf02-1535-4bee-9022-19d106fd7518', -# 'cpf': '07879819908', -# 'name': 'Sérgio R Siqueira', -# 'email': 'sergio@somosbeta.com.br', -# 'payment_method': 'MANUAL', -# 'address': { -# 'city': 'Curitiba', -# 'postcode': '81280350', -# 'neighborhood': 'Cidade Industrial', -# 'address1': 'Rua Monsenhor Ivo Zanlorenzi', -# 'address2': 'nº 5190, ap 1802', -# 'state': 'PR', -# }, -# 'items': [ -# { -# 'id': 'e1c44881-2fe3-484e-ada2-12b6bf5b9398', -# 'name': 'NR-35 Segurança nos Trabalhos em Altura', -# 'quantity': 2, -# 'unit_price': 119, -# } -# ], -# }, -# ), -# lambda_context, -# ) -# print(r) -# assert r['statusCode'] == HTTPStatus.CREATED +def test_checkout_from_user( + app, + seeds, + http_api_proxy: HttpApiProxy, + dynamodb_persistence_layer: DynamoDBPersistenceLayer, + lambda_context: LambdaContext, +): + r = app.lambda_handler( + http_api_proxy( + raw_path='/orders', + method=HTTPMethod.POST, + body={ + 'user_id': '15bacf02-1535-4bee-9022-19d106fd7518', + 'cpf': '07879819908', + 'name': 'Sérgio R Siqueira', + 'email': 'sergio@somosbeta.com.br', + 'payment_method': 'MANUAL', + 'address': { + 'sk': 'METADATA#ADDRESS', + 'address1': 'Rua Monsenhor Ivo Zanlorenzi', + 'address2': '5190, ap 1802', + 'postcode': '81280350', + 'city': 'Curitiba', + 'neighborhood': 'Cidade Industrial', + 'state': 'PR', + }, + 'items': [ + { + 'id': 'e1c44881-2fe3-484e-ada2-12b6bf5b9398', + 'name': 'NR-35 Segurança nos Trabalhos em Altura', + 'quantity': 2, + 'unit_price': 119, + 'access_period': 20, + } + ], + }, + ), + lambda_context, + ) + print(r) + assert r['statusCode'] == HTTPStatus.CREATED diff --git a/api.saladeaula.digital/uv.lock b/api.saladeaula.digital/uv.lock index 7c08be1..0d7f83a 100644 --- a/api.saladeaula.digital/uv.lock +++ b/api.saladeaula.digital/uv.lock @@ -667,7 +667,7 @@ wheels = [ [[package]] name = "layercake" -version = "0.11.4" +version = "0.12.0" source = { directory = "../layercake" } dependencies = [ { name = "arnparse" }, diff --git a/apps/admin.saladeaula.digital/app/conf.ts b/apps/admin.saladeaula.digital/app/conf.ts index 2597a6d..03cbf50 100644 --- a/apps/admin.saladeaula.digital/app/conf.ts +++ b/apps/admin.saladeaula.digital/app/conf.ts @@ -1 +1,2 @@ export const TZ = 'America/Sao_Paulo' +export const INTERNAL_EMAIL_DOMAIN = 'users.noreply.saladeaula.digital' diff --git a/apps/admin.saladeaula.digital/app/middleware/workspace.ts b/apps/admin.saladeaula.digital/app/middleware/workspace.ts index a044f8c..ebfc795 100644 --- a/apps/admin.saladeaula.digital/app/middleware/workspace.ts +++ b/apps/admin.saladeaula.digital/app/middleware/workspace.ts @@ -14,6 +14,7 @@ export type Workspace = { id: string name: string cnpj: string + email: string } export type WorkspaceContextProps = { diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/route.tsx index 9bb615f..dffe854 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/route.tsx @@ -1,7 +1,7 @@ import type { Route } from './+types/route' -import { useFetcher } from 'react-router' -import { Link } from 'react-router' +import { useEffect, useState } from 'react' +import { useFetcher, Link } from 'react-router' import { useMount } from 'ahooks' import { BookSearchIcon, CircleCheckBigIcon, WalletIcon } from 'lucide-react' @@ -22,10 +22,14 @@ import { } from '@repo/ui/components/ui/breadcrumb' import { Switch } from '@repo/ui/components/ui/switch' import { createSearch } from '@repo/util/meili' -import { cloudflareContext } from '@repo/auth/context' +import { cloudflareContext, userContext } from '@repo/auth/context' import { Label } from '@repo/ui/components/ui/label' import { Skeleton } from '@repo/ui/components/skeleton' +import { request as req, HttpMethod } from '@repo/util/request' +import { INTERNAL_EMAIL_DOMAIN } from '@/conf' +import { workspaceContext } from '@/middleware/workspace' +import { useWorksapce } from '@/components/workspace-switcher' import { Step, StepItem, StepSeparator } from '@/components/step' import { Wizard, WizardStep } from '@/components/wizard' import type { Course } from '../_.$orgid.enrollments.add/data' @@ -33,10 +37,7 @@ import { Bulk } from './bulk' import { Payment } from './payment' import { Assigned } from './assigned' import { Review } from './review' - import { useWizardStore } from './store' -import { useEffect, useState } from 'react' -import { useWorksapce } from '@/components/workspace-switcher' export function meta({}: Route.MetaArgs) { return [{ title: 'Comprar matrículas' }] @@ -55,10 +56,36 @@ export async function loader({ context }: Route.LoaderArgs) { return { courses } } -export async function action({ request }: Route.ActionArgs) { +export async function action({ params, request, context }: Route.ActionArgs) { const body = (await request.json()) as object + const user = context.get(userContext)! + const { activeWorkspace } = context.get(workspaceContext) + const { id: org_id, name, cnpj } = activeWorkspace - console.log(body) + const r = await req({ + url: '/orders', + headers: new Headers({ 'Content-Type': 'application/json' }), + method: HttpMethod.POST, + body: JSON.stringify({ + org_id, + name, + cnpj, + email: `org+${cnpj}@${INTERNAL_EMAIL_DOMAIN}`, + created_by: { id: user.sub, name: user.name }, + ...body + }), + request, + context + }) + + if (!r.ok) { + const error = await r.json().catch(() => ({})) + return { ok: false, error } + } + + console.log(await r.json()) + + return { ok: true } } export default function Route({ @@ -71,7 +98,11 @@ export default function Route({ useWizardStore() const onSubmit = async () => { - await fetcher.submit(JSON.stringify(state), { + const items = state.items.map(({ course, quantity }) => ({ + ...course, + quantity + })) + await fetcher.submit(JSON.stringify({ ...state, items }), { method: 'post', encType: 'application/json' }) @@ -88,6 +119,10 @@ export default function Route({ } }, [address]) + useEffect(() => { + console.log(fetcher.data) + }, [fetcher.data]) + if (!mounted) { return } diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/store.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/store.tsx index 3729cc8..90aac54 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/store.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/store.tsx @@ -59,7 +59,7 @@ export const useWizardStore = create()( const subtotal = items.reduce( (acc, { course, quantity }) => acc + - (course?.unit_price || 0) * + (course.unit_price || 0) * (Number.isFinite(quantity) && quantity > 0 ? quantity : 1), 0 ) diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.users.add/data.ts b/apps/admin.saladeaula.digital/app/routes/_.$orgid.users.add/data.ts index 65b58ac..25f1d0e 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.users.add/data.ts +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.users.add/data.ts @@ -1,3 +1,4 @@ +import { INTERNAL_EMAIL_DOMAIN } from '@/conf' import { isValidCPF } from '@brazilian-utils/brazilian-utils' import { adjectives, @@ -17,7 +18,7 @@ function randomEmail() { separator: '-' }) - return `${randomName}@users.noreply.saladeaula.digital` + return `${randomName}@${INTERNAL_EMAIL_DOMAIN}` } export const formSchema = z diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid/route.tsx index ac9aad4..90418b0 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid/route.tsx @@ -2,7 +2,7 @@ import type { Route } from './+types/route' import * as cookie from 'cookie' import { Outlet, type ShouldRevalidateFunctionArgs } from 'react-router' -import { use, useEffect } from 'react' +import { useEffect } from 'react' import { SidebarInset, diff --git a/courses-events/template.yaml b/courses-events/template.yaml index 8b34927..f9dff61 100644 --- a/courses-events/template.yaml +++ b/courses-events/template.yaml @@ -14,7 +14,7 @@ Globals: Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:103 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:104 Environment: Variables: TZ: America/Sao_Paulo diff --git a/enrollments-events/template.yaml b/enrollments-events/template.yaml index d5c39e9..513fcd1 100644 --- a/enrollments-events/template.yaml +++ b/enrollments-events/template.yaml @@ -25,7 +25,7 @@ Globals: Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:103 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:104 Environment: Variables: TZ: America/Sao_Paulo diff --git a/id.saladeaula.digital/template.yaml b/id.saladeaula.digital/template.yaml index 981003b..1059180 100644 --- a/id.saladeaula.digital/template.yaml +++ b/id.saladeaula.digital/template.yaml @@ -14,7 +14,7 @@ Globals: Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:103 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:104 Environment: Variables: TZ: America/Sao_Paulo diff --git a/konviva-events/template.yaml b/konviva-events/template.yaml index b45531f..c298f5b 100644 --- a/konviva-events/template.yaml +++ b/konviva-events/template.yaml @@ -20,7 +20,7 @@ Globals: Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:103 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:104 Environment: Variables: TZ: America/Sao_Paulo diff --git a/layercake/layercake/extra_types.py b/layercake/layercake/extra_types.py index 6803e01..d42aacd 100644 --- a/layercake/layercake/extra_types.py +++ b/layercake/layercake/extra_types.py @@ -60,53 +60,25 @@ else: return field_schema -class PaymentCardValidation: - """ - >>> class CreditCard(BaseModel): - ... exp: PaymentCardValidation - - - >>> CreditCard(exp='20/23') - Traceback (most recent call last): - ... - pydantic_core._pydantic_core.ValidationError: 1 validation error for CreditCard - ... - - >>> CreditCard(exp='12/23') - CreditCard(exp=datetime.date(2023, 12, 1)) - - >>> CreditCard(exp='12/2024') - CreditCard(exp=datetime.date(2024, 12, 1)) - - >>> CreditCard(exp='2024-12-02') - CreditCard(exp=datetime.date(2024, 12, 2)) - """ - - @classmethod - def __get_pydantic_core_schema__( - cls, _source: type[Any], _handler: GetCoreSchemaHandler - ) -> CoreSchema: - return core_schema.no_info_after_validator_function( - cls._validate, core_schema.str_schema() - ) - - @classmethod - def _validate(cls, __input_value: str) -> date: - if '/' in __input_value: - month, year = __input_value.split('/') - return date(int(f'20{year[-2:]}'), int(month), 1) - - try: - return date.fromisoformat(__input_value) - except Exception: - raise ValueError('Invalid card expiration date.') - - class CreditCard(BaseModel): - name: NameStr + """ + >>> cc = CreditCard( + ... holder_name='Mike Shinoda', + ... number='4111111111111111', + ... cvv='123', + ... exp_month='01', + ... exp_year='2026' + ... ) + >>> str(cc.number.brand) + 'Visa' + >>> cc + CreditCard(holder_name='Mike Shinoda', number='4111111111111111', cvv='123', exp_month='01', exp_year='2026') + """ + holder_name: NameStr number: PaymentCardNumber cvv: str = Field(..., min_length=3) - exp: PaymentCardValidation + exp_month: str = Field(..., pattern=r'^\d{2}$') + exp_year: str = Field(..., pattern=r'^\d{4}$') @property def brand(self) -> str: @@ -118,12 +90,12 @@ class CreditCard(BaseModel): @property def first_name(self) -> str: - first_name, _ = self.name.split(' ', 1) + first_name, _ = self.holder_name.split(' ', 1) return first_name @property def last_name(self) -> str: - _, last_name = self.name.split(' ', 1) + _, last_name = self.holder_name.split(' ', 1) return last_name diff --git a/layercake/pyproject.toml b/layercake/pyproject.toml index 9bc953f..ba4a7c9 100644 --- a/layercake/pyproject.toml +++ b/layercake/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "layercake" -version = "0.11.4" +version = "0.12.0" description = "Packages shared dependencies to optimize deployment and ensure consistency across functions." readme = "README.md" authors = [ diff --git a/layercake/uv.lock b/layercake/uv.lock index c4371af..433e4ef 100644 --- a/layercake/uv.lock +++ b/layercake/uv.lock @@ -824,7 +824,7 @@ wheels = [ [[package]] name = "layercake" -version = "0.11.2" +version = "0.12.0" source = { editable = "." } dependencies = [ { name = "arnparse" }, diff --git a/orders-events/template.yaml b/orders-events/template.yaml index 2930f57..2d66168 100644 --- a/orders-events/template.yaml +++ b/orders-events/template.yaml @@ -26,7 +26,7 @@ Globals: Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:103 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:104 Environment: Variables: TZ: America/Sao_Paulo diff --git a/orders-events/tests/events/payments/__init__.py b/orders-events/tests/events/payments/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/orders-events/tests/events/payments/test_create_invoice.py b/orders-events/tests/events/payments/test_create_invoice.py new file mode 100644 index 0000000..1de3510 --- /dev/null +++ b/orders-events/tests/events/payments/test_create_invoice.py @@ -0,0 +1,21 @@ +from aws_lambda_powertools.utilities.typing.lambda_context import LambdaContext +from layercake.dynamodb import DynamoDBPersistenceLayer + +import events.payments.create_invoice as app + + +def test_create_invoice( + dynamodb_seeds, + dynamodb_persistence_layer: DynamoDBPersistenceLayer, + lambda_context: LambdaContext, +): + event = { + 'detail': { + 'new_image': { + 'id': '', + 'sk': '0' + } + } + } + + assert app.lambda_handler(event, lambda_context) # type: ignore diff --git a/streams-events/template.yaml b/streams-events/template.yaml index b3c72af..8a9679c 100644 --- a/streams-events/template.yaml +++ b/streams-events/template.yaml @@ -8,7 +8,7 @@ Globals: Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:103 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:104 Environment: Variables: LOG_LEVEL: DEBUG diff --git a/users-events/template.yaml b/users-events/template.yaml index 1b8859d..03db8e2 100644 --- a/users-events/template.yaml +++ b/users-events/template.yaml @@ -17,7 +17,7 @@ Globals: Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:103 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:104 Environment: Variables: TZ: America/Sao_Paulo