add webhook to docseal

This commit is contained in:
2025-11-03 20:08:49 -03:00
parent eca3ac42dc
commit fef60f2ae0
22 changed files with 290 additions and 29 deletions

View File

@@ -1,5 +1,8 @@
import base64
import json
import os
from dataclasses import dataclass
from http import HTTPMethod
import jsonlines
import pytest
@@ -17,6 +20,7 @@ def pytest_configure():
os.environ['DOCSEAL_KEY'] = 'gUWhWtYBgTaP8fc1q5GZ6JuUHaZzMgZna6KFBHz3Gzk'
os.environ['USER_TABLE'] = PYTEST_TABLE_NAME
os.environ['COURSE_TABLE'] = PYTEST_TABLE_NAME
os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME
os.environ['ORDER_TABLE'] = PYTEST_TABLE_NAME
os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME
os.environ['BUCKET_NAME'] = 'saladeaula.digital'
@@ -36,6 +40,78 @@ def lambda_context() -> LambdaContext:
return LambdaContext()
class HttpApiProxy:
def __call__(
self,
raw_path: str,
method: str = HTTPMethod.GET,
body: dict = {},
*,
headers: dict = {},
auth_flow_type: str = 'USER_AUTH',
queryStringParameters: dict = {},
**kwargs,
) -> dict:
return {
'version': '2.0',
'routeKey': '$default',
'rawPath': raw_path,
'rawQueryString': 'parameter1=value1&parameter1=value2&parameter2=value',
'cookies': ['cookie1', 'cookie2'],
'headers': headers,
'queryStringParameters': queryStringParameters,
'requestContext': {
'accountId': '123456789012',
'apiId': 'api-id',
'authorizer': {
'lambda': {
'user': {
'name': 'Sérgio R Siqueira',
'email': 'sergio@somosbeta.com.br',
'email_verified': 'true',
'custom:user_id': '5OxmMjL-ujoR5IMGegQz',
'sub': 'c4f30dbd-083e-4b84-aa50-c31afe9b9c01',
},
'auth_flow_type': auth_flow_type,
},
'jwt': {
'claims': {'claim1': 'value1', 'claim2': 'value2'},
'scopes': ['scope1', 'scope2'],
},
},
'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),
'pathParameters': {'parameter1': 'value1'},
'isBase64Encoded': True,
'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 http_api_proxy():
return HttpApiProxy()
@pytest.fixture
def dynamodb_client():
from boto3clients import dynamodb_client as client
@@ -80,3 +156,10 @@ def seeds(dynamodb_client):
TableName=PYTEST_TABLE_NAME,
Item=serialize(line),
)
@pytest.fixture
def app():
import app
return app

View File

@@ -1,7 +1,8 @@
import app.events.send_reminder_emails as app
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
import events.send_reminder_emails as app
def test_reminder_access_period_before_30_days(
seeds,

View File

@@ -1,6 +1,7 @@
import app.events.send_reminder_emails as app
from aws_lambda_powertools.utilities.typing import LambdaContext
import events.send_reminder_emails as app
def test_reminder_cert_expiration_before_30_days(
seeds,

View File

@@ -1,6 +1,7 @@
import app.events.send_reminder_emails as app
from aws_lambda_powertools.utilities.typing import LambdaContext
import events.send_reminder_emails as app
def test_reminder_no_access_after_3_days(
seeds,

View File

@@ -1,6 +1,7 @@
import app.events.send_reminder_emails as app
from aws_lambda_powertools.utilities.typing import LambdaContext
import events.send_reminder_emails as app
def test_reminder_no_activity_after_7_days(
seeds,

View File

@@ -1,6 +1,5 @@
from datetime import timedelta
import app.events.reporting.append_cert as app
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dateutils import now
from layercake.dynamodb import (
@@ -9,6 +8,8 @@ from layercake.dynamodb import (
TransactKey,
)
import events.reporting.append_cert as app
def test_append_cert(
seeds,

View File

@@ -1,10 +1,11 @@
import app.events.reporting.send_report_email as app
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import (
DynamoDBPersistenceLayer,
KeyPair,
)
import events.reporting.send_report_email as app
def test_send_report_email(
monkeypatch,

View File

@@ -1,11 +1,11 @@
import app.events.stopgap.patch_course_metadata as app
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import (
DynamoDBPersistenceLayer,
SortKey,
TransactKey,
KeyPair,
)
import events.stopgap.patch_course_metadata as app
def test_enroll(
seeds,
@@ -27,10 +27,8 @@ def test_enroll(
}
assert app.lambda_handler(event, lambda_context) # type: ignore
result = dynamodb_persistence_layer.collection.get_items(
TransactKey('47ZxxcVBjvhDS5TE98tpfQ')
+ SortKey('0')
+ SortKey('METADATA#DEDUPLICATION_WINDOW')
r = dynamodb_persistence_layer.collection.get_item(
KeyPair('47ZxxcVBjvhDS5TE98tpfQ', '0')
)
assert 'METADATA#DEDUPLICATION_WINDOW' in result
assert 'access_expires_at' in r

View File

@@ -1,7 +1,8 @@
import app.events.allocate_slots as app
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import DynamoDBPersistenceLayer, PartitionKey
import events.allocate_slots as app
def test_allocate_slots(
seeds,

View File

@@ -14,6 +14,7 @@ def test_ask_to_sign(
's3_uri': 's3://saladeaula.digital/certs/samples/nr11-operador-de-munck.pdf'
},
'user': {
'id': '5OxmMjL-ujoR5IMGegQz',
'name': 'Sérgio R Siqueira',
'email': 'sergio@somosbeta.com.br',
},

View File

@@ -1,7 +1,8 @@
import app.events.enroll as app
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, PartitionKey
import events.enroll as app
def test_enroll(
seeds,

View File

@@ -1,7 +1,8 @@
import app.events.reenroll_if_failed as app
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
import events.reenroll_if_failed as app
def test_reenroll_custom_dedup_window(
seeds,

View File

@@ -1,10 +1,11 @@
import app.events.schedule_reminders as app
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import (
DynamoDBPersistenceLayer,
PartitionKey,
)
import events.schedule_reminders as app
def test_schedule_reminders(
seeds,

View File

@@ -1,4 +1,3 @@
import app.events.set_access_expired as app
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import (
DynamoDBPersistenceLayer,
@@ -6,6 +5,8 @@ from layercake.dynamodb import (
TransactKey,
)
import events.set_access_expired as app
def test_set_access_expired(
seeds,

View File

@@ -1,4 +1,3 @@
import app.events.set_cert_expired as app
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import (
DynamoDBPersistenceLayer,
@@ -6,6 +5,8 @@ from layercake.dynamodb import (
TransactKey,
)
import events.set_cert_expired as app
def test_set_cert_expired(
seeds,

View File

@@ -25,7 +25,7 @@
// Enrollment
{"id": "6437a282-6fe8-4e4d-9eb0-da1007238007", "sk": "0", "status": "IN_PROGRESS", "progress": 10}
{"id": "845fe390-e3c3-4514-97f8-c42de0566cf0", "sk": "0", "status": "COMPLETED", "progress": 100}
{"id": "845fe390-e3c3-4514-97f8-c42de0566cf0", "sk": "0", "status": "COMPLETED", "progress": 100, "cert": {"s3_uri": "s3://saladeaula.digital/certs/samples/nr11-operador-de-munck.pdf", "issued_at": "2025-08-24T01:44:42.703012-03:06"}}
{"id": "1ee108ae-67d4-4545-bf6d-4e641cdaa4e0", "sk": "0", "status": "COMPLETED", "score": 100, "course": {"id": "123", "name": "CIPA Grau de Risco 1"}, "user": {"name": "Kurt Cobain"}, "cert": {"s3_uri": "s3://saladeaula.digital/issuedcerts/1ee108ae-67d4-4545-bf6d-4e641cdaa4e0.pdf"}}
{"id": "1ee108ae-67d4-4545-bf6d-4e641cdaa4e0", "sk": "STARTED", "started_at": "2025-08-24T01:44:42.703012-03:06"}

View File

@@ -0,0 +1,38 @@
from http import HTTPMethod, HTTPStatus
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
from .conftest import HttpApiProxy, LambdaContext
enrollment_id = '845fe390-e3c3-4514-97f8-c42de0566cf0'
body = {
'data': {
'name': enrollment_id,
'slug': 'jzNV3ZrF6T16tX',
'combined_document_url': 'https://docs.eduseg.com.br/file/WyI3MGVhNGQwYS02YTE5LTQyYzItODgyNy1iZTc0Zjc2ZTlkNmUiLCJibG9iIiwxNzYyMjEwMjg2XQ--72caad853fdc1c64c5321368c7d883828be0b859ed39689355bac9dffe71b8e2/e249c51b-3e68-42eb-bb4b-20659263ce1c.pdf',
},
}
def test_postback(
app,
seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
# This data was added from seeds
r = app.lambda_handler(
http_api_proxy(
raw_path='/',
method=HTTPMethod.POST,
body=body,
),
lambda_context,
)
assert r['statusCode'] == HTTPStatus.NO_CONTENT
r = dynamodb_persistence_layer.get_item(
KeyPair('845fe390-e3c3-4514-97f8-c42de0566cf0', '0')
)
print(r)

View File

@@ -1,6 +1,6 @@
import base64
import uuid
from unittest.mock import MagicMock, patch
from unittest.mock import patch
from docseal import create_submission_from_pdf
@@ -13,10 +13,12 @@ Seu certificado já está pronto e aguardando apenas a sua assinatura digital.
"""
def test_create_submission_from_pdf():
r = MagicMock()
def Response(*args, **kwargs):
return type('Response', (), {'raise_for_status': object})
with patch('docseal.requests.post', r):
def test_create_submission_from_pdf():
with patch('docseal.requests.post', Response):
with open('tests/sample.pdf', 'rb') as f:
file = base64.b64encode(f.read())
create_submission_from_pdf(