update orders

This commit is contained in:
2026-01-07 19:04:07 -03:00
parent 3f76273f83
commit d1fc6c602c
23 changed files with 305 additions and 129 deletions

View File

@@ -68,7 +68,6 @@ def health():
@app.exception_handler(ServiceError) @app.exception_handler(ServiceError)
def exc_error(exc: ServiceError): def exc_error(exc: ServiceError):
logger.exception(exc) logger.exception(exc)
return JSONResponse( return JSONResponse(
body={ body={
'type': type(exc).__name__, 'type': type(exc).__name__,

View File

@@ -1,13 +1,17 @@
import re import re
from decimal import Decimal from decimal import Decimal
from functools import reduce
from http import HTTPStatus from http import HTTPStatus
from typing import Any, Literal from typing import Any, Literal
from uuid import uuid4 from uuid import uuid4
from aws_lambda_powertools.event_handler.api_gateway import Router from aws_lambda_powertools.event_handler.api_gateway import Router
from layercake.dateutils import now from aws_lambda_powertools.event_handler.exceptions import (
from layercake.dynamodb import DynamoDBPersistenceLayer NotFoundError,
from layercake.extra_types import CnpjStr, CpfStr, NameStr )
from layercake.dateutils import now, ttl
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
from layercake.extra_types import CnpjStr, CpfStr, CreditCard, NameStr
from pydantic import ( from pydantic import (
UUID4, UUID4,
BaseModel, BaseModel,
@@ -22,12 +26,14 @@ from api_gateway import JSONResponse
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from config import ORDER_TABLE from config import ORDER_TABLE
from routes.enrollments.enroll import Enrollment from routes.enrollments.enroll import Enrollment
from routes.orgs.address import address
router = Router() router = Router()
dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
class CouponNotFoundError(NotFoundError): ...
class User(BaseModel): class User(BaseModel):
id: UUID4 | str id: UUID4 | str
name: NameStr name: NameStr
@@ -71,13 +77,15 @@ class Checkout(BaseModel):
address: Address address: Address
payment_method: Literal['PIX', 'CREDIT_CARD', 'BANK_SLIP', 'MANUAL'] payment_method: Literal['PIX', 'CREDIT_CARD', 'BANK_SLIP', 'MANUAL']
items: tuple[Item, ...] items: tuple[Item, ...]
enrollments: tuple[Enrollment, ...] | None = None enrollments: tuple[Enrollment, ...] = tuple()
coupon: Coupon | None = None coupon: Coupon | None = None
org_id: UUID4 | str | None = None org_id: UUID4 | str | None = None
user_id: UUID4 | str | None = None user_id: UUID4 | str | None = None
cnpj: CnpjStr | None = None cnpj: CnpjStr | None = None
cpf: CpfStr | None = None cpf: CpfStr | None = None
created_by: User | 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') @model_validator(mode='after')
def verify_fields(self): def verify_fields(self):
@@ -99,7 +107,14 @@ class Checkout(BaseModel):
def model_dump(self, **kwargs) -> dict[str, Any]: def model_dump(self, **kwargs) -> dict[str, Any]:
return super().model_dump( return super().model_dump(
exclude_none=True, exclude_none=True,
exclude={'items', 'address', 'created_by'}, exclude={
'items',
'address',
'created_by',
'coupon',
'credit_card',
'enrollments',
},
**kwargs, **kwargs,
) )
@@ -107,28 +122,43 @@ class Checkout(BaseModel):
@router.post('/') @router.post('/')
def checkout(payload: Checkout): def checkout(payload: Checkout):
now_ = now() now_ = now()
order_id = str(payload.id) order_id = payload.id
address = payload.address address = payload.address
credit_card = payload.credit_card
items = payload.items
enrollments = payload.enrollments
coupon = payload.coupon 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: with dyn.transact_writer() as transact:
transact.put( transact.put(
item={ item={
'id': order_id, 'id': order_id,
'sk': '0', 'sk': '0',
'total': '', 'status': 'PENDING',
'discount': '', 'subtotal': subtotal,
'total': total,
'discount': discount,
# Post-migration (orders): rename `create_date` to `created_at`
'create_date': now_,
'due_date': '', 'due_date': '',
'created_at': now_,
} }
| ({'coupon': coupon.code} if coupon else {}) | ({'coupon': coupon.code} if coupon else {})
| ({'installments': payload.installments} if payload.installments else {})
| payload.model_dump() | payload.model_dump()
) )
transact.put( transact.put(
item={ item={
'id': order_id, 'id': order_id,
'sk': 'ITEMS', 'sk': 'ITEMS',
'items': [], 'items': [item.model_dump() for item in items],
'created_at': now_, 'created_at': now_,
} }
) )
@@ -141,14 +171,78 @@ def checkout(payload: Checkout):
| address.model_dump() | 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: if coupon:
transact.put( transact.put(
item={ item={
'id': order_id, 'id': order_id,
'sk': 'COUPON', 'sk': 'METADATA#COUPON',
'created_at': now_, 'created_at': now_,
} }
| coupon.model_dump() | 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) 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)

View File

@@ -26,7 +26,7 @@ Globals:
Architectures: Architectures:
- x86_64 - x86_64
Layers: Layers:
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:103 - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:104
Environment: Environment:
Variables: Variables:
TZ: America/Sao_Paulo TZ: America/Sao_Paulo

View File

@@ -7,7 +7,7 @@ from layercake.dynamodb import DynamoDBPersistenceLayer, PartitionKey
from ...conftest import HttpApiProxy, LambdaContext from ...conftest import HttpApiProxy, LambdaContext
def test_checkout( def test_checkout_coupon(
app, app,
seeds, seeds,
http_api_proxy: HttpApiProxy, http_api_proxy: HttpApiProxy,
@@ -23,7 +23,15 @@ def test_checkout(
'cnpj': '00000000000191', 'cnpj': '00000000000191',
'name': 'Branco do Brasil', 'name': 'Branco do Brasil',
'email': 'bb@users.noreply.saladeaula.digital', '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': { 'created_by': {
'id': '15bacf02-1535-4bee-9022-19d106fd7518', 'id': '15bacf02-1535-4bee-9022-19d106fd7518',
'name': 'Sérgio R Siqueira', 'name': 'Sérgio R Siqueira',
@@ -33,17 +41,59 @@ def test_checkout(
'postcode': '81280350', 'postcode': '81280350',
'neighborhood': 'Cidade Industrial', 'neighborhood': 'Cidade Industrial',
'address1': 'Rua Monsenhor Ivo Zanlorenzi', 'address1': 'Rua Monsenhor Ivo Zanlorenzi',
'address2': '5190, ap 1802', 'address2': '5190, ap 1802',
'state': 'PR', 'state': 'PR',
}, },
'items': [ 'items': [
{ {
'id': 'e1c44881-2fe3-484e-ada2-12b6bf5b9398', 'name': 'CIPA Grau de Risco 1',
'name': 'NR-35 Segurança nos Trabalhos em Altura', 'id': '3c27ea9c-9464-46a1-9717-8c1441793186',
'quantity': 2, 'quantity': 1,
'unit_price': 119, '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, lambda_context,
@@ -56,42 +106,44 @@ def test_checkout(
pprint(r['items']) pprint(r['items'])
# def test_checkout_from_user( def test_checkout_from_user(
# app, app,
# seeds, seeds,
# http_api_proxy: HttpApiProxy, http_api_proxy: HttpApiProxy,
# dynamodb_persistence_layer: DynamoDBPersistenceLayer, dynamodb_persistence_layer: DynamoDBPersistenceLayer,
# lambda_context: LambdaContext, lambda_context: LambdaContext,
# ): ):
# r = app.lambda_handler( r = app.lambda_handler(
# http_api_proxy( http_api_proxy(
# raw_path='/orders', raw_path='/orders',
# method=HTTPMethod.POST, method=HTTPMethod.POST,
# body={ body={
# 'user_id': '15bacf02-1535-4bee-9022-19d106fd7518', 'user_id': '15bacf02-1535-4bee-9022-19d106fd7518',
# 'cpf': '07879819908', 'cpf': '07879819908',
# 'name': 'Sérgio R Siqueira', 'name': 'Sérgio R Siqueira',
# 'email': 'sergio@somosbeta.com.br', 'email': 'sergio@somosbeta.com.br',
# 'payment_method': 'MANUAL', 'payment_method': 'MANUAL',
# 'address': { 'address': {
# 'city': 'Curitiba', 'sk': 'METADATA#ADDRESS',
# 'postcode': '81280350', 'address1': 'Rua Monsenhor Ivo Zanlorenzi',
# 'neighborhood': 'Cidade Industrial', 'address2': '5190, ap 1802',
# 'address1': 'Rua Monsenhor Ivo Zanlorenzi', 'postcode': '81280350',
# 'address2': 'nº 5190, ap 1802', 'city': 'Curitiba',
# 'state': 'PR', 'neighborhood': 'Cidade Industrial',
# }, 'state': 'PR',
# 'items': [ },
# { 'items': [
# 'id': 'e1c44881-2fe3-484e-ada2-12b6bf5b9398', {
# 'name': 'NR-35 Segurança nos Trabalhos em Altura', 'id': 'e1c44881-2fe3-484e-ada2-12b6bf5b9398',
# 'quantity': 2, 'name': 'NR-35 Segurança nos Trabalhos em Altura',
# 'unit_price': 119, 'quantity': 2,
# } 'unit_price': 119,
# ], 'access_period': 20,
# }, }
# ), ],
# lambda_context, },
# ) ),
# print(r) lambda_context,
# assert r['statusCode'] == HTTPStatus.CREATED )
print(r)
assert r['statusCode'] == HTTPStatus.CREATED

View File

@@ -667,7 +667,7 @@ wheels = [
[[package]] [[package]]
name = "layercake" name = "layercake"
version = "0.11.4" version = "0.12.0"
source = { directory = "../layercake" } source = { directory = "../layercake" }
dependencies = [ dependencies = [
{ name = "arnparse" }, { name = "arnparse" },

View File

@@ -1 +1,2 @@
export const TZ = 'America/Sao_Paulo' export const TZ = 'America/Sao_Paulo'
export const INTERNAL_EMAIL_DOMAIN = 'users.noreply.saladeaula.digital'

View File

@@ -14,6 +14,7 @@ export type Workspace = {
id: string id: string
name: string name: string
cnpj: string cnpj: string
email: string
} }
export type WorkspaceContextProps = { export type WorkspaceContextProps = {

View File

@@ -1,7 +1,7 @@
import type { Route } from './+types/route' import type { Route } from './+types/route'
import { useFetcher } from 'react-router' import { useEffect, useState } from 'react'
import { Link } from 'react-router' import { useFetcher, Link } from 'react-router'
import { useMount } from 'ahooks' import { useMount } from 'ahooks'
import { BookSearchIcon, CircleCheckBigIcon, WalletIcon } from 'lucide-react' import { BookSearchIcon, CircleCheckBigIcon, WalletIcon } from 'lucide-react'
@@ -22,10 +22,14 @@ import {
} from '@repo/ui/components/ui/breadcrumb' } from '@repo/ui/components/ui/breadcrumb'
import { Switch } from '@repo/ui/components/ui/switch' import { Switch } from '@repo/ui/components/ui/switch'
import { createSearch } from '@repo/util/meili' 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 { Label } from '@repo/ui/components/ui/label'
import { Skeleton } from '@repo/ui/components/skeleton' 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 { Step, StepItem, StepSeparator } from '@/components/step'
import { Wizard, WizardStep } from '@/components/wizard' import { Wizard, WizardStep } from '@/components/wizard'
import type { Course } from '../_.$orgid.enrollments.add/data' import type { Course } from '../_.$orgid.enrollments.add/data'
@@ -33,10 +37,7 @@ import { Bulk } from './bulk'
import { Payment } from './payment' import { Payment } from './payment'
import { Assigned } from './assigned' import { Assigned } from './assigned'
import { Review } from './review' import { Review } from './review'
import { useWizardStore } from './store' import { useWizardStore } from './store'
import { useEffect, useState } from 'react'
import { useWorksapce } from '@/components/workspace-switcher'
export function meta({}: Route.MetaArgs) { export function meta({}: Route.MetaArgs) {
return [{ title: 'Comprar matrículas' }] return [{ title: 'Comprar matrículas' }]
@@ -55,10 +56,36 @@ export async function loader({ context }: Route.LoaderArgs) {
return { courses } 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 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({ export default function Route({
@@ -71,7 +98,11 @@ export default function Route({
useWizardStore() useWizardStore()
const onSubmit = async () => { 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', method: 'post',
encType: 'application/json' encType: 'application/json'
}) })
@@ -88,6 +119,10 @@ export default function Route({
} }
}, [address]) }, [address])
useEffect(() => {
console.log(fetcher.data)
}, [fetcher.data])
if (!mounted) { if (!mounted) {
return <Skeleton /> return <Skeleton />
} }

View File

@@ -59,7 +59,7 @@ export const useWizardStore = create<WizardStore>()(
const subtotal = items.reduce( const subtotal = items.reduce(
(acc, { course, quantity }) => (acc, { course, quantity }) =>
acc + acc +
(course?.unit_price || 0) * (course.unit_price || 0) *
(Number.isFinite(quantity) && quantity > 0 ? quantity : 1), (Number.isFinite(quantity) && quantity > 0 ? quantity : 1),
0 0
) )

View File

@@ -1,3 +1,4 @@
import { INTERNAL_EMAIL_DOMAIN } from '@/conf'
import { isValidCPF } from '@brazilian-utils/brazilian-utils' import { isValidCPF } from '@brazilian-utils/brazilian-utils'
import { import {
adjectives, adjectives,
@@ -17,7 +18,7 @@ function randomEmail() {
separator: '-' separator: '-'
}) })
return `${randomName}@users.noreply.saladeaula.digital` return `${randomName}@${INTERNAL_EMAIL_DOMAIN}`
} }
export const formSchema = z export const formSchema = z

View File

@@ -2,7 +2,7 @@ import type { Route } from './+types/route'
import * as cookie from 'cookie' import * as cookie from 'cookie'
import { Outlet, type ShouldRevalidateFunctionArgs } from 'react-router' import { Outlet, type ShouldRevalidateFunctionArgs } from 'react-router'
import { use, useEffect } from 'react' import { useEffect } from 'react'
import { import {
SidebarInset, SidebarInset,

View File

@@ -14,7 +14,7 @@ Globals:
Architectures: Architectures:
- x86_64 - x86_64
Layers: Layers:
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:103 - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:104
Environment: Environment:
Variables: Variables:
TZ: America/Sao_Paulo TZ: America/Sao_Paulo

View File

@@ -25,7 +25,7 @@ Globals:
Architectures: Architectures:
- x86_64 - x86_64
Layers: Layers:
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:103 - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:104
Environment: Environment:
Variables: Variables:
TZ: America/Sao_Paulo TZ: America/Sao_Paulo

View File

@@ -14,7 +14,7 @@ Globals:
Architectures: Architectures:
- x86_64 - x86_64
Layers: Layers:
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:103 - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:104
Environment: Environment:
Variables: Variables:
TZ: America/Sao_Paulo TZ: America/Sao_Paulo

View File

@@ -20,7 +20,7 @@ Globals:
Architectures: Architectures:
- x86_64 - x86_64
Layers: Layers:
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:103 - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:104
Environment: Environment:
Variables: Variables:
TZ: America/Sao_Paulo TZ: America/Sao_Paulo

View File

@@ -60,53 +60,25 @@ else:
return field_schema 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): 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 number: PaymentCardNumber
cvv: str = Field(..., min_length=3) 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 @property
def brand(self) -> str: def brand(self) -> str:
@@ -118,12 +90,12 @@ class CreditCard(BaseModel):
@property @property
def first_name(self) -> str: def first_name(self) -> str:
first_name, _ = self.name.split(' ', 1) first_name, _ = self.holder_name.split(' ', 1)
return first_name return first_name
@property @property
def last_name(self) -> str: def last_name(self) -> str:
_, last_name = self.name.split(' ', 1) _, last_name = self.holder_name.split(' ', 1)
return last_name return last_name

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "layercake" name = "layercake"
version = "0.11.4" version = "0.12.0"
description = "Packages shared dependencies to optimize deployment and ensure consistency across functions." description = "Packages shared dependencies to optimize deployment and ensure consistency across functions."
readme = "README.md" readme = "README.md"
authors = [ authors = [

2
layercake/uv.lock generated
View File

@@ -824,7 +824,7 @@ wheels = [
[[package]] [[package]]
name = "layercake" name = "layercake"
version = "0.11.2" version = "0.12.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "arnparse" }, { name = "arnparse" },

View File

@@ -26,7 +26,7 @@ Globals:
Architectures: Architectures:
- x86_64 - x86_64
Layers: Layers:
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:103 - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:104
Environment: Environment:
Variables: Variables:
TZ: America/Sao_Paulo TZ: America/Sao_Paulo

View File

@@ -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

View File

@@ -8,7 +8,7 @@ Globals:
Architectures: Architectures:
- x86_64 - x86_64
Layers: Layers:
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:103 - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:104
Environment: Environment:
Variables: Variables:
LOG_LEVEL: DEBUG LOG_LEVEL: DEBUG

View File

@@ -17,7 +17,7 @@ Globals:
Architectures: Architectures:
- x86_64 - x86_64
Layers: Layers:
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:103 - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:104
Environment: Environment:
Variables: Variables:
TZ: America/Sao_Paulo TZ: America/Sao_Paulo