diff --git a/poetry.lock b/poetry.lock index ffe3dee..f8a284f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -936,6 +936,28 @@ files = [ {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] +[[package]] +name = "docker" +version = "7.1.0" +description = "A Python library for the Docker Engine API." +optional = false +python-versions = ">=3.8" +files = [ + {file = "docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0"}, + {file = "docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c"}, +] + +[package.dependencies] +pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""} +requests = ">=2.26.0" +urllib3 = ">=1.26.0" + +[package.extras] +dev = ["coverage (==7.2.7)", "pytest (==7.4.2)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.1.0)", "ruff (==0.1.8)"] +docs = ["myst-parser (==0.18.0)", "sphinx (==5.1.1)"] +ssh = ["paramiko (>=2.4.3)"] +websockets = ["websocket-client (>=1.3.0)"] + [[package]] name = "filelock" version = "3.16.0" @@ -1138,9 +1160,10 @@ antlr4-python3-runtime = {version = "*", optional = true, markers = "extra == \" boto3 = ">=1.9.201" botocore = ">=1.14.0" cryptography = ">=3.3.1" +docker = {version = ">=3.0.0", optional = true, markers = "extra == \"dynamodb\""} Jinja2 = ">=2.10.1" jsonpath-ng = {version = "*", optional = true, markers = "extra == \"stepfunctions\""} -py-partiql-parser = {version = "0.5.6", optional = true, markers = "extra == \"s3\""} +py-partiql-parser = {version = "0.5.6", optional = true, markers = "extra == \"dynamodb\" or extra == \"s3\""} python-dateutil = ">=2.1,<3.0.0" PyYAML = {version = ">=5.1", optional = true, markers = "extra == \"s3\" or extra == \"ssm\""} requests = ">=2.5" @@ -1609,6 +1632,29 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -1927,4 +1973,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "db2caf6ba5dd7262085db519c37312fbbb5e9eaf4a8ef9682cce6e3a8aad0fa2" +content-hash = "9fd58c873131c3ba191759ad1859f1fddaf22f341528baad08f24903903a498e" diff --git a/pyproject.toml b/pyproject.toml index eac3e72..27363d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ mypy = "^1.4.0" coverage = "^7.2.7" pytest = "^8.1.1" pytest-asyncio = "^0.23.6" -moto = {extras = ["s3", "ssm", "stepfunctions", "secretsmanager"], version = "^5.0.5"} +moto = {extras = ["s3", "ssm", "stepfunctions", "secretsmanager", "dynamodb"], version = "^5.0.14"} boto3-stubs = {extras = ["s3", "ssm", "secretsmanager", "dynamodb", "stepfunctions", "sqs", "lambda", "logs",], version = "^1.34.32"} ruff = "^0" petname = "^2.6" diff --git a/src/shared/common.py b/src/shared/common.py index 93152b4..7c0d98a 100644 --- a/src/shared/common.py +++ b/src/shared/common.py @@ -90,6 +90,7 @@ def acquire_lock(ddb_client: DynamoDBClient, lock_name: str, execution_id: str): except ClientError as ce: if ce.response["Error"]["Code"] == "ConditionalCheckFailedException": raise SingletonCheckFailure(f"Lock already exists for {lock_name}") from ce + raise ce return resp.items diff --git a/tests/mocked/conftest.py b/tests/mocked/conftest.py index dccbc10..f7febdd 100644 --- a/tests/mocked/conftest.py +++ b/tests/mocked/conftest.py @@ -8,8 +8,12 @@ from integration.test_helpers import temp_env_vars from mesh_client import MeshClient from moto import mock_aws +from mypy_boto3_dynamodb import DynamoDBClient from mypy_boto3_s3 import S3Client from mypy_boto3_stepfunctions import SFNClient +from nhs_aws_helpers import ( + dynamodb_client as _ddb_client, +) from nhs_aws_helpers import ( s3_client as _s3_client, ) @@ -39,6 +43,40 @@ def s3_client(_mock_aws) -> S3Client: return _s3_client() +@pytest.fixture(name="ddb_client") +def ddb_client(_mock_aws) -> DynamoDBClient: + return _ddb_client() + + +@pytest.fixture +def mocked_lock_table(ddb_client: DynamoDBClient, environment): + table_name = os.getenv("DDB_LOCK_TABLE_NAME") or "mocked-lock-table" + ddb_client.create_table( + AttributeDefinitions=[ + {"AttributeName": "LockOwner", "AttributeType": "S"}, + {"AttributeName": "LockType", "AttributeType": "S"}, + {"AttributeName": "LockName", "AttributeType": "S"}, + ], + TableName=table_name, + KeySchema=[{"AttributeName": "LockName", "KeyType": "HASH"}], + GlobalSecondaryIndexes=[ + { + "IndexName": "LockTypeOwnerTableIndex", + "KeySchema": [ + {"AttributeName": "LockType", "KeyType": "HASH"}, + {"AttributeName": "LockOwner", "KeyType": "RANGE"}, + ], + "Projection": {"ProjectionType": "KEYS_ONLY"}, + } + ], + BillingMode="PAY_PER_REQUEST", + ) + yield ddb_client.describe_table(TableName=table_name).get("Table", None) + + # FIXME - do I need this? + ddb_client.delete_table(TableName=table_name) + + @pytest.fixture def environment( _mock_aws, @@ -61,6 +99,7 @@ def environment( SHARED_KEY_CONFIG_KEY=f"/{environment}/mesh/MESH_SHARED_KEY", MAILBOXES_BASE_CONFIG_KEY=f"/{environment}/mesh/mailboxes", VERIFY_CHECKS_COMMON_NAME=False, + DDB_LOCK_TABLE_NAME="mocked-lock-table", ): yield environment diff --git a/tests/mocked/mesh_check_send_parameters_application_test.py b/tests/mocked/mesh_check_send_parameters_application_test.py index 8596ad0..f3b5985 100644 --- a/tests/mocked/mesh_check_send_parameters_application_test.py +++ b/tests/mocked/mesh_check_send_parameters_application_test.py @@ -12,7 +12,11 @@ def test_mesh_check_send_parameters_happy_path_chunked( - mesh_s3_bucket: str, environment: str, send_message_sfn_arn: str, capsys + mesh_s3_bucket: str, + environment: str, + send_message_sfn_arn: str, + capsys, + mocked_lock_table, ): """Test the lambda as a whole, happy path for small file""" @@ -52,6 +56,8 @@ def test_mesh_check_send_parameters_happy_path_chunked( "total_chunks": 4, "workflow_id": "TESTWORKFLOW", }, + "lock_name": f"SendLock_{mesh_s3_bucket}_X26ABC2/outbound/testfile.json", + "execution_id": "TEST1234", }, } from mesh_check_send_parameters_application import ( @@ -305,7 +311,10 @@ def sample_trigger_event( "time": "2021-06-29T14:10:55Z", "region": "eu-west-2", "resources": [], - "detail": s3_event, + "EventDetail": { + "detail": s3_event, + }, + "ExecutionId": "TEST1234", } return event_bridge_event