Skip to content

Commit 52e06fd

Browse files
committed
CCM-22090: PR feedback
1 parent 746ebe4 commit 52e06fd

4 files changed

Lines changed: 166 additions & 58 deletions

File tree

‎sandbox/__test__/message_responses.spec.js‎

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { setup } from './helpers.js'
44

55
const VALID_MESSAGE_ID = '11111111-1111-4111-8111-111111111111';
66
const NOT_FOUND_MESSAGE_ID = '00000000-0000-4000-8000-000000000404';
7-
const TOO_MANY_RESPONSES_MESSAGE_ID = '00000000-0000-4000-8000-000000000422';
7+
const TOO_MANY_RESPONSES_MESSAGE_ID = '00000000-0000-4000-8000-000000000500';
88

99
describe('/api/v1/message-responses/:messageId', () => {
1010
let env;
@@ -34,7 +34,14 @@ describe('/api/v1/message-responses/:messageId', () => {
3434
.get(`/api/v1/message-responses/${VALID_MESSAGE_ID}`)
3535
.set({ Authorization: 'banned' })
3636
.expect(403, {
37-
error: 'Forbidden'
37+
errors: [
38+
{
39+
code: 'CM_FORBIDDEN',
40+
status: '403',
41+
title: 'Forbidden',
42+
detail: 'Client not recognised or not yet onboarded.'
43+
}
44+
]
3845
})
3946
.expect('Content-Type', /json/, done);
4047
});
@@ -43,7 +50,15 @@ describe('/api/v1/message-responses/:messageId', () => {
4350
request(server)
4451
.get('/api/v1/message-responses/not-a-valid-uuid')
4552
.expect(400, {
46-
error: 'Invalid messageId format'
53+
errors: [
54+
{
55+
code: 'CM_INVALID_REQUEST',
56+
status: '400',
57+
title: 'Invalid Request',
58+
detail: 'The messageId path parameter is not a valid UUID.',
59+
source: { parameter: 'messageId' }
60+
}
61+
]
4762
})
4863
.expect('Content-Type', /json/, done);
4964
});
@@ -52,17 +67,30 @@ describe('/api/v1/message-responses/:messageId', () => {
5267
request(server)
5368
.get(`/api/v1/message-responses/${NOT_FOUND_MESSAGE_ID}`)
5469
.expect(404, {
55-
error: 'No responses found for the specified messageId'
70+
errors: [
71+
{
72+
code: 'CM_NOT_FOUND',
73+
status: '404',
74+
title: 'Resource not found',
75+
detail: 'The resource at the requested URI was not found.'
76+
}
77+
]
5678
})
5779
.expect('Content-Type', /json/, done);
5880
});
5981

6082
it('returns a 500 when too many responses are returned', (done) => {
6183
request(server)
6284
.get(`/api/v1/message-responses/${TOO_MANY_RESPONSES_MESSAGE_ID}`)
63-
.expect(422, {
64-
error: 'response_too_large',
65-
message: 'There are too many responses to return.'
85+
.expect(500, {
86+
errors: [
87+
{
88+
code: 'CM_TOO_MANY_RESPONSES',
89+
status: '500',
90+
title: 'Too many responses',
91+
detail: 'There are too many responses to return.'
92+
}
93+
]
6694
})
6795
.expect('Content-Type', /json/, done);
6896
});
@@ -72,7 +100,15 @@ describe('/api/v1/message-responses/:messageId', () => {
72100
.get(`/api/v1/message-responses/${VALID_MESSAGE_ID}`)
73101
.set('Content-Type', 'text/plain')
74102
.expect(415, {
75-
message: 'Unsupported media type.'
103+
errors: [
104+
{
105+
code: 'CM_UNSUPPORTED_MEDIA',
106+
status: '415',
107+
title: 'Unsupported media',
108+
detail: 'Invalid content-type, this API only supports application/vnd.api+json or application/json.',
109+
source: { header: 'Content-Type' }
110+
}
111+
]
76112
})
77113
.expect('Content-Type', /json/, done);
78114
});
@@ -82,7 +118,14 @@ describe('/api/v1/message-responses/:messageId', () => {
82118
.get(`/api/v1/message-responses/${VALID_MESSAGE_ID}`)
83119
.set('Prefer', 'code=429')
84120
.expect(429, {
85-
message: 'Too many requests.'
121+
errors: [
122+
{
123+
code: 'CM_QUOTA',
124+
status: '429',
125+
title: 'Too many requests',
126+
detail: 'You have made too many requests. Re-send the request after the time (in seconds) specified `Retry-After` header.'
127+
}
128+
]
86129
})
87130
.expect('Content-Type', /json/, done);
88131
});
@@ -94,17 +137,19 @@ describe('/api/v1/message-responses/:messageId', () => {
94137
.expect('Content-Type', /json/)
95138
.expect((res) => {
96139
const { body } = res;
97-
if (!Array.isArray(body)) throw new Error('response must be an array');
98-
const first = body[0];
99-
if (!first.responseId) throw new Error('missing responseId');
100-
if (first.messageId !== VALID_MESSAGE_ID) throw new Error('incorrect messageId');
101-
if (!first.messageReference) throw new Error('missing messageReference');
102-
if (!first.code) throw new Error('missing code');
103-
if (!first.channel) throw new Error('missing channel');
104-
if (!first.channelStatus) throw new Error('missing channelStatus');
105-
if (!first.cascadeType) throw new Error('missing cascadeType');
106-
if (!first.authoredAt) throw new Error('missing authoredAt');
107-
if (!first.timestamp) throw new Error('missing timestamp');
140+
if (!Array.isArray(body.data)) throw new Error('response must contain a data array');
141+
const first = body.data[0];
142+
if (!first.id) throw new Error('missing id');
143+
if (first.type !== 'RecipientResponseSnapshot') throw new Error('incorrect type');
144+
const { attributes } = first;
145+
if (attributes.messageId !== VALID_MESSAGE_ID) throw new Error('incorrect messageId');
146+
if (!attributes.messageReference) throw new Error('missing messageReference');
147+
if (!attributes.code) throw new Error('missing code');
148+
if (!attributes.channel) throw new Error('missing channel');
149+
if (!attributes.channelStatus) throw new Error('missing channelStatus');
150+
if (!attributes.cascadeType) throw new Error('missing cascadeType');
151+
if (!attributes.authoredAt) throw new Error('missing authoredAt');
152+
if (!attributes.timestamp) throw new Error('missing timestamp');
108153
})
109154
.end(done);
110155
});
Lines changed: 96 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,99 @@
1-
import { sendError } from './utils.js'
2-
31
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
42

53
const notFoundMessageId = '00000000-0000-4000-8000-000000000404';
6-
const tooManyResponsesMessageId = '00000000-0000-4000-8000-000000000422';
4+
const tooManyResponsesMessageId = '00000000-0000-4000-8000-000000000500';
5+
const supportedContentTypes = ['application/json', 'application/vnd.api+json'];
76

87
export async function messageResponses(req, res, next) {
98
if (req.headers.authorization === 'banned') {
10-
res.status(403).json({ error: 'Forbidden' });
9+
res.status(403).json({
10+
errors: [
11+
{
12+
code: 'CM_FORBIDDEN',
13+
status: '403',
14+
title: 'Forbidden',
15+
detail: 'Client not recognised or not yet onboarded.'
16+
}
17+
]
18+
});
1119
next();
1220
return;
1321
}
1422

15-
if (req.headers['content-type'] && req.headers['content-type'] !== 'application/json') {
16-
sendError(res, 415, 'Unsupported media type.');
23+
if (req.headers['content-type'] && !supportedContentTypes.includes(req.headers['content-type'])) {
24+
res.status(415).json({
25+
errors: [
26+
{
27+
code: 'CM_UNSUPPORTED_MEDIA',
28+
status: '415',
29+
title: 'Unsupported media',
30+
detail: 'Invalid content-type, this API only supports application/vnd.api+json or application/json.',
31+
source: { header: 'Content-Type' }
32+
}
33+
]
34+
});
1735
next();
1836
return;
1937
}
2038

2139
if (req.headers.prefer === 'code=429') {
22-
sendError(res, 429, 'Too many requests.');
40+
res.status(429).json({
41+
errors: [
42+
{
43+
code: 'CM_QUOTA',
44+
status: '429',
45+
title: 'Too many requests',
46+
detail: 'You have made too many requests. Re-send the request after the time (in seconds) specified `Retry-After` header.'
47+
}
48+
]
49+
});
2350
next();
2451
return;
2552
}
2653

2754
const { messageId } = req.params;
2855

2956
if (!uuidRegex.test(messageId)) {
30-
res.status(400).json({ error: 'Invalid messageId format' });
57+
res.status(400).json({
58+
errors: [
59+
{
60+
code: 'CM_INVALID_REQUEST',
61+
status: '400',
62+
title: 'Invalid Request',
63+
detail: 'The messageId path parameter is not a valid UUID.',
64+
source: { parameter: 'messageId' }
65+
}
66+
]
67+
});
3168
next();
3269
return;
3370
}
3471

3572
if (messageId === notFoundMessageId) {
36-
res.status(404).json({ error: 'No responses found for the specified messageId' });
73+
res.status(404).json({
74+
errors: [
75+
{
76+
code: 'CM_NOT_FOUND',
77+
status: '404',
78+
title: 'Resource not found',
79+
detail: 'The resource at the requested URI was not found.'
80+
}
81+
]
82+
});
3783
next();
3884
return;
3985
}
4086

4187
if (messageId === tooManyResponsesMessageId) {
42-
res.status(422).json({
43-
error: 'response_too_large',
44-
message: 'There are too many responses to return.'
88+
res.status(500).json({
89+
errors: [
90+
{
91+
code: 'CM_TOO_MANY_RESPONSES',
92+
status: '500',
93+
title: 'Too many responses',
94+
detail: 'There are too many responses to return.'
95+
}
96+
]
4597
});
4698
next();
4799
return;
@@ -51,28 +103,36 @@ export async function messageResponses(req, res, next) {
51103
}
52104

53105
function getDefaultResponse(messageId) {
54-
return [
55-
{
56-
responseId: '22222222-2222-4222-8222-222222222222',
57-
messageId,
58-
messageReference: 'msg-ref-1',
59-
channel: 'nhsapp',
60-
channelStatus: 'delivered',
61-
cascadeType: 'primary',
62-
code: 'YES',
63-
authoredAt: '2026-01-02T09:00:00.000Z',
64-
timestamp: '2026-01-02T09:00:02.345Z'
65-
},
66-
{
67-
responseId: '33333333-3333-4333-8333-333333333333',
68-
messageId,
69-
messageReference: 'msg-ref-1',
70-
channel: 'nhsapp',
71-
channelStatus: 'delivered',
72-
cascadeType: 'secondary',
73-
code: 'NO',
74-
authoredAt: '2026-01-02T09:05:00.000Z',
75-
timestamp: '2026-01-02T09:05:01.678Z'
76-
}
77-
];
106+
return {
107+
data: [
108+
{
109+
type: 'RecipientResponseSnapshot',
110+
id: '22222222-2222-4222-8222-222222222222',
111+
attributes: {
112+
messageId,
113+
messageReference: 'msg-ref-1',
114+
channel: 'nhsapp',
115+
channelStatus: 'delivered',
116+
cascadeType: 'primary',
117+
code: 'YES',
118+
authoredAt: '2026-01-02T09:00:00.000Z',
119+
timestamp: '2026-01-02T09:00:02.345Z'
120+
}
121+
},
122+
{
123+
type: 'RecipientResponseSnapshot',
124+
id: '33333333-3333-4333-8333-333333333333',
125+
attributes: {
126+
messageId,
127+
messageReference: 'msg-ref-1',
128+
channel: 'nhsapp',
129+
channelStatus: 'delivered',
130+
cascadeType: 'secondary',
131+
code: 'NO',
132+
authoredAt: '2026-01-02T09:05:00.000Z',
133+
timestamp: '2026-01-02T09:05:01.678Z'
134+
}
135+
}
136+
]
137+
};
78138
}

‎specification/documentation/GetMessageResponses.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,5 @@ Here's an example curl command:
6565

6666
```
6767
curl -X GET 'https://sandbox.api.service.nhs.uk/comms/v1/message-responses/11111111-1111-4111-8111-111111111111' \
68-
--header 'Accept: application/json'
68+
--header 'Accept: application/vnd.api+json'
6969
```

‎specification/responses/4xx/415_UnsupportedMedia.yaml‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
description: |+
2-
The `Content-Type` of the request is not supported. This endpoint supports `application/json`.
2+
The `Content-Type` of the request is not supported. This endpoint supports `application/vnd.api+json` and `application/json`.
33
content:
4+
application/vnd.api+json:
5+
schema:
6+
$ref: ../../schemas/responses/errors/UnsupportedMedia.yaml
47
application/json:
58
schema:
69
$ref: ../../schemas/responses/errors/UnsupportedMedia.yaml

0 commit comments

Comments
 (0)