This commit is contained in:
2025-08-05 21:14:09 -03:00
parent 5c57da7ecb
commit f96ad67eeb
27 changed files with 1960 additions and 0 deletions

View File

View File

@@ -0,0 +1,148 @@
import base64
import json
import os
from dataclasses import dataclass
from http import HTTPMethod
import jsonlines
import pytest
PYTEST_TABLE_NAME = 'pytest'
PK = 'id'
SK = 'sk'
# https://docs.pytest.org/en/7.1.x/reference/reference.html#pytest.hookspec.pytest_configure
def pytest_configure():
os.environ['TZ'] = 'America/Sao_Paulo'
os.environ['OAUTH2_TABLE'] = PYTEST_TABLE_NAME
os.environ['JWT_SECRET'] = 'secret'
os.environ['DYNAMODB_PARTITION_KEY'] = PK
os.environ['DYNAMODB_SORT_KEY'] = SK
@dataclass
class LambdaContext:
function_name: str = 'test'
memory_limit_in_mb: int = 128
invoked_function_arn: str = 'arn:aws:lambda:eu-west-1:809313241:function:test'
aws_request_id: str = '52fdfc07-2182-154f-163f-5f0f9a621d72'
class HttpApiProxy:
def __call__(
self,
raw_path: str,
method: str = HTTPMethod.GET,
body: dict | str | None = None,
*,
headers: dict = {},
cookies: dict = {},
query_string_parameters: dict = {},
is_base64_encoded: bool = True,
**kwargs,
) -> dict:
return {
'version': '2.0',
'routeKey': '$default',
'rawPath': raw_path,
'rawQueryString': 'parameter1=value1&parameter1=value2&parameter2=value',
'cookies': cookies,
'headers': headers,
'queryStringParameters': query_string_parameters,
'requestContext': {
'accountId': '123456789012',
'apiId': 'api-id',
'authorizer': {},
'domainName': 'id.execute-api.us-east-1.amazonaws.com',
'domainPrefix': 'id',
'http': {
'method': str(method),
'path': raw_path,
'protocol': 'HTTP/1.1',
'sourceIp': '192.168.0.1/32',
'userAgent': 'agent',
},
'requestId': 'id',
'routeKey': '$default',
'stage': '$default',
'time': '12/Mar/2020:19:03:58 +0000',
'timeEpoch': 1583348638390,
},
'body': _base64_dict(body) if isinstance(body, dict) else body,
'pathParameters': {'parameter1': 'value1'},
'isBase64Encoded': is_base64_encoded,
'stageVariables': {'stageVariable1': 'value1', 'stageVariable2': 'value2'},
}
def _base64_dict(obj: dict = {}) -> str | None:
if not obj:
return None
return base64.b64encode(json.dumps(obj).encode()).decode()
@pytest.fixture
def lambda_context() -> LambdaContext:
return LambdaContext()
@pytest.fixture
def http_api_proxy():
return HttpApiProxy()
@pytest.fixture
def dynamodb_client():
from boto3clients import dynamodb_client as client
try:
client.create_table(
AttributeDefinitions=[
{'AttributeName': PK, 'AttributeType': 'S'},
{'AttributeName': SK, 'AttributeType': 'S'},
],
TableName=PYTEST_TABLE_NAME,
KeySchema=[
{'AttributeName': PK, 'KeyType': 'HASH'},
{'AttributeName': SK, 'KeyType': 'RANGE'},
],
ProvisionedThroughput={
'ReadCapacityUnits': 123,
'WriteCapacityUnits': 123,
},
)
except Exception:
pass
yield client
client.delete_table(TableName=PYTEST_TABLE_NAME)
@pytest.fixture()
def dynamodb_persistence_layer(dynamodb_client):
from layercake.dynamodb import DynamoDBPersistenceLayer
return DynamoDBPersistenceLayer(PYTEST_TABLE_NAME, dynamodb_client)
@pytest.fixture()
def dynamodb_seeds(dynamodb_client):
from layercake.dynamodb import serialize
with open('tests/seeds.jsonl', 'rb') as fp:
reader = jsonlines.Reader(fp)
for line in reader.iter(type=dict, skip_invalid=True):
dynamodb_client.put_item(
TableName=PYTEST_TABLE_NAME,
Item=serialize(line),
)
@pytest.fixture
def mock_app():
import app
return app

View File

@@ -0,0 +1,45 @@
from http import HTTPMethod
from layercake.dynamodb import DynamoDBPersistenceLayer
from ..conftest import HttpApiProxy, LambdaContext
def test_authorize(
mock_app,
dynamodb_seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
client_id = 'd72d4005-1fa7-4430-9754-80d5e2487bb6'
r = mock_app.lambda_handler(
http_api_proxy(
raw_path='/authorize',
method=HTTPMethod.GET,
query_string_parameters={
'response_type': 'code',
'client_id': client_id,
'redirect_uri': 'https://localhost/callback',
'scope': 'openid',
'nonce': '123',
},
),
lambda_context,
)
assert 'Location' in r['headers']
r = dynamodb_persistence_layer.query(
key_cond_expr='#pk = :pk AND begins_with(#sk, :sk)',
expr_attr_name={
'#pk': 'id',
'#sk': 'sk',
},
expr_attr_values={
':pk': f'OAUTH2_CODE#CLIENT_ID#{client_id}',
':sk': 'CODE',
},
)
assert len(r['items']) == 2

View File

@@ -0,0 +1,42 @@
from http import HTTPMethod
from ..conftest import HttpApiProxy, LambdaContext
def test_html_page(
mock_app,
dynamodb_seeds,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
r = mock_app.lambda_handler(
http_api_proxy(
raw_path='/login',
method=HTTPMethod.GET,
),
lambda_context,
)
print(r)
def test_login(
mock_app,
dynamodb_seeds,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
r = mock_app.lambda_handler(
http_api_proxy(
raw_path='/login',
method=HTTPMethod.POST,
headers={
'Content-Type': 'application/x-www-form-urlencoded',
},
body='username=sergio@somosbeta.com.br&password=pytest@123',
is_base64_encoded=False,
),
lambda_context,
)
print(r)

View File

@@ -0,0 +1,7 @@
{"id": "OAUTH2_CLIENT", "sk": "CLIENT_ID#d72d4005-1fa7-4430-9754-80d5e2487bb6", "secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code"]}
{"id": "OAUTH2_CODE#CLIENT_ID#d72d4005-1fa7-4430-9754-80d5e2487bb6", "sk": "CODE#kyqp3oSuRFTfuBaCmq3XOgGWg67l42Kt3D6xPEj7Yd3MLdi9", "redirect_uri": "https://localhost/callback", "user_id": "0cb0ce87-9df6-40c1-9fa7-7dfdafd7910e", "nonce": "123", "scope": "openid profile email"}
// Post-migration: uncomment the following line
// {"id": "EMAIL", "sk": "sergio@somosbeta.com.br", "user_id": "357db1c5-7442-4075-98a3-fbe5c938a419"}
{"id": "email", "sk": "sergio@somosbeta.com.br", "user_id": "357db1c5-7442-4075-98a3-fbe5c938a419"}
{"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "0", "name": "Sérgio R Siqueira", "email": "sergio@somosbeta.com.br"}
{"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "PASSWORD", "hash": "$pbkdf2-sha256$29000$IuTcm7M2BiAEgPB.b.3dGw$d8xVCbx8zxg7MeQBrOvCOgniiilsIHEMHzoH/OXftLQ"}