Skip to content

Commit 0fea390

Browse files
committed
Merge branch 'feature/PI-746-delete_a_product' into release/2025-02-07
2 parents bd5658d + 6799cdb commit 0fea390

File tree

32 files changed

+924
-129
lines changed

32 files changed

+924
-129
lines changed

infrastructure/swagger/05_paths.yaml

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -229,26 +229,26 @@ paths:
229229
security:
230230
- ${authoriser_name}: []
231231
- app-level0: []
232-
# delete:
233-
# operationId: deleteproductendpoint
234-
# summary: Delete a Product resource (DELETE)
235-
# parameters:
236-
# - $ref: "#/components/parameters/ProductTeamId"
237-
# - $ref: "#/components/parameters/ProductId"
238-
# - $ref: "#/components/parameters/HeaderVersion"
239-
# - $ref: "#/components/parameters/HeaderRequestId"
240-
# - $ref: "#/components/parameters/HeaderCorrelationId"
241-
# responses:
242-
# "204":
243-
# description: Delete Product operation successful
244-
# "404":
245-
# $ref: "#/components/responses/NotFound"
246-
# x-amazon-apigateway-integration:
247-
# <<: *ApiGatewayIntegration
248-
# uri: ${method_deleteCpmProduct}
249-
# security:
250-
# - ${authoriser_name}: []
251-
# - app-level0: []
232+
delete:
233+
operationId: deleteproductendpoint
234+
summary: Delete a Product resource (DELETE)
235+
parameters:
236+
- $ref: "#/components/parameters/ProductTeamId"
237+
- $ref: "#/components/parameters/ProductId"
238+
- $ref: "#/components/parameters/HeaderVersion"
239+
- $ref: "#/components/parameters/HeaderRequestId"
240+
- $ref: "#/components/parameters/HeaderCorrelationId"
241+
responses:
242+
"204":
243+
description: Delete Product operation successful
244+
"404":
245+
$ref: "#/components/responses/NotFound"
246+
x-amazon-apigateway-integration:
247+
<<: *ApiGatewayIntegration
248+
uri: ${method_deleteCpmProduct}
249+
security:
250+
- ${authoriser_name}: []
251+
- app-level0: []
252252

253253
/ProductTeamEpr/{product_team_id}/ProductEpr:
254254
post:

infrastructure/terraform/per_workspace/main.tf

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,23 @@ module "cpmtable" {
6969
attributes = [
7070
{ name = "pk", type = "S" },
7171
{ name = "sk", type = "S" },
72-
{ name = "pk_read", type = "S" },
73-
{ name = "sk_read", type = "S" },
72+
{ name = "pk_read_1", type = "S" },
73+
{ name = "sk_read_1", type = "S" },
74+
{ name = "pk_read_2", type = "S" },
75+
{ name = "sk_read_2", type = "S" },
7476
]
7577

7678
global_secondary_indexes = [
7779
{
78-
name = "idx_gsi_read"
79-
hash_key = "pk_read"
80-
range_key = "sk_read"
80+
name = "idx_gsi_read_1"
81+
hash_key = "pk_read_1"
82+
range_key = "sk_read_1"
83+
projection_type = "ALL"
84+
},
85+
{
86+
name = "idx_gsi_read_2"
87+
hash_key = "pk_read_2"
88+
range_key = "sk_read_2"
8189
projection_type = "ALL"
8290
}
8391
]

src/api/createCpmProduct/tests/test_index.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from domain.repository.product_team_repository import ProductTeamRepository
88
from event.json import json_loads
99

10-
from test_helpers.dynamodb import mock_table
10+
from test_helpers.dynamodb import mock_table_cpm
1111
from test_helpers.response_assertions import _response_assertions
1212

1313
from .data import product_payload, product_team_payload
@@ -22,7 +22,7 @@ def _mock_test(version, params):
2222
name=product_team_payload["name"], keys=product_team_payload["keys"]
2323
)
2424

25-
with mock_table(table_name=TABLE_NAME) as client, mock.patch.dict(
25+
with mock_table_cpm(table_name=TABLE_NAME) as client, mock.patch.dict(
2626
os.environ,
2727
{
2828
"DYNAMODB_TABLE": TABLE_NAME,
@@ -113,7 +113,7 @@ def test_incoming_errors(params, error, status_code, version):
113113
],
114114
)
115115
def test_index_no_such_product_team(version):
116-
with mock_table(TABLE_NAME), mock.patch.dict(
116+
with mock_table_cpm(TABLE_NAME), mock.patch.dict(
117117
os.environ,
118118
{
119119
"DYNAMODB_TABLE": TABLE_NAME,

src/api/createProductTeam/tests/v1/test_index_v1.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pytest
66
from event.json import json_loads
77

8-
from test_helpers.dynamodb import mock_table
8+
from test_helpers.dynamodb import mock_table_cpm
99
from test_helpers.response_assertions import _response_assertions
1010
from test_helpers.sample_data import (
1111
CPM_PRODUCT_TEAM_NO_ID,
@@ -22,7 +22,7 @@
2222
],
2323
)
2424
def test_index(version):
25-
with mock_table(table_name=TABLE_NAME), mock.patch.dict(
25+
with mock_table_cpm(table_name=TABLE_NAME), mock.patch.dict(
2626
os.environ,
2727
{
2828
"DYNAMODB_TABLE": TABLE_NAME,
@@ -72,7 +72,7 @@ def test_index(version):
7272
],
7373
)
7474
def test_index_bad_payload(version):
75-
with mock_table(table_name=TABLE_NAME), mock.patch.dict(
75+
with mock_table_cpm(table_name=TABLE_NAME), mock.patch.dict(
7676
os.environ,
7777
{
7878
"DYNAMODB_TABLE": TABLE_NAME,
@@ -120,7 +120,7 @@ def test_index_bad_payload(version):
120120
],
121121
)
122122
def test_index(version):
123-
with mock_table(table_name=TABLE_NAME), mock.patch.dict(
123+
with mock_table_cpm(table_name=TABLE_NAME), mock.patch.dict(
124124
os.environ,
125125
{
126126
"DYNAMODB_TABLE": TABLE_NAME,

src/api/deleteCpmProduct/index.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from api_utils.api_step_chain import execute_step_chain
2+
from domain.logging.step_decorators import logging_step_decorators
3+
from event.aws.client import dynamodb_client
4+
from event.environment import BaseEnvironment
5+
from event.logging.logger import setup_logger
6+
7+
from .src.v1.steps import steps as v1_steps
8+
9+
10+
class Environment(BaseEnvironment):
11+
DYNAMODB_TABLE: str
12+
13+
14+
versioned_steps = {"1": v1_steps}
15+
cache = {
16+
**Environment.build().dict(),
17+
"DYNAMODB_CLIENT": dynamodb_client(),
18+
}
19+
20+
21+
def handler(event: dict, context=None):
22+
setup_logger(service_name=__file__)
23+
return execute_step_chain(
24+
event=event,
25+
cache=cache,
26+
versioned_steps=versioned_steps,
27+
)
28+
29+
30+
STEP_DECORATORS = [*logging_step_decorators]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from builder.lambda_build import build
2+
3+
if __name__ == "__main__":
4+
build(__file__)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[
2+
"dynamodb:Query",
3+
"dynamodb:PutItem",
4+
"dynamodb:UpdateItem",
5+
"dynamodb:DeleteItem"
6+
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
["kms:Decrypt"]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from http import HTTPStatus
2+
3+
from domain.api.common_steps.read_cpm_product import before_steps, read_product
4+
from domain.core.cpm_product import CpmProduct
5+
from domain.repository.cpm_product_repository import CpmProductRepository
6+
7+
8+
def delete_product(data, cache) -> CpmProduct:
9+
product: CpmProduct = data[read_product]
10+
product_repo: CpmProductRepository = CpmProductRepository(
11+
table_name=cache["DYNAMODB_TABLE"], dynamodb_client=cache["DYNAMODB_CLIENT"]
12+
)
13+
product.delete()
14+
return product_repo.write(product)
15+
16+
17+
def set_http_status(data, cache) -> tuple[int, None]:
18+
product: CpmProduct = data[read_product]
19+
return HTTPStatus.OK, {"code": "200", "message": f"{product.id} has been deleted."}
20+
21+
22+
steps = [
23+
*before_steps,
24+
delete_product,
25+
set_http_status,
26+
]
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import os
2+
from contextlib import contextmanager
3+
from datetime import datetime
4+
from unittest import mock
5+
6+
import pytest
7+
from domain.core.cpm_system_id import ProductId
8+
from domain.core.enum import Status
9+
from domain.core.root import Root
10+
from domain.repository.cpm_product_repository import CpmProductRepository
11+
from domain.repository.errors import ItemNotFound
12+
from domain.repository.product_team_repository import ProductTeamRepository
13+
14+
from test_helpers.dynamodb import mock_table_cpm
15+
from test_helpers.sample_data import CPM_PRODUCT_TEAM_NO_ID
16+
from test_helpers.uuid import consistent_uuid
17+
18+
TABLE_NAME = "hiya"
19+
20+
ODS_CODE = "F5H1R"
21+
PRODUCT_TEAM_ID = consistent_uuid(1)
22+
PRODUCT_ID = "P.AAA-AAA"
23+
PRODUCT_NAME = "My Product"
24+
VERSION = 1
25+
26+
27+
@contextmanager
28+
def mock_lambda():
29+
org = Root.create_ods_organisation(ods_code=CPM_PRODUCT_TEAM_NO_ID["ods_code"])
30+
product_team = org.create_product_team(
31+
name=CPM_PRODUCT_TEAM_NO_ID["name"], keys=CPM_PRODUCT_TEAM_NO_ID["keys"]
32+
)
33+
34+
with mock_table_cpm(table_name=TABLE_NAME) as client, mock.patch.dict(
35+
os.environ,
36+
{"DYNAMODB_TABLE": TABLE_NAME, "AWS_DEFAULT_REGION": "eu-west-2"},
37+
clear=True,
38+
):
39+
product_team_repo = ProductTeamRepository(
40+
table_name=TABLE_NAME, dynamodb_client=client
41+
)
42+
product_team_repo.write(entity=product_team)
43+
44+
product = product_team.create_cpm_product(
45+
name=PRODUCT_NAME, product_id=PRODUCT_ID
46+
)
47+
product_repo = CpmProductRepository(
48+
table_name=TABLE_NAME, dynamodb_client=client
49+
)
50+
product_repo.write(entity=product)
51+
52+
import api.deleteCpmProduct.index as index
53+
54+
index.cache["DYNAMODB_CLIENT"] = client
55+
56+
yield index, product_team
57+
58+
59+
def test_index():
60+
with mock_lambda() as (index, product_team):
61+
# Execute the lambda
62+
response = index.handler(
63+
event={
64+
"headers": {"version": VERSION},
65+
"pathParameters": {
66+
"product_team_id": product_team.id,
67+
"product_id": PRODUCT_ID,
68+
},
69+
}
70+
)
71+
72+
# Validate that the response indicates that a resource was deleted
73+
assert response["statusCode"] == 200
74+
assert (
75+
response["body"]
76+
== '{"code": "200", "message": "P.AAA-AAA has been deleted."}'
77+
)
78+
79+
# Retrieve the created resource
80+
repo = CpmProductRepository(
81+
table_name=TABLE_NAME, dynamodb_client=index.cache["DYNAMODB_CLIENT"]
82+
)
83+
with pytest.raises(ItemNotFound):
84+
repo.read(product_team_id=product_team.id, id=PRODUCT_ID)
85+
86+
deleted_product = repo.read(
87+
product_team_id=product_team.id, id=PRODUCT_ID, status="inactive"
88+
).dict()
89+
90+
# Sense checks on the deleted resource
91+
created_on = deleted_product.pop("created_on")
92+
updated_on = deleted_product.pop("updated_on")
93+
deleted_on = deleted_product.pop("deleted_on")
94+
assert created_on < updated_on
95+
assert updated_on == deleted_on
96+
assert isinstance(created_on, datetime)
97+
assert isinstance(updated_on, datetime)
98+
assert isinstance(deleted_on, datetime)
99+
100+
assert ProductId.validate_cpm_system_id(deleted_product.pop("id"))
101+
assert deleted_product == {
102+
"name": PRODUCT_NAME,
103+
"ods_code": ODS_CODE,
104+
"product_team_id": product_team.id,
105+
"status": Status.INACTIVE,
106+
"keys": [],
107+
}
108+
109+
110+
@pytest.mark.parametrize(
111+
["path_parameters", "error_code", "status_code"],
112+
[
113+
(
114+
{"product_team_id": PRODUCT_TEAM_ID},
115+
"MISSING_VALUE",
116+
400,
117+
),
118+
(
119+
{"product_id": PRODUCT_ID},
120+
"MISSING_VALUE",
121+
400,
122+
),
123+
(
124+
{
125+
"product_team_id": PRODUCT_TEAM_ID,
126+
"product_id": PRODUCT_ID,
127+
"extra_forbidden_param": "foo",
128+
},
129+
"VALIDATION_ERROR",
130+
400,
131+
),
132+
(
133+
{"product_team_id": "does-not-exist", "product_id": PRODUCT_ID},
134+
"RESOURCE_NOT_FOUND",
135+
404,
136+
),
137+
(
138+
{"product_team_id": PRODUCT_TEAM_ID, "product_id": "does-not-exist"},
139+
"RESOURCE_NOT_FOUND",
140+
404,
141+
),
142+
],
143+
)
144+
def test_incoming_errors(path_parameters, error_code, status_code):
145+
with mock_lambda() as (index, product_team):
146+
# Execute the lambda
147+
response = index.handler(
148+
event={
149+
"headers": {"version": VERSION},
150+
"pathParameters": path_parameters,
151+
}
152+
)
153+
154+
# Validate that the response indicates that the expected error
155+
assert response["statusCode"] == status_code
156+
assert error_code in response["body"]

0 commit comments

Comments
 (0)