add tests to canceled enrollment or scheduled with seat
This commit is contained in:
@@ -343,6 +343,7 @@ def _enroll_later(enrollment: Enrollment, context: Context):
|
|||||||
'user': user.model_dump(),
|
'user': user.model_dump(),
|
||||||
'course': course.model_dump(),
|
'course': course.model_dump(),
|
||||||
'org_name': org.name,
|
'org_name': org.name,
|
||||||
|
'enrollment_id': enrollment.id,
|
||||||
'created_by': {
|
'created_by': {
|
||||||
'id': created_by.id,
|
'id': created_by.id,
|
||||||
'name': created_by.name,
|
'name': created_by.name,
|
||||||
|
|||||||
@@ -162,9 +162,9 @@ const statuses: Record<string, { icon: LucideIcon; color?: string }> = {
|
|||||||
|
|
||||||
const labels: Record<string, string> = {
|
const labels: Record<string, string> = {
|
||||||
PENDING: 'Aguardando',
|
PENDING: 'Aguardando',
|
||||||
EXECUTED: 'Executado',
|
EXECUTED: 'Executada',
|
||||||
SCHEDULED: 'Agendado',
|
SCHEDULED: 'Agendada',
|
||||||
ROLLBACK: 'Revogado'
|
ROLLBACK: 'Revogada'
|
||||||
}
|
}
|
||||||
|
|
||||||
function Status({ status: s }: { status: string }) {
|
function Status({ status: s }: { status: string }) {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class Course(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Enrollment(BaseModel):
|
class Enrollment(BaseModel):
|
||||||
id: UUID4 | str = Field(default_factory=uuid4)
|
id: UUID4 | str
|
||||||
user: User
|
user: User
|
||||||
course: Course
|
course: Course
|
||||||
progress: int = Field(default=0, ge=0, le=100)
|
progress: int = Field(default=0, ge=0, le=100)
|
||||||
@@ -61,7 +61,7 @@ Org = TypedDict('Org', {'org_id': str, 'name': str})
|
|||||||
|
|
||||||
CreatedBy = TypedDict('CreatedBy', {'id': str, 'name': str})
|
CreatedBy = TypedDict('CreatedBy', {'id': str, 'name': str})
|
||||||
|
|
||||||
Seat = TypedDict('Seat', {'order_id': str})
|
Seat = TypedDict('Seat', {'order_id': str, 'enrollment_id': NotRequired[str]})
|
||||||
|
|
||||||
DeduplicationWindow = TypedDict('DeduplicationWindow', {'offset_days': int})
|
DeduplicationWindow = TypedDict('DeduplicationWindow', {'offset_days': int})
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
from aws_lambda_powertools import Logger
|
from aws_lambda_powertools import Logger
|
||||||
from aws_lambda_powertools.utilities.data_classes import (
|
from aws_lambda_powertools.utilities.data_classes import (
|
||||||
EventBridgeEvent,
|
EventBridgeEvent,
|
||||||
@@ -84,6 +86,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
|
|
||||||
def _handler(course: Course, context: dict) -> Enrollment:
|
def _handler(course: Course, context: dict) -> Enrollment:
|
||||||
enrollment = Enrollment(
|
enrollment = Enrollment(
|
||||||
|
id=uuid4(),
|
||||||
user=context['user'],
|
user=context['user'],
|
||||||
course=course,
|
course=course,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
from aws_lambda_powertools import Logger
|
from aws_lambda_powertools import Logger
|
||||||
from aws_lambda_powertools.utilities.data_classes import (
|
from aws_lambda_powertools.utilities.data_classes import (
|
||||||
@@ -37,8 +38,9 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
offset_days = old_image.get('dedup_window_offset_days')
|
offset_days = old_image.get('dedup_window_offset_days')
|
||||||
billing_day = old_image.get('subscription_billing_day')
|
billing_day = old_image.get('subscription_billing_day')
|
||||||
created_by = old_image.get('created_by')
|
created_by = old_image.get('created_by')
|
||||||
seat: Seat | None = old_image.get('seat')
|
seat: Seat = old_image.get('seat', {})
|
||||||
enrollment = Enrollment(
|
enrollment = Enrollment(
|
||||||
|
id=old_image['enrollment_id'],
|
||||||
course=old_image['course'],
|
course=old_image['course'],
|
||||||
user=old_image['user'],
|
user=old_image['user'],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
rollback_at = :now, \
|
rollback_at = :now, \
|
||||||
reason = :reason',
|
reason = :reason',
|
||||||
cond_expr='attribute_exists(sk) AND #status = :executed',
|
cond_expr='attribute_exists(sk) AND #status = :executed',
|
||||||
table_name=ORDER_TABLE,
|
|
||||||
expr_attr_names={
|
expr_attr_names={
|
||||||
'#status': 'status',
|
'#status': 'status',
|
||||||
},
|
},
|
||||||
@@ -47,6 +46,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
':reason': 'CANCELLATION',
|
':reason': 'CANCELLATION',
|
||||||
':now': now_,
|
':now': now_,
|
||||||
},
|
},
|
||||||
|
table_name=ORDER_TABLE,
|
||||||
)
|
)
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from aws_lambda_powertools import Logger
|
||||||
|
from aws_lambda_powertools.utilities.data_classes import (
|
||||||
|
EventBridgeEvent,
|
||||||
|
event_source,
|
||||||
|
)
|
||||||
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||||
|
from layercake.dateutils import now
|
||||||
|
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
||||||
|
|
||||||
|
from boto3clients import dynamodb_client
|
||||||
|
from config import ENROLLMENT_TABLE, ORDER_TABLE
|
||||||
|
|
||||||
|
logger = Logger(__name__)
|
||||||
|
dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
|
||||||
|
|
||||||
|
|
||||||
|
@event_source(data_class=EventBridgeEvent)
|
||||||
|
@logger.inject_lambda_context
|
||||||
|
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
||||||
|
old_image = event.detail['old_image']
|
||||||
|
order_id = old_image['seat']['order_id']
|
||||||
|
enrollment_id = old_image['enrollment_id']
|
||||||
|
*_, org_id = old_image['id'].split('#')
|
||||||
|
now_ = now()
|
||||||
|
|
||||||
|
with dyn.transact_writer() as transact:
|
||||||
|
transact.update(
|
||||||
|
key=KeyPair(
|
||||||
|
pk=order_id,
|
||||||
|
sk=f'ENROLLMENT#{enrollment_id}',
|
||||||
|
table_name=ORDER_TABLE,
|
||||||
|
),
|
||||||
|
cond_expr='attribute_exists(sk) AND #status = :scheduled',
|
||||||
|
update_expr='SET #status = :rollback, \
|
||||||
|
rollback_at = :now, \
|
||||||
|
reason = :reason',
|
||||||
|
expr_attr_names={
|
||||||
|
'#status': 'status',
|
||||||
|
},
|
||||||
|
expr_attr_values={
|
||||||
|
':rollback': 'ROLLBACK',
|
||||||
|
':scheduled': 'SCHEDULED',
|
||||||
|
':reason': 'CANCELLATION',
|
||||||
|
':now': now_,
|
||||||
|
},
|
||||||
|
table_name=ORDER_TABLE,
|
||||||
|
)
|
||||||
|
transact.put(
|
||||||
|
item={
|
||||||
|
'id': f'SEAT#ORG#{org_id}',
|
||||||
|
'sk': f'ORDER#{order_id}#ENROLLMENT#{uuid4()}',
|
||||||
|
'course': old_image['course'],
|
||||||
|
'created_at': now_,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
@@ -211,11 +211,14 @@ Resources:
|
|||||||
keys:
|
keys:
|
||||||
id:
|
id:
|
||||||
- prefix: SCHEDULED#ORG#
|
- prefix: SCHEDULED#ORG#
|
||||||
|
old_image:
|
||||||
|
enrollment_id:
|
||||||
|
- exists: true
|
||||||
|
|
||||||
EventReenrollIfFailedFunction:
|
EventReenrollOnFailedFunction:
|
||||||
Type: AWS::Serverless::Function
|
Type: AWS::Serverless::Function
|
||||||
Properties:
|
Properties:
|
||||||
Handler: events.reenroll_if_failed.lambda_handler
|
Handler: events.reenroll_on_failed.lambda_handler
|
||||||
LoggingConfig:
|
LoggingConfig:
|
||||||
LogGroup: !Ref EventLog
|
LogGroup: !Ref EventLog
|
||||||
Policies:
|
Policies:
|
||||||
@@ -240,10 +243,10 @@ Resources:
|
|||||||
old_image:
|
old_image:
|
||||||
status: [IN_PROGRESS]
|
status: [IN_PROGRESS]
|
||||||
|
|
||||||
EventRestoreSeatFunction:
|
EventRestoreSeatOnCanceledFunction:
|
||||||
Type: AWS::Serverless::Function
|
Type: AWS::Serverless::Function
|
||||||
Properties:
|
Properties:
|
||||||
Handler: events.restore_seat.lambda_handler
|
Handler: events.restore_seat_on_canceled.lambda_handler
|
||||||
LoggingConfig:
|
LoggingConfig:
|
||||||
LogGroup: !Ref EventLog
|
LogGroup: !Ref EventLog
|
||||||
Policies:
|
Policies:
|
||||||
@@ -265,6 +268,32 @@ Resources:
|
|||||||
order_id:
|
order_id:
|
||||||
- exists: true
|
- exists: true
|
||||||
|
|
||||||
|
EventRestoreSeatOnScheduledCanceledFunction:
|
||||||
|
Type: AWS::Serverless::Function
|
||||||
|
Properties:
|
||||||
|
Handler: events.restore_seat_on_scheduled_canceled.lambda_handler
|
||||||
|
LoggingConfig:
|
||||||
|
LogGroup: !Ref EventLog
|
||||||
|
Policies:
|
||||||
|
- DynamoDBCrudPolicy:
|
||||||
|
TableName: !Ref EnrollmentTable
|
||||||
|
- DynamoDBCrudPolicy:
|
||||||
|
TableName: !Ref OrderTable
|
||||||
|
Events:
|
||||||
|
DynamoDBEvent:
|
||||||
|
Type: EventBridgeRule
|
||||||
|
Properties:
|
||||||
|
Pattern:
|
||||||
|
resources: [!Ref EnrollmentTable]
|
||||||
|
detail-type: [REMOVE]
|
||||||
|
detail:
|
||||||
|
old_image:
|
||||||
|
id:
|
||||||
|
- prefix: SCHEDULED#ORG#
|
||||||
|
seat:
|
||||||
|
order_id:
|
||||||
|
- exists: true
|
||||||
|
|
||||||
# DEPRECATED
|
# DEPRECATED
|
||||||
EventAllocateSlotsFunction:
|
EventAllocateSlotsFunction:
|
||||||
Type: AWS::Serverless::Function
|
Type: AWS::Serverless::Function
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from aws_lambda_powertools.utilities.typing import LambdaContext
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||||
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
||||||
|
|
||||||
import events.reenroll_if_failed as app
|
import events.reenroll_on_failed as app
|
||||||
|
|
||||||
|
|
||||||
def test_reenroll_custom_dedup_window(
|
def test_reenroll_custom_dedup_window(
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||||
|
from layercake.dynamodb import DynamoDBPersistenceLayer
|
||||||
|
|
||||||
|
import events.restore_seat_on_canceled as app
|
||||||
|
|
||||||
|
|
||||||
|
def test_restore_seat_on_canceled(
|
||||||
|
dynamodb_seeds,
|
||||||
|
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||||
|
lambda_context: LambdaContext,
|
||||||
|
):
|
||||||
|
event = {
|
||||||
|
'detail': {
|
||||||
|
'old_image': {
|
||||||
|
'id': '',
|
||||||
|
'seat': {'order_id': ''},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert app.lambda_handler(event, lambda_context) # type: ignore
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||||
|
from layercake.dynamodb import DynamoDBPersistenceLayer, PartitionKey
|
||||||
|
|
||||||
|
import events.restore_seat_on_scheduled_canceled as app
|
||||||
|
|
||||||
|
|
||||||
|
def test_restore_seat_on_scheduled_canceled(
|
||||||
|
dynamodb_seeds,
|
||||||
|
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||||
|
lambda_context: LambdaContext,
|
||||||
|
):
|
||||||
|
org_id = 'cJtK9SsnJhKPyxESe7g3DG'
|
||||||
|
event = {
|
||||||
|
'detail': {
|
||||||
|
'old_image': {
|
||||||
|
'seat': {
|
||||||
|
'order_id': 'f1ecaa69-8054-4cdc-ba13-a6680e18df21',
|
||||||
|
},
|
||||||
|
'enrollment_id': '19c0aa75-473e-4d4c-822d-2d42d46d2167',
|
||||||
|
'course': {
|
||||||
|
'name': 'Gestão da Cultura de Segurança',
|
||||||
|
'id': 'c19cd7ee-3cc8-4f9c-95ff-dad7993f49b1',
|
||||||
|
'access_period': 365,
|
||||||
|
},
|
||||||
|
'org_name': 'Beta Educação',
|
||||||
|
'user': {
|
||||||
|
'name': 'Sérgio Rafael de Siqueira',
|
||||||
|
'cpf': '07879819908',
|
||||||
|
'id': '5OxmMjL-ujoR5IMGegQz',
|
||||||
|
'email': 'sergio@somosbeta.com.br',
|
||||||
|
},
|
||||||
|
'ttl': 1769828760,
|
||||||
|
'sk': '2026-01-31T00:00:00-03:06#addf2b5f2cbf30080df8582e6a95eb96',
|
||||||
|
'id': f'SCHEDULED#ORG#{org_id}',
|
||||||
|
'scheduled_at': '2026-01-25T14:58:09.772660-03:00',
|
||||||
|
'created_by': {
|
||||||
|
'name': 'Sérgio Rafael de Siqueira',
|
||||||
|
'id': '5OxmMjL-ujoR5IMGegQz',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert app.lambda_handler(event, lambda_context) # type: ignore
|
||||||
|
r = dynamodb_persistence_layer.collection.query(PartitionKey(f'SEAT#ORG#{org_id}'))
|
||||||
|
assert len(r['items'])
|
||||||
@@ -47,4 +47,7 @@
|
|||||||
{"id": "00237409-9384-4692-9be5-b4443a41e1c4", "sk": "admins#1234", "email": "sergio@somosbeta.com.br", "name": "Sérgio R Siqueira"}
|
{"id": "00237409-9384-4692-9be5-b4443a41e1c4", "sk": "admins#1234", "email": "sergio@somosbeta.com.br", "name": "Sérgio R Siqueira"}
|
||||||
|
|
||||||
// file: tests/events/test_reenroll_if_failed.py::test_reenroll_custom_dedup_window
|
// file: tests/events/test_reenroll_if_failed.py::test_reenroll_custom_dedup_window
|
||||||
{"id": "SUBSCRIPTION", "sk": "ORG#123"}
|
{"id": "SUBSCRIPTION", "sk": "ORG#123"}
|
||||||
|
|
||||||
|
// file: tests/events/test_restore_seat_on_scheduled_canceled.py
|
||||||
|
{"id": "f1ecaa69-8054-4cdc-ba13-a6680e18df21", "sk": "ENROLLMENT#19c0aa75-473e-4d4c-822d-2d42d46d2167", "status": "SCHEDULED"}
|
||||||
@@ -352,6 +352,7 @@ def _enroll_later(enrollment: Enrollment, context: Context) -> None:
|
|||||||
'user': user.model_dump(),
|
'user': user.model_dump(),
|
||||||
'course': course.model_dump(),
|
'course': course.model_dump(),
|
||||||
'org_name': org.name,
|
'org_name': org.name,
|
||||||
|
'enrollment_id': enrollment.id,
|
||||||
'created_by': {
|
'created_by': {
|
||||||
'id': created_by['user_id'],
|
'id': created_by['user_id'],
|
||||||
'name': created_by['name'],
|
'name': created_by['name'],
|
||||||
|
|||||||
Reference in New Issue
Block a user