Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions srv/channel-submission-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ export default class ChannelSubmissionService extends cds.ApplicationService {
async init() {
// Stamp server-controlled fields; never trust client-sent status / reviewer / submitter.
this.before('CREATE', 'Submissions', (req) => {
// EDIT / REMOVE act on an existing channel — reject at submit time (400) instead of
// letting the row sit PENDING only to fail at approve. ADD carries no target.
if ((req.data.kind === 'EDIT' || req.data.kind === 'REMOVE') && !req.data.targetChannel_ID) {
return req.reject(400, `A target channel is required for ${req.data.kind} submissions.`, 'targetChannel_ID');
}
req.data.submitterId = req.user.id;
req.data.status = 'PENDING';
req.data.reviewerId = null;
Expand Down
18 changes: 16 additions & 2 deletions test/channel-submission-service.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,23 @@ describe('ChannelSubmissionService', () => {
expect(row.reviewNote).toBeNull();
});

test('service is insert-only — READ is not allowed', async () => {
test('service is insert-only — READ is rejected 405 Method Not Allowed', async () => {
// CAP 10 answers a READ on an @insertonly entity with 405 (not a generic 4xx/5xx).
// Asserting the exact code proves the entity is insert-only rather than merely erroring.
await expect(
project.get('/channel-submissions/Submissions', authUser),
).rejects.toMatchObject({ response: { status: expect.any(Number) } });
).rejects.toMatchObject({ response: { status: 405 } });
});

test('EDIT without a target channel is rejected at submit time (400)', async () => {
await expect(
project.post('/channel-submissions/Submissions', { kind: 'EDIT', proposed: '{}' }, authUser),
).rejects.toMatchObject({ response: { status: 400 } });
});

test('REMOVE without a target channel is rejected at submit time (400)', async () => {
await expect(
project.post('/channel-submissions/Submissions', { kind: 'REMOVE', proposed: '{}' }, authUser),
).rejects.toMatchObject({ response: { status: 400 } });
});
});
Loading