Skip to content

Commit eb6eefd

Browse files
committed
CCM-12327: check/set lock number on update/delete endpoints
1 parent b1047e9 commit eb6eefd

File tree

20 files changed

+1188
-620
lines changed

20 files changed

+1188
-620
lines changed

infrastructure/terraform/modules/backend-api/spec.tmpl.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1553,6 +1553,15 @@
15531553
"schema": {
15541554
"type": "string"
15551555
}
1556+
},
1557+
{
1558+
"description": "Lock number of the current version of the template",
1559+
"in": "header",
1560+
"name": "X-Lock-Number",
1561+
"required": true,
1562+
"schema": {
1563+
"type": "integer"
1564+
}
15561565
}
15571566
],
15581567
"responses": {
@@ -1665,6 +1674,15 @@
16651674
"schema": {
16661675
"type": "string"
16671676
}
1677+
},
1678+
{
1679+
"description": "Lock number of the current version of the template",
1680+
"in": "header",
1681+
"name": "X-Lock-Number",
1682+
"required": true,
1683+
"schema": {
1684+
"type": "integer"
1685+
}
16681686
}
16691687
],
16701688
"requestBody": {
@@ -1741,6 +1759,15 @@
17411759
"schema": {
17421760
"type": "string"
17431761
}
1762+
},
1763+
{
1764+
"description": "Lock number of the current version of the template",
1765+
"in": "header",
1766+
"name": "X-Lock-Number",
1767+
"required": true,
1768+
"schema": {
1769+
"type": "integer"
1770+
}
17441771
}
17451772
],
17461773
"responses": {
@@ -1806,6 +1833,15 @@
18061833
"schema": {
18071834
"type": "string"
18081835
}
1836+
},
1837+
{
1838+
"description": "Lock number of the current version of the template",
1839+
"in": "header",
1840+
"name": "X-Lock-Number",
1841+
"required": true,
1842+
"schema": {
1843+
"type": "integer"
1844+
}
18091845
}
18101846
],
18111847
"responses": {

lambdas/backend-api/src/__tests__/templates/api/delete.test.ts

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ describe('Template API - Delete', () => {
2626
const event = mock<APIGatewayProxyEvent>({
2727
requestContext: { authorizer: ctx },
2828
pathParameters: { templateId: 'id' },
29+
headers: {
30+
'X-Lock-Number': '0',
31+
},
2932
});
3033

3134
const result = await handler(event, mock<Context>(), jest.fn());
@@ -51,6 +54,9 @@ describe('Template API - Delete', () => {
5154
},
5255
body: JSON.stringify({ name: 'test' }),
5356
pathParameters: { templateId: undefined },
57+
headers: {
58+
'X-Lock-Number': '0',
59+
},
5460
});
5561

5662
const result = await handler(event, mock<Context>(), jest.fn());
@@ -83,6 +89,9 @@ describe('Template API - Delete', () => {
8389
authorizer: { user: 'sub', clientId: 'nhs-notify-client-id' },
8490
},
8591
pathParameters: { templateId: '1-2-3' },
92+
headers: {
93+
'X-Lock-Number': '0',
94+
},
8695
});
8796

8897
const result = await handler(event, mock<Context>(), jest.fn());
@@ -95,10 +104,14 @@ describe('Template API - Delete', () => {
95104
}),
96105
});
97106

98-
expect(mocks.templateClient.deleteTemplate).toHaveBeenCalledWith('1-2-3', {
99-
userId: 'sub',
100-
clientId: 'nhs-notify-client-id',
101-
});
107+
expect(mocks.templateClient.deleteTemplate).toHaveBeenCalledWith(
108+
'1-2-3',
109+
{
110+
userId: 'sub',
111+
clientId: 'nhs-notify-client-id',
112+
},
113+
'0'
114+
);
102115
});
103116

104117
test('should return no content', async () => {
@@ -113,6 +126,9 @@ describe('Template API - Delete', () => {
113126
authorizer: { user: 'sub', clientId: 'nhs-notify-client-id' },
114127
},
115128
pathParameters: { templateId: '1-2-3' },
129+
headers: {
130+
'X-Lock-Number': '0',
131+
},
116132
});
117133

118134
const result = await handler(event, mock<Context>(), jest.fn());
@@ -122,9 +138,44 @@ describe('Template API - Delete', () => {
122138
body: JSON.stringify({ statusCode: 204, template: undefined }),
123139
});
124140

125-
expect(mocks.templateClient.deleteTemplate).toHaveBeenCalledWith('1-2-3', {
126-
userId: 'sub',
127-
clientId: 'nhs-notify-client-id',
141+
expect(mocks.templateClient.deleteTemplate).toHaveBeenCalledWith(
142+
'1-2-3',
143+
{
144+
userId: 'sub',
145+
clientId: 'nhs-notify-client-id',
146+
},
147+
'0'
148+
);
149+
});
150+
151+
test('coerces missing lock number header to empty string', async () => {
152+
const { handler, mocks } = setup();
153+
154+
mocks.templateClient.deleteTemplate.mockResolvedValueOnce({
155+
error: { errorMeta: { code: 409, description: 'Invalid lock number' } },
128156
});
157+
158+
const event = mock<APIGatewayProxyEvent>({
159+
requestContext: {
160+
authorizer: { user: 'sub', clientId: 'nhs-notify-client-id' },
161+
},
162+
pathParameters: { templateId: '1-2-3' },
163+
});
164+
165+
const result = await handler(event, mock<Context>(), jest.fn());
166+
167+
expect(result).toEqual({
168+
statusCode: 409,
169+
body: JSON.stringify({
170+
statusCode: 409,
171+
technicalMessage: 'Invalid lock number',
172+
}),
173+
});
174+
175+
expect(mocks.templateClient.deleteTemplate).toHaveBeenCalledWith(
176+
'1-2-3',
177+
{ userId: 'sub', clientId: 'nhs-notify-client-id' },
178+
''
179+
);
129180
});
130181
});

lambdas/backend-api/src/__tests__/templates/api/update.test.ts

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ describe('Template API - Update', () => {
2828
requestContext: { authorizer: ctx },
2929
pathParameters: { templateId: 'id' },
3030
body: JSON.stringify({ name: 'test' }),
31+
headers: {
32+
'X-Lock-Number': '1',
33+
},
3134
});
3235

3336
const result = await handler(event, mock<Context>(), jest.fn());
@@ -51,6 +54,9 @@ describe('Template API - Update', () => {
5154
requestContext: { authorizer: undefined },
5255
body: JSON.stringify({ name: 'test' }),
5356
pathParameters: { templateId: '1-2-3' },
57+
headers: {
58+
'X-Lock-Number': '1',
59+
},
5460
});
5561

5662
const result = await handler(event, mock<Context>(), jest.fn());
@@ -88,6 +94,9 @@ describe('Template API - Update', () => {
8894
},
8995
pathParameters: { templateId: '1-2-3' },
9096
body: undefined,
97+
headers: {
98+
'X-Lock-Number': '1',
99+
},
91100
});
92101

93102
const result = await handler(event, mock<Context>(), jest.fn());
@@ -106,7 +115,8 @@ describe('Template API - Update', () => {
106115
expect(mocks.templateClient.updateTemplate).toHaveBeenCalledWith(
107116
'1-2-3',
108117
{},
109-
{ userId: 'sub', clientId: 'nhs-notify-client-id' }
118+
{ userId: 'sub', clientId: 'nhs-notify-client-id' },
119+
'1'
110120
);
111121
});
112122

@@ -119,6 +129,9 @@ describe('Template API - Update', () => {
119129
},
120130
body: JSON.stringify({ name: 'test' }),
121131
pathParameters: { templateId: undefined },
132+
headers: {
133+
'X-Lock-Number': '1',
134+
},
122135
});
123136

124137
const result = await handler(event, mock<Context>(), jest.fn());
@@ -152,6 +165,9 @@ describe('Template API - Update', () => {
152165
},
153166
body: JSON.stringify({ name: 'name' }),
154167
pathParameters: { templateId: '1-2-3' },
168+
headers: {
169+
'X-Lock-Number': '1',
170+
},
155171
});
156172

157173
const result = await handler(event, mock<Context>(), jest.fn());
@@ -167,7 +183,8 @@ describe('Template API - Update', () => {
167183
expect(mocks.templateClient.updateTemplate).toHaveBeenCalledWith(
168184
'1-2-3',
169185
{ name: 'name' },
170-
{ userId: 'sub', clientId: 'nhs-notify-client-id' }
186+
{ userId: 'sub', clientId: 'nhs-notify-client-id' },
187+
'1'
171188
);
172189
});
173190

@@ -186,7 +203,7 @@ describe('Template API - Update', () => {
186203
templateStatus: 'NOT_YET_SUBMITTED',
187204
createdAt: new Date().toISOString(),
188205
updatedAt: new Date().toISOString(),
189-
lockNumber: 1,
206+
lockNumber: 2,
190207
};
191208

192209
mocks.templateClient.updateTemplate.mockResolvedValueOnce({
@@ -199,6 +216,9 @@ describe('Template API - Update', () => {
199216
},
200217
body: JSON.stringify(update),
201218
pathParameters: { templateId: '1-2-3' },
219+
headers: {
220+
'X-Lock-Number': '1',
221+
},
202222
});
203223

204224
const result = await handler(event, mock<Context>(), jest.fn());
@@ -211,7 +231,47 @@ describe('Template API - Update', () => {
211231
expect(mocks.templateClient.updateTemplate).toHaveBeenCalledWith(
212232
'1-2-3',
213233
update,
214-
{ userId: 'sub', clientId: 'nhs-notify-client-id' }
234+
{ userId: 'sub', clientId: 'nhs-notify-client-id' },
235+
'1'
236+
);
237+
});
238+
239+
test('coerces lock number header to empty string if missing', async () => {
240+
const { handler, mocks } = setup();
241+
242+
const update: CreateUpdateTemplate = {
243+
name: 'updated-name',
244+
message: 'message',
245+
templateType: 'SMS',
246+
};
247+
248+
mocks.templateClient.updateTemplate.mockResolvedValueOnce({
249+
error: { errorMeta: { code: 409, description: 'Invalid lock number' } },
250+
});
251+
252+
const event = mock<APIGatewayProxyEvent>({
253+
requestContext: {
254+
authorizer: { user: 'sub', clientId: 'nhs-notify-client-id' },
255+
},
256+
body: JSON.stringify(update),
257+
pathParameters: { templateId: '1-2-3' },
258+
});
259+
260+
const result = await handler(event, mock<Context>(), jest.fn());
261+
262+
expect(result).toEqual({
263+
statusCode: 409,
264+
body: JSON.stringify({
265+
statusCode: 409,
266+
technicalMessage: 'Invalid lock number',
267+
}),
268+
});
269+
270+
expect(mocks.templateClient.updateTemplate).toHaveBeenCalledWith(
271+
'1-2-3',
272+
update,
273+
{ userId: 'sub', clientId: 'nhs-notify-client-id' },
274+
''
215275
);
216276
});
217277
});

0 commit comments

Comments
 (0)