From ad105a377ae0eae2f8a1e5a596efd95f1e426f05 Mon Sep 17 00:00:00 2001 From: Jake Bromberg Date: Sat, 21 Feb 2026 08:27:23 -0800 Subject: [PATCH 1/5] fix: return 201 Created for all resource creation endpoints All create endpoints (flowsheet entries, bin entries, albums, artists, rotation, formats, genres, schedule) returned 200 instead of 201. Co-authored-by: Cursor --- apps/backend/controllers/djs.controller.ts | 2 +- .../controllers/flowsheet.controller.ts | 6 +- .../backend/controllers/library.controller.ts | 10 ++-- .../controllers/schedule.controller.ts | 2 +- .../controllers/create-status-codes.test.ts | 57 +++++++++++++++++++ 5 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 tests/unit/controllers/create-status-codes.test.ts diff --git a/apps/backend/controllers/djs.controller.ts b/apps/backend/controllers/djs.controller.ts index 1e160cf..d179fc7 100644 --- a/apps/backend/controllers/djs.controller.ts +++ b/apps/backend/controllers/djs.controller.ts @@ -21,7 +21,7 @@ export const addToBin: RequestHandler = async (req, re }; try { const added_bin_item = await DJService.addToBin(bin_entry); - res.status(200).json(added_bin_item); + res.status(201).json(added_bin_item); } catch (e) { console.error('Server error: Failed to insert into bin'); console.error(e); diff --git a/apps/backend/controllers/flowsheet.controller.ts b/apps/backend/controllers/flowsheet.controller.ts index 9ac09f2..a8c933c 100644 --- a/apps/backend/controllers/flowsheet.controller.ts +++ b/apps/backend/controllers/flowsheet.controller.ts @@ -206,7 +206,7 @@ export const addEntry: RequestHandler = async (req: Request console.error('[Flowsheet] Metadata fetch failed:', err)); } - res.status(200).json(completedEntry); + res.status(201).json(completedEntry); } else if ( body.album_title === undefined || body.artist_name === undefined || @@ -234,7 +234,7 @@ export const addEntry: RequestHandler = async (req: Request console.error('[Flowsheet] Metadata fetch failed:', err)); } - res.status(200).json(completedEntry); + res.status(201).json(completedEntry); } } catch (e) { console.error('Error: Failed to add track to flowsheet'); @@ -253,7 +253,7 @@ export const addEntry: RequestHandler = async (req: Request = } else { try { const rotationRelease: RotationRelease = await libraryService.addToRotation(req.body); - res.status(200).json(rotationRelease); + res.status(201).json(rotationRelease); } catch (e) { console.error(e); next(e); @@ -279,7 +279,7 @@ export const addFormat: RequestHandler = async (req, res, next) => { }; const insertion = await libraryService.insertFormat(newFormat); - res.status(200).json(insertion); + res.status(201).json(insertion); } catch (e) { console.error('Failed to add new format'); console.error(e); @@ -309,7 +309,7 @@ export const addGenre: RequestHandler = async (req, res, next) => { const insertion = await libraryService.insertGenre(newGenre); - res.status(200).json(insertion); + res.status(201).json(insertion); } catch (e) { console.error('Failed to add new genre'); console.error(e); diff --git a/apps/backend/controllers/schedule.controller.ts b/apps/backend/controllers/schedule.controller.ts index 48b4153..db6aa43 100644 --- a/apps/backend/controllers/schedule.controller.ts +++ b/apps/backend/controllers/schedule.controller.ts @@ -17,7 +17,7 @@ export const addToSchedule: RequestHandler = async (req: Request = {}) { + const req = { body } as unknown as Request; + const res = { + status: jest.fn().mockReturnThis(), + json: jest.fn().mockReturnThis(), + send: jest.fn().mockReturnThis(), + } as unknown as Response; + const next = jest.fn() as unknown as NextFunction; + return { req, res, next }; +} + +describe('create endpoints return 201', () => { + describe('addToBin', () => { + it('should return 201 when a bin entry is created', async () => { + const created = { id: 1, dj_id: 'dj-1', album_id: 10, track_title: null }; + (DJService.addToBin as jest.Mock).mockResolvedValue(created); + + const { req, res, next } = mockReqResNext({ + dj_id: 'dj-1', + album_id: 10, + }); + + await addToBin(req, res, next); + + expect(res.status).toHaveBeenCalledWith(201); + expect(res.json).toHaveBeenCalledWith(created); + }); + }); + + describe('addToSchedule', () => { + it('should return 201 when a schedule entry is created', async () => { + const created = { id: 1, day: 'Monday', start_time: '10:00', end_time: '12:00' }; + (ScheduleService.addToSchedule as jest.Mock).mockResolvedValue(created); + + const { req, res, next } = mockReqResNext({ + day: 'Monday', + start_time: '10:00', + end_time: '12:00', + }); + + await addToSchedule(req, res, next); + + expect(res.status).toHaveBeenCalledWith(201); + expect(res.json).toHaveBeenCalledWith(created); + }); + }); +}); From f18b4e5b66129faa0106866c103a25f571ab7d6c Mon Sep 17 00:00:00 2001 From: Jake Bromberg Date: Fri, 27 Feb 2026 11:04:50 -0800 Subject: [PATCH 2/5] fix: resolve lint errors --- .../controllers/create-status-codes.test.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/tests/unit/controllers/create-status-codes.test.ts b/tests/unit/controllers/create-status-codes.test.ts index 1db5fe2..12ded7f 100644 --- a/tests/unit/controllers/create-status-codes.test.ts +++ b/tests/unit/controllers/create-status-codes.test.ts @@ -10,13 +10,16 @@ import { addToSchedule } from '../../../apps/backend/controllers/schedule.contro function mockReqResNext(body: Record = {}) { const req = { body } as unknown as Request; + const statusMock = jest.fn().mockReturnThis(); + const jsonMock = jest.fn().mockReturnThis(); + const sendMock = jest.fn().mockReturnThis(); const res = { - status: jest.fn().mockReturnThis(), - json: jest.fn().mockReturnThis(), - send: jest.fn().mockReturnThis(), + status: statusMock, + json: jsonMock, + send: sendMock, } as unknown as Response; const next = jest.fn() as unknown as NextFunction; - return { req, res, next }; + return { req, res, next, statusMock, jsonMock, sendMock }; } describe('create endpoints return 201', () => { @@ -25,15 +28,15 @@ describe('create endpoints return 201', () => { const created = { id: 1, dj_id: 'dj-1', album_id: 10, track_title: null }; (DJService.addToBin as jest.Mock).mockResolvedValue(created); - const { req, res, next } = mockReqResNext({ + const { req, res, next, statusMock, jsonMock } = mockReqResNext({ dj_id: 'dj-1', album_id: 10, }); await addToBin(req, res, next); - expect(res.status).toHaveBeenCalledWith(201); - expect(res.json).toHaveBeenCalledWith(created); + expect(statusMock).toHaveBeenCalledWith(201); + expect(jsonMock).toHaveBeenCalledWith(created); }); }); @@ -42,7 +45,7 @@ describe('create endpoints return 201', () => { const created = { id: 1, day: 'Monday', start_time: '10:00', end_time: '12:00' }; (ScheduleService.addToSchedule as jest.Mock).mockResolvedValue(created); - const { req, res, next } = mockReqResNext({ + const { req, res, next, statusMock, jsonMock } = mockReqResNext({ day: 'Monday', start_time: '10:00', end_time: '12:00', @@ -50,8 +53,8 @@ describe('create endpoints return 201', () => { await addToSchedule(req, res, next); - expect(res.status).toHaveBeenCalledWith(201); - expect(res.json).toHaveBeenCalledWith(created); + expect(statusMock).toHaveBeenCalledWith(201); + expect(jsonMock).toHaveBeenCalledWith(created); }); }); }); From 4be7e5ef04432c14f5baaa30a3cd2b271a1f5b96 Mon Sep 17 00:00:00 2001 From: Jake Bromberg Date: Fri, 27 Feb 2026 14:26:36 -0800 Subject: [PATCH 3/5] test: update flowsheet integration tests to expect 201 for creation endpoints The addEntry controller now returns 201 Created instead of 200 OK. Update all POST /flowsheet assertions in the integration tests accordingly. --- tests/integration/flowsheet.spec.js | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/integration/flowsheet.spec.js b/tests/integration/flowsheet.spec.js index 5cda171..bd34b8e 100644 --- a/tests/integration/flowsheet.spec.js +++ b/tests/integration/flowsheet.spec.js @@ -167,7 +167,7 @@ describe('Add to Flowsheet', () => { track_title: 'Carry the Zero', // record_label: 'Warner Bros', }) - .expect(200); + .expect(201); expect(res.body).toBeDefined(); expect(res.body.album_title).toEqual('Keep it Like a Secret'); @@ -182,7 +182,7 @@ describe('Add to Flowsheet', () => { track_title: 'Carry the Zero', rotation_id: 1, }) - .expect(200); + .expect(201); expect(res.body).toBeDefined(); expect(res.body.album_title).toEqual('Keep it Like a Secret'); @@ -197,7 +197,7 @@ describe('Add to Flowsheet', () => { track_title: 'Carry the Zero', record_label: 'Warner Bros', }) - .expect(200); + .expect(201); expect(res.body.album_title).toEqual('Keep it Like a Secret'); expect(res.body.track_title).toEqual('Carry the Zero'); @@ -213,7 +213,7 @@ describe('Add to Flowsheet', () => { track_title: 'Carry the Zero', request_flag: true, }) - .expect(200); + .expect(201); expect(res.body).toBeDefined(); expect(res.body.album_title).toEqual('Keep it Like a Secret'); @@ -229,7 +229,7 @@ describe('Add to Flowsheet', () => { album_title: 'Keep it Like a Secret', track_title: 'Carry the Zero', }) - .expect(200); + .expect(201); expect(res.body).toBeDefined(); expect(res.body.album_title).toEqual('Keep it Like a Secret'); @@ -246,7 +246,7 @@ describe('Add to Flowsheet', () => { track_title: 'Carry the Zero', record_label: 'Warner Bros', }) - .expect(200); + .expect(201); expect(res.body).toBeDefined(); expect(res.body.album_title).toEqual('Keep it Like a Secret'); @@ -264,7 +264,7 @@ describe('Add to Flowsheet', () => { track_title: 'Carry the Zero', request_flag: true, }) - .expect(200); + .expect(201); expect(res.body).toBeDefined(); expect(res.body.album_title).toEqual('Keep it Like a Secret'); @@ -278,7 +278,7 @@ describe('Add to Flowsheet', () => { .send({ message: 'Test Message', }) - .expect(200); + .expect(201); expect(res.body).toBeDefined(); expect(res.body.message).toEqual('Test Message'); @@ -492,7 +492,7 @@ describe('Retrieve Now Playing', () => { album_id: 1, //Built to Spill - Keep it Like a Secret track_title: 'Carry the Zero', }) - .expect(200); + .expect(201); }); afterEach(async () => { @@ -515,7 +515,7 @@ describe('Retrieve Now Playing', () => { album_id: 2, //Ravyn Lenae - Crush track_title: 'Venom', }) - .expect(200); + .expect(201); res = await request.get('/flowsheet/latest').expect(200); expect(res.body).toBeDefined(); @@ -537,7 +537,7 @@ describe('Shift Flowsheet Entries', () => { album_id: 1, //Built to Spill - Keep it Like a Secret track_title: 'Carry the Zero', }) - .expect(200); + .expect(201); await request .post('/flowsheet') @@ -546,7 +546,7 @@ describe('Shift Flowsheet Entries', () => { album_id: 2, //Ravyn Lenae - Crush track_title: 'Venom', }) - .expect(200); + .expect(201); await request .post('/flowsheet') @@ -555,7 +555,7 @@ describe('Shift Flowsheet Entries', () => { album_id: 3, //Jockstrap - I Love You Jennifer B track_title: 'Debra', }) - .expect(200); + .expect(201); }); afterEach(async () => { @@ -700,7 +700,7 @@ describe('Retrieve Playlist Object', () => { album_id: 3, //Jockstrap - I Love You Jennifer B track_title: 'Debra', }) - .expect(200); + .expect(201); await fls_util.leave_show(global.primary_dj_id, global.access_token); }); @@ -771,7 +771,7 @@ describe('V1 API - entry_type field', () => { album_id: 1, track_title: 'Carry the Zero', }) - .expect(200); + .expect(201); // POST response includes entry_type expect(addRes.body.entry_type).toBe('track'); From ef5e8043c9e271d247182c3949eb471252e53c65 Mon Sep 17 00:00:00 2001 From: Jake Bromberg Date: Mon, 9 Mar 2026 23:08:16 -0700 Subject: [PATCH 4/5] test: update integration tests to expect 201 for all create endpoints Updates remaining .expect(200) to .expect(201) in djs, library, and schedule integration tests for POST endpoints that create resources. --- tests/integration/djs.spec.js | 4 ++-- tests/integration/library.spec.js | 12 ++++++------ tests/integration/schedule.spec.js | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/integration/djs.spec.js b/tests/integration/djs.spec.js index 46e0cc3..c1e0800 100644 --- a/tests/integration/djs.spec.js +++ b/tests/integration/djs.spec.js @@ -52,7 +52,7 @@ describe('DJ Bin', () => { dj_id: global.primary_dj_id, album_id: 1, }) - .expect(200); + .expect(201); expectFields(res.body, 'album_id', 'dj_id'); expect(res.body.album_id).toBe(1); @@ -67,7 +67,7 @@ describe('DJ Bin', () => { album_id: 1, track_title: 'Carry the Zero', }) - .expect(200); + .expect(201); expectFields(res.body, 'album_id', 'track_title'); expect(res.body.album_id).toBe(1); diff --git a/tests/integration/library.spec.js b/tests/integration/library.spec.js index 6dd4851..9b95ec7 100644 --- a/tests/integration/library.spec.js +++ b/tests/integration/library.spec.js @@ -87,7 +87,7 @@ describe('Library Catalog', () => { genre_id: 1, format_id: 1, }) - .expect(200); + .expect(201); expectFields(res.body, 'id', 'album_title'); expect(res.body.album_title).toContain('Test Album'); @@ -204,7 +204,7 @@ describe('Library Rotation', () => { album_id: 2, rotation_bin: 'M', }) - .expect(200); + .expect(201); expectFields(res.body, 'id', 'album_id', 'rotation_bin'); expect(res.body.album_id).toBe(2); @@ -323,7 +323,7 @@ describe('Library Artists', () => { genre_id: 1, code_number: 1, }) - .expect(200); + .expect(201); expectFields(res.body, 'id', 'artist_name', 'alphabetical_name', 'code_letters', 'code_number'); expect(res.body.artist_name).toContain('Test Artist'); @@ -394,7 +394,7 @@ describe('Library Artists', () => { genre_id: 1, code_number: 1, }) - .expect(200); + .expect(201); expect(res.body.alphabetical_name).toBe(`Band ${uniqueSuffix}, The`); }); @@ -448,7 +448,7 @@ describe('Library Formats', () => { .send({ name: `Test Format ${uniqueSuffix}`, }) - .expect(200); + .expect(201); expectFields(res.body, 'id', 'format_name'); expect(res.body.format_name).toContain('Test Format'); @@ -495,7 +495,7 @@ describe('Library Genres', () => { name: `Test Genre ${uniqueSuffix}`, description: 'A test genre for integration testing', }) - .expect(200); + .expect(201); expectFields(res.body, 'id', 'genre_name'); expect(res.body.genre_name).toContain('Test Genre'); diff --git a/tests/integration/schedule.spec.js b/tests/integration/schedule.spec.js index f3c219a..0ab40dd 100644 --- a/tests/integration/schedule.spec.js +++ b/tests/integration/schedule.spec.js @@ -51,7 +51,7 @@ describe('Schedule', () => { show_duration: 8, }; - const res = await request.post('/schedule').send(newShift).expect(200); + const res = await request.post('/schedule').send(newShift).expect(201); expectFields(res.body, 'id', 'day', 'start_time', 'show_duration'); expect(res.body.day).toBe(0); @@ -71,7 +71,7 @@ describe('Schedule', () => { specialty_id: null, }; - const res = await request.post('/schedule').send(newShift).expect(200); + const res = await request.post('/schedule').send(newShift).expect(201); expect(res.body.day).toBe(2); @@ -96,7 +96,7 @@ describe('Schedule', () => { show_duration: 4, }; - const res = await request.post('/schedule').send(newShift).expect(200); + const res = await request.post('/schedule').send(newShift).expect(201); expect(res.body.day).toBe(day); @@ -113,7 +113,7 @@ describe('Schedule', () => { show_duration: 2, }; - const res = await request.post('/schedule').send(newShift).expect(200); + const res = await request.post('/schedule').send(newShift).expect(201); expect(res.body.start_time).toBe('08:30:00'); @@ -132,7 +132,7 @@ describe('Schedule', () => { show_duration: 4, }; - const postRes = await request.post('/schedule').send(newShift).expect(200); + const postRes = await request.post('/schedule').send(newShift).expect(201); if (postRes.body.id) { createdScheduleIds.push(postRes.body.id); From 0d923da7f8e1bffa9366d8bfa9f92b7c4d11aaff Mon Sep 17 00:00:00 2001 From: Jake Bromberg Date: Mon, 9 Mar 2026 23:55:17 -0700 Subject: [PATCH 5/5] test: update metadata.spec.js to expect 201 for POST /flowsheet All 19 POST /flowsheet calls in the metadata integration tests were still expecting 200, but the controller now returns 201 for successful creation. Updated to match. --- tests/integration/metadata.spec.js | 38 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/integration/metadata.spec.js b/tests/integration/metadata.spec.js index f204cee..dbadd98 100644 --- a/tests/integration/metadata.spec.js +++ b/tests/integration/metadata.spec.js @@ -34,7 +34,7 @@ describe('Metadata Fields in Flowsheet Response', () => { album_id: 4, // Sufjan Stevens - Illinois track_title: 'Chicago', }) - .expect(200); + .expect(201); // Get the flowsheet entries const getRes = await request.get('/flowsheet').query({ limit: 5 }).send().expect(200); @@ -65,7 +65,7 @@ describe('Metadata Fields in Flowsheet Response', () => { album_id: 5, // Kendrick Lamar - To Pimp a Butterfly track_title: 'Alright', }) - .expect(200); + .expect(201); const res = await request.get('/flowsheet/latest').expect(200); @@ -97,7 +97,7 @@ describe('Fire-and-Forget Metadata Fetch', () => { album_id: 4, // Sufjan Stevens - Illinois track_title: 'Casimir Pulaski Day', }) - .expect(200); + .expect(201); // Response should return immediately with the entry expect(addRes.body.id).toBeDefined(); @@ -124,7 +124,7 @@ describe('Fire-and-Forget Metadata Fetch', () => { album_title: 'OK Computer', track_title: 'Paranoid Android', }) - .expect(200); + .expect(201); // Response should return immediately expect(addRes.body.id).toBeDefined(); @@ -145,7 +145,7 @@ describe('Fire-and-Forget Metadata Fetch', () => { .send({ message: 'PSA: Station ID at the top of the hour', }) - .expect(200); + .expect(201); const entryId = addRes.body.id; @@ -183,7 +183,7 @@ describe('Flowsheet CRUD with Metadata', () => { album_id: 5, track_title: 'CRUD Test Track', }) - .expect(200); + .expect(201); // Get entries - should include new entry const res = await request.get('/flowsheet').query({ limit: 10 }).send().expect(200); @@ -201,7 +201,7 @@ describe('Flowsheet CRUD with Metadata', () => { album_id: 6, track_title: 'Delete Test Track', }) - .expect(200); + .expect(201); const entryId = addRes.body.id; @@ -230,7 +230,7 @@ describe('Flowsheet CRUD with Metadata', () => { album_id: 4, track_title: 'Update Test Original', }) - .expect(200); + .expect(201); const entryId = addRes.body.id; @@ -279,7 +279,7 @@ describe('Metadata with Rotation Entries', () => { track_title: 'Rotation Test Track', rotation_id: 2, }) - .expect(200); + .expect(201); // Get the entry const getRes = await request.get('/flowsheet').query({ limit: 5 }).send().expect(200); @@ -316,7 +316,7 @@ describe('Flowsheet Cache Behavior', () => { album_id: 4, track_title: 'Cache Consistency Test', }) - .expect(200); + .expect(201); // Query multiple times - should return consistent results const res1 = await request.get('/flowsheet').query({ limit: 10 }).send().expect(200); @@ -339,7 +339,7 @@ describe('Flowsheet Cache Behavior', () => { album_id: 5, track_title: 'Cache Invalidation Test Add', }) - .expect(200); + .expect(201); // Query - should include the new entry (proves cache was invalidated) const afterRes = await request.get('/flowsheet').query({ limit: 50 }).send().expect(200); @@ -357,7 +357,7 @@ describe('Flowsheet Cache Behavior', () => { album_id: 4, track_title: 'Cache Invalidation Test Delete', }) - .expect(200); + .expect(201); const entryId = addRes.body.id; @@ -386,7 +386,7 @@ describe('Flowsheet Cache Behavior', () => { album_id: 4, track_title: 'Cache Update Original', }) - .expect(200); + .expect(201); const entryId = addRes.body.id; @@ -440,7 +440,7 @@ describe('Conditional GET (304 Not Modified)', () => { album_id: 4, track_title: 'Last-Modified Header Test', }) - .expect(200); + .expect(201); const res = await request.get('/flowsheet/latest').expect(200); @@ -479,7 +479,7 @@ describe('Conditional GET (304 Not Modified)', () => { album_id: 4, track_title: 'Modification Test Track', }) - .expect(200); + .expect(201); // Request with old If-Modified-Since should return 200 with new data const updatedRes = await request @@ -504,7 +504,7 @@ describe('Conditional GET (304 Not Modified)', () => { album_id: 5, track_title: 'Latest 304 Test', }) - .expect(200); + .expect(201); // First request to get timestamp const initialRes = await request.get('/flowsheet/latest').expect(200); @@ -551,7 +551,7 @@ describe('Conditional GET (304 Not Modified)', () => { album_id: 4, track_title: 'Since Query Param Test', }) - .expect(200); + .expect(201); // Request with old since param should return 200 with new data const updatedRes = await request.get('/flowsheet').query({ limit: 10, since: lastModified }).send().expect(200); @@ -578,7 +578,7 @@ describe('Conditional GET (304 Not Modified)', () => { album_id: 4, track_title: 'Precedence Test Track', }) - .expect(200); + .expect(201); // Get the new Last-Modified const updatedRes = await request.get('/flowsheet').query({ limit: 10 }).send().expect(200); @@ -603,7 +603,7 @@ describe('Conditional GET (304 Not Modified)', () => { album_id: 5, track_title: 'Latest Since Test', }) - .expect(200); + .expect(201); // First request to get timestamp const initialRes = await request.get('/flowsheet/latest').expect(200);