From d339af1a95ef47c871285aa3e1a1f00ef3debc4d Mon Sep 17 00:00:00 2001 From: Jake Bromberg Date: Sat, 28 Feb 2026 10:09:17 -0800 Subject: [PATCH 1/4] Add library code lookup, reviews API, and album update endpoint Implement the library code lookup TODO at library.controller.ts:105-108: - lookupByLibraryCode() queries library_artist_view by code_letters, code_artist_number, with optional code_number and genre_name filters Add reviews API (GET/PUT /library/reviews): - getReviewByAlbumId() returns review for an album - upsertReview() uses onConflictDoUpdate on album_id unique constraint Add album update endpoint (PATCH /library): - updateAlbumFields() updates label and/or album_title on a library row All endpoints use existing catalog:read/catalog:write permissions. Includes unit tests for all new service functions. --- .../backend/controllers/library.controller.ts | 47 +++- apps/backend/controllers/review.controller.ts | 48 +++++ apps/backend/routes/library.route.ts | 7 + apps/backend/services/library.service.ts | 44 +++- apps/backend/services/review.service.ts | 32 +++ tests/mocks/database.mock.ts | 3 + .../library.service.code-lookup.test.ts | 201 ++++++++++++++++++ tests/unit/services/review.service.test.ts | 146 +++++++++++++ 8 files changed, 524 insertions(+), 4 deletions(-) create mode 100644 apps/backend/controllers/review.controller.ts create mode 100644 apps/backend/services/review.service.ts create mode 100644 tests/unit/services/library.service.code-lookup.test.ts create mode 100644 tests/unit/services/review.service.test.ts diff --git a/apps/backend/controllers/library.controller.ts b/apps/backend/controllers/library.controller.ts index 2210e03..40f3c65 100644 --- a/apps/backend/controllers/library.controller.ts +++ b/apps/backend/controllers/library.controller.ts @@ -83,6 +83,7 @@ type AlbumQueryParams = { code_letters?: string; code_artist_number?: string; code_number?: number; + genre_name?: string; n?: number; page?: number; }; @@ -103,9 +104,19 @@ export const searchForAlbum: RequestHandler = async ( 'Missing query parameter. Query must include: artist_name, album_title, or code_letters, code_artist_number, and code_number' ); } else if (query.code_letters !== undefined && query.code_artist_number !== undefined) { - //quickly look up albums by that artist - res.status(501); - res.send('TODO: Library Code Lookup'); + try { + const response = await libraryService.lookupByLibraryCode( + query.code_letters, + parseInt(query.code_artist_number), + query.code_number !== undefined ? Number(query.code_number) : undefined, + query.genre_name + ); + res.status(200).json(response); + } catch (e) { + console.error('Error: Library code lookup failed'); + console.error(e); + next(e); + } } else { try { const response = await libraryService.fuzzySearchLibrary(query.artist_name, query.album_title, query.n); @@ -283,3 +294,33 @@ export const getAlbum: RequestHandler, + res, + next +) => { + const { body } = req; + if (body.album_id === undefined) { + res.status(400).send('Bad Request, Missing Parameter: album_id'); + } else { + try { + const fields: { label?: string; album_title?: string } = {}; + if (body.label !== undefined) fields.label = body.label; + if (body.album_title !== undefined) fields.album_title = body.album_title; + + const updated = await libraryService.updateAlbumFields(body.album_id, fields); + res.status(200).json(updated); + } catch (e) { + console.error('Error updating album'); + console.error(e); + next(e); + } + } +}; diff --git a/apps/backend/controllers/review.controller.ts b/apps/backend/controllers/review.controller.ts new file mode 100644 index 0000000..4074bfb --- /dev/null +++ b/apps/backend/controllers/review.controller.ts @@ -0,0 +1,48 @@ +import { Request, RequestHandler } from 'express'; +import * as reviewService from '../services/review.service.js'; + +export const getReview: RequestHandler = async ( + req: Request, + res, + next +) => { + const { query } = req; + if (query.album_id === undefined) { + res.status(400).send('Bad Request, Missing Parameter: album_id'); + } else { + try { + const review = await reviewService.getReviewByAlbumId(parseInt(query.album_id)); + res.status(200).json(review ?? null); + } catch (e) { + console.error('Error retrieving review'); + console.error(e); + next(e); + } + } +}; + +type UpsertReviewBody = { + album_id?: number; + review?: string; + author?: string; +}; + +export const upsertReview: RequestHandler = async ( + req: Request, + res, + next +) => { + const { body } = req; + if (body.album_id === undefined || body.review === undefined) { + res.status(400).send('Bad Request, Missing Parameters: album_id and review are required'); + } else { + try { + const result = await reviewService.upsertReview(body.album_id, body.review, body.author); + res.status(200).json(result); + } catch (e) { + console.error('Error upserting review'); + console.error(e); + next(e); + } + } +}; diff --git a/apps/backend/routes/library.route.ts b/apps/backend/routes/library.route.ts index 0d65147..6bc9f9f 100644 --- a/apps/backend/routes/library.route.ts +++ b/apps/backend/routes/library.route.ts @@ -1,6 +1,7 @@ import { requirePermissions } from '@wxyc/authentication'; import { Router } from 'express'; import * as libraryController from '../controllers/library.controller.js'; +import * as reviewController from '../controllers/review.controller.js'; import * as requestLineController from '../controllers/requestLine.controller.js'; import { requireAnonymousAuth } from '../middleware/anonymousAuth.js'; @@ -31,3 +32,9 @@ library_route.get('/genres', requirePermissions({ catalog: ['read'] }), libraryC library_route.post('/genres', requirePermissions({ catalog: ['write'] }), libraryController.addGenre); library_route.get('/info', requirePermissions({ catalog: ['read'] }), libraryController.getAlbum); + +library_route.get('/reviews', requirePermissions({ catalog: ['read'] }), reviewController.getReview); + +library_route.put('/reviews', requirePermissions({ catalog: ['write'] }), reviewController.upsertReview); + +library_route.patch('/', requirePermissions({ catalog: ['write'] }), libraryController.updateAlbum); diff --git a/apps/backend/services/library.service.ts b/apps/backend/services/library.service.ts index 8f98ff7..dd412a0 100644 --- a/apps/backend/services/library.service.ts +++ b/apps/backend/services/library.service.ts @@ -1,4 +1,4 @@ -import { desc, eq, sql } from 'drizzle-orm'; +import { and, desc, eq, sql } from 'drizzle-orm'; import { RotationAddRequest } from '../controllers/library.controller.js'; import { db } from '@wxyc/database'; import { @@ -224,6 +224,48 @@ export const isISODate = (date: string): boolean => { return date.match(regex) !== null; }; +export const lookupByLibraryCode = async ( + code_letters: string, + code_artist_number: number, + code_number?: number, + genre_name?: string +): Promise => { + const conditions = [ + eq(library_artist_view.code_letters, code_letters), + eq(library_artist_view.code_artist_number, code_artist_number), + ]; + + if (code_number !== undefined) { + conditions.push(eq(library_artist_view.code_number, code_number)); + } + + if (genre_name !== undefined) { + conditions.push(eq(library_artist_view.genre_name, genre_name)); + } + + const result = await db + .select() + .from(library_artist_view) + .where(and(...conditions)); + + return result as LibraryArtistViewEntry[]; +}; + +export const updateAlbumFields = async ( + albumId: number, + fields: { label?: string; album_title?: string } +) => { + const result = await db + .update(library) + .set({ + ...fields, + last_modified: sql`now()`, + }) + .where(eq(library.id, albumId)) + .returning(); + return result[0]; +}; + // ============================================================================= // Request Line Enhanced Search Functions // ============================================================================= diff --git a/apps/backend/services/review.service.ts b/apps/backend/services/review.service.ts new file mode 100644 index 0000000..61bdd82 --- /dev/null +++ b/apps/backend/services/review.service.ts @@ -0,0 +1,32 @@ +import { eq, sql } from 'drizzle-orm'; +import { db } from '@wxyc/database'; +import { reviews } from '@wxyc/database'; + +export const getReviewByAlbumId = async (albumId: number) => { + const result = await db + .select() + .from(reviews) + .where(eq(reviews.album_id, albumId)) + .limit(1); + return result[0]; +}; + +export const upsertReview = async (albumId: number, review: string, author?: string) => { + const result = await db + .insert(reviews) + .values({ + album_id: albumId, + review, + author: author ?? null, + }) + .onConflictDoUpdate({ + target: reviews.album_id, + set: { + review, + author: author ?? null, + last_modified: sql`now()`, + }, + }) + .returning(); + return result[0]; +}; diff --git a/tests/mocks/database.mock.ts b/tests/mocks/database.mock.ts index 64a7e21..9f2b03e 100644 --- a/tests/mocks/database.mock.ts +++ b/tests/mocks/database.mock.ts @@ -71,6 +71,9 @@ export const genres = {}; export const format = {}; export const rotation = {}; export const library_artist_view = {}; +export const reviews = { + album_id: 'album_id', +}; export const flowsheet = { id: 'id', show_id: 'show_id', diff --git a/tests/unit/services/library.service.code-lookup.test.ts b/tests/unit/services/library.service.code-lookup.test.ts new file mode 100644 index 0000000..314c03e --- /dev/null +++ b/tests/unit/services/library.service.code-lookup.test.ts @@ -0,0 +1,201 @@ +// Mock dependencies before importing the service +jest.mock('@wxyc/database', () => ({ + db: { + select: jest.fn().mockReturnThis(), + insert: jest.fn().mockReturnThis(), + update: jest.fn().mockReturnThis(), + delete: jest.fn().mockReturnThis(), + execute: jest.fn().mockReturnThis(), + }, + library: {}, + artists: {}, + genres: {}, + format: {}, + rotation: {}, + library_artist_view: { + code_letters: 'code_letters', + code_artist_number: 'code_artist_number', + code_number: 'code_number', + genre_name: 'genre_name', + }, +})); + +jest.mock('drizzle-orm', () => ({ + and: jest.fn((...conditions) => ({ and: conditions })), + eq: jest.fn((a, b) => ({ eq: [a, b] })), + sql: Object.assign( + jest.fn((strings: TemplateStringsArray, ...values: unknown[]) => ({ sql: strings, values })), + { raw: jest.fn((s: string) => ({ raw: s })) } + ), + desc: jest.fn((col) => ({ desc: col })), +})); + +import { lookupByLibraryCode, updateAlbumFields } from '../../../apps/backend/services/library.service'; +import { db } from '@wxyc/database'; +import { and, eq } from 'drizzle-orm'; + +type MockDb = { + select: jest.Mock; + insert: jest.Mock; + update: jest.Mock; + from: jest.Mock; + where: jest.Mock; + limit: jest.Mock; + values: jest.Mock; + returning: jest.Mock; + set: jest.Mock; + _whereResolveValue: unknown[]; +}; + +function setUpChain() { + const mockDb = db as unknown as MockDb; + mockDb._whereResolveValue = []; + mockDb.from = jest.fn().mockReturnValue(mockDb); + mockDb.limit = jest.fn().mockResolvedValue([]); + mockDb.values = jest.fn().mockReturnValue(mockDb); + mockDb.set = jest.fn().mockReturnValue(mockDb); + mockDb.returning = jest.fn().mockResolvedValue([]); + + // where() returns a thenable that also has .returning() for chaining + // This supports both: + // await db.select().from().where() -- lookupByLibraryCode + // await db.update().set().where().returning() -- updateAlbumFields + mockDb.where = jest.fn().mockImplementation(() => ({ + then: (resolve: (val: unknown) => void, reject?: (err: unknown) => void) => + Promise.resolve(mockDb._whereResolveValue).then(resolve, reject), + returning: mockDb.returning, + })); + + mockDb.select.mockReturnValue(mockDb); + mockDb.insert.mockReturnValue(mockDb); + mockDb.update.mockReturnValue(mockDb); + + return mockDb; +} + +describe('library.service - code lookup and album update', () => { + let mockDb: MockDb; + + beforeEach(() => { + jest.clearAllMocks(); + mockDb = setUpChain(); + }); + + describe('lookupByLibraryCode', () => { + it('queries with code_letters and code_artist_number', async () => { + const mockResults = [ + { + id: 1, + code_letters: 'AB', + code_artist_number: 5, + code_number: 1, + artist_name: 'Test Artist', + album_title: 'Test Album', + genre_name: 'Rock', + format_name: 'CD', + label: 'Test Label', + }, + ]; + + mockDb._whereResolveValue = mockResults; + + const result = await lookupByLibraryCode('AB', 5); + + expect(mockDb.select).toHaveBeenCalled(); + expect(mockDb.from).toHaveBeenCalled(); + expect(mockDb.where).toHaveBeenCalled(); + expect(eq).toHaveBeenCalledWith('code_letters', 'AB'); + expect(eq).toHaveBeenCalledWith('code_artist_number', 5); + expect(and).toHaveBeenCalled(); + expect(result).toEqual(mockResults); + }); + + it('filters by code_number when provided', async () => { + mockDb._whereResolveValue = []; + + await lookupByLibraryCode('AB', 5, 3); + + expect(eq).toHaveBeenCalledWith('code_number', 3); + }); + + it('filters by genre_name when provided', async () => { + mockDb._whereResolveValue = []; + + await lookupByLibraryCode('AB', 5, undefined, 'Rock'); + + expect(eq).toHaveBeenCalledWith('genre_name', 'Rock'); + }); + + it('returns empty array when no matches found', async () => { + mockDb._whereResolveValue = []; + + const result = await lookupByLibraryCode('ZZ', 99); + + expect(result).toEqual([]); + }); + }); + + describe('updateAlbumFields', () => { + it('updates only provided fields', async () => { + const mockUpdated = { + id: 42, + album_title: 'Original Title', + label: 'New Label', + last_modified: new Date(), + }; + + mockDb.returning.mockResolvedValue([mockUpdated]); + + const result = await updateAlbumFields(42, { label: 'New Label' }); + + expect(mockDb.update).toHaveBeenCalled(); + expect(mockDb.set).toHaveBeenCalledWith( + expect.objectContaining({ + label: 'New Label', + }) + ); + expect(result).toEqual(mockUpdated); + }); + + it('updates album_title when provided', async () => { + const mockUpdated = { + id: 42, + album_title: 'New Title', + label: 'Some Label', + last_modified: new Date(), + }; + + mockDb.returning.mockResolvedValue([mockUpdated]); + + const result = await updateAlbumFields(42, { album_title: 'New Title' }); + + expect(mockDb.set).toHaveBeenCalledWith( + expect.objectContaining({ + album_title: 'New Title', + }) + ); + expect(result).toEqual(mockUpdated); + }); + + it('updates both label and album_title when both provided', async () => { + const mockUpdated = { + id: 42, + album_title: 'New Title', + label: 'New Label', + last_modified: new Date(), + }; + + mockDb.returning.mockResolvedValue([mockUpdated]); + + const result = await updateAlbumFields(42, { label: 'New Label', album_title: 'New Title' }); + + expect(mockDb.set).toHaveBeenCalledWith( + expect.objectContaining({ + label: 'New Label', + album_title: 'New Title', + }) + ); + expect(result).toEqual(mockUpdated); + }); + }); +}); diff --git a/tests/unit/services/review.service.test.ts b/tests/unit/services/review.service.test.ts new file mode 100644 index 0000000..127d9b1 --- /dev/null +++ b/tests/unit/services/review.service.test.ts @@ -0,0 +1,146 @@ +// Mock dependencies before importing the service +jest.mock('@wxyc/database', () => ({ + db: { + select: jest.fn().mockReturnThis(), + insert: jest.fn().mockReturnThis(), + update: jest.fn().mockReturnThis(), + delete: jest.fn().mockReturnThis(), + execute: jest.fn().mockReturnThis(), + }, + reviews: { album_id: 'album_id' }, +})); + +jest.mock('drizzle-orm', () => ({ + eq: jest.fn((a, b) => ({ eq: [a, b] })), + sql: Object.assign( + jest.fn((strings: TemplateStringsArray, ...values: unknown[]) => ({ sql: strings, values })), + { raw: jest.fn((s: string) => ({ raw: s })) } + ), + desc: jest.fn((col) => ({ desc: col })), +})); + +import { getReviewByAlbumId, upsertReview } from '../../../apps/backend/services/review.service'; +import { db } from '@wxyc/database'; + +// Build a chainable mock from the db mock +type MockDb = { + select: jest.Mock; + insert: jest.Mock; + update: jest.Mock; + from: jest.Mock; + where: jest.Mock; + limit: jest.Mock; + values: jest.Mock; + returning: jest.Mock; + set: jest.Mock; +}; + +function setUpChain() { + const mockDb = db as unknown as MockDb; + // Each chainable method returns the same object + mockDb.from = jest.fn().mockReturnValue(mockDb); + mockDb.where = jest.fn().mockReturnValue(mockDb); + mockDb.limit = jest.fn().mockResolvedValue([]); + mockDb.values = jest.fn().mockReturnValue(mockDb); + mockDb.set = jest.fn().mockReturnValue(mockDb); + mockDb.returning = jest.fn().mockResolvedValue([]); + + // select/insert/update return the chainable object + mockDb.select.mockReturnValue(mockDb); + mockDb.insert.mockReturnValue(mockDb); + mockDb.update.mockReturnValue(mockDb); + + return mockDb; +} + +describe('review.service', () => { + let mockDb: MockDb; + + beforeEach(() => { + jest.clearAllMocks(); + mockDb = setUpChain(); + }); + + describe('getReviewByAlbumId', () => { + it('returns a review when found', async () => { + const mockReview = { + id: 1, + album_id: 42, + review: 'Great album!', + author: 'DJ Test', + add_date: '2024-01-15', + last_modified: new Date('2024-01-15'), + }; + + mockDb.limit.mockResolvedValue([mockReview]); + + const result = await getReviewByAlbumId(42); + + expect(mockDb.select).toHaveBeenCalled(); + expect(mockDb.from).toHaveBeenCalled(); + expect(mockDb.where).toHaveBeenCalled(); + expect(mockDb.limit).toHaveBeenCalledWith(1); + expect(result).toEqual(mockReview); + }); + + it('returns undefined when no review is found', async () => { + mockDb.limit.mockResolvedValue([]); + + const result = await getReviewByAlbumId(999); + + expect(result).toBeUndefined(); + }); + }); + + describe('upsertReview', () => { + it('calls insert with onConflictDoUpdate', async () => { + const mockResult = { + id: 1, + album_id: 42, + review: 'Updated review', + author: 'DJ Updated', + add_date: '2024-01-15', + last_modified: new Date(), + }; + + const onConflictDoUpdate = jest.fn().mockReturnValue(mockDb); + mockDb.values.mockReturnValue({ onConflictDoUpdate }); + onConflictDoUpdate.mockReturnValue(mockDb); + mockDb.returning.mockResolvedValue([mockResult]); + + const result = await upsertReview(42, 'Updated review', 'DJ Updated'); + + expect(mockDb.insert).toHaveBeenCalled(); + expect(mockDb.values).toHaveBeenCalled(); + expect(onConflictDoUpdate).toHaveBeenCalled(); + expect(result).toEqual(mockResult); + }); + + it('passes null for author when not provided', async () => { + const mockResult = { + id: 1, + album_id: 42, + review: 'No author review', + author: null, + add_date: '2024-01-15', + last_modified: new Date(), + }; + + const onConflictDoUpdate = jest.fn().mockReturnValue(mockDb); + mockDb.values.mockReturnValue({ onConflictDoUpdate }); + onConflictDoUpdate.mockReturnValue(mockDb); + mockDb.returning.mockResolvedValue([mockResult]); + + const result = await upsertReview(42, 'No author review'); + + expect(mockDb.values).toHaveBeenCalledWith( + expect.objectContaining({ + album_id: 42, + review: 'No author review', + author: null, + }) + ); + expect(result).toEqual(mockResult); + }); + }); +}); From c7198e7e6b550ab0d01559b899f07e26f37d34b5 Mon Sep 17 00:00:00 2001 From: Jake Bromberg Date: Sat, 28 Feb 2026 10:38:15 -0800 Subject: [PATCH 2/4] Update library code lookup integration test to expect 200 The code lookup endpoint is now implemented via lookupByLibraryCode(), so the test should expect 200 with valid album data instead of 501. Replaces the single 501 stub test with three tests: successful lookup by code_letters + code_artist_number, narrowed lookup with code_number, and empty result for non-existent codes. --- tests/integration/library.spec.js | 34 ++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/tests/integration/library.spec.js b/tests/integration/library.spec.js index f52a34d..446476d 100644 --- a/tests/integration/library.spec.js +++ b/tests/integration/library.spec.js @@ -69,10 +69,38 @@ describe('Library Catalog', () => { expect(res.body.length).toBe(0); }); - test('code lookup returns 501 (not implemented)', async () => { - const res = await auth.get('/library').query({ code_letters: 'BUI', code_artist_number: '1' }).expect(501); + test('code lookup returns albums matching library code', async () => { + const res = await auth.get('/library').query({ code_letters: 'BUI', code_artist_number: '1' }).expect(200); - expectErrorContains(res, 'TODO'); + expectArray(res); + expect(res.body.length).toBeGreaterThan(0); + res.body.forEach((album) => { + expectFields(album, 'id', 'code_letters', 'code_artist_number', 'code_number', 'artist_name', 'album_title'); + expect(album.code_letters).toBe('BUI'); + expect(album.code_artist_number).toBe(1); + }); + }); + + test('code lookup with code_number returns specific album', async () => { + const res = await auth + .get('/library') + .query({ code_letters: 'BUI', code_artist_number: '1', code_number: 1 }) + .expect(200); + + expectArray(res); + expect(res.body.length).toBeLessThanOrEqual(1); + if (res.body.length > 0) { + expect(res.body[0].code_letters).toBe('BUI'); + expect(res.body[0].code_artist_number).toBe(1); + expect(res.body[0].code_number).toBe(1); + } + }); + + test('code lookup returns empty array for non-existent code', async () => { + const res = await auth.get('/library').query({ code_letters: 'ZZZ', code_artist_number: '999' }).expect(200); + + expectArray(res); + expect(res.body.length).toBe(0); }); }); From ee1391a1574201c464b98790be905abc451c55a4 Mon Sep 17 00:00:00 2001 From: Jake Bromberg Date: Sat, 28 Feb 2026 11:00:24 -0800 Subject: [PATCH 3/4] Fix integration test fixture data and Prettier formatting Use seed data code_letters 'BU' / code_artist_number 60 (Built to Spill) instead of 'BUI' / 1 which doesn't exist in the CI database. Run Prettier on files modified in the previous commit. --- apps/backend/controllers/library.controller.ts | 6 +----- apps/backend/controllers/review.controller.ts | 6 +----- apps/backend/services/library.service.ts | 5 +---- apps/backend/services/review.service.ts | 6 +----- tests/integration/library.spec.js | 15 +++++++++------ 5 files changed, 13 insertions(+), 25 deletions(-) diff --git a/apps/backend/controllers/library.controller.ts b/apps/backend/controllers/library.controller.ts index 40f3c65..197d44a 100644 --- a/apps/backend/controllers/library.controller.ts +++ b/apps/backend/controllers/library.controller.ts @@ -301,11 +301,7 @@ type UpdateAlbumBody = { album_title?: string; }; -export const updateAlbum: RequestHandler = async ( - req: Request, - res, - next -) => { +export const updateAlbum: RequestHandler = async (req: Request, res, next) => { const { body } = req; if (body.album_id === undefined) { res.status(400).send('Bad Request, Missing Parameter: album_id'); diff --git a/apps/backend/controllers/review.controller.ts b/apps/backend/controllers/review.controller.ts index 4074bfb..6033cd6 100644 --- a/apps/backend/controllers/review.controller.ts +++ b/apps/backend/controllers/review.controller.ts @@ -27,11 +27,7 @@ type UpsertReviewBody = { author?: string; }; -export const upsertReview: RequestHandler = async ( - req: Request, - res, - next -) => { +export const upsertReview: RequestHandler = async (req: Request, res, next) => { const { body } = req; if (body.album_id === undefined || body.review === undefined) { res.status(400).send('Bad Request, Missing Parameters: album_id and review are required'); diff --git a/apps/backend/services/library.service.ts b/apps/backend/services/library.service.ts index dd412a0..3beec18 100644 --- a/apps/backend/services/library.service.ts +++ b/apps/backend/services/library.service.ts @@ -251,10 +251,7 @@ export const lookupByLibraryCode = async ( return result as LibraryArtistViewEntry[]; }; -export const updateAlbumFields = async ( - albumId: number, - fields: { label?: string; album_title?: string } -) => { +export const updateAlbumFields = async (albumId: number, fields: { label?: string; album_title?: string }) => { const result = await db .update(library) .set({ diff --git a/apps/backend/services/review.service.ts b/apps/backend/services/review.service.ts index 61bdd82..8eb3a8a 100644 --- a/apps/backend/services/review.service.ts +++ b/apps/backend/services/review.service.ts @@ -3,11 +3,7 @@ import { db } from '@wxyc/database'; import { reviews } from '@wxyc/database'; export const getReviewByAlbumId = async (albumId: number) => { - const result = await db - .select() - .from(reviews) - .where(eq(reviews.album_id, albumId)) - .limit(1); + const result = await db.select().from(reviews).where(eq(reviews.album_id, albumId)).limit(1); return result[0]; }; diff --git a/tests/integration/library.spec.js b/tests/integration/library.spec.js index 446476d..f5f4084 100644 --- a/tests/integration/library.spec.js +++ b/tests/integration/library.spec.js @@ -70,28 +70,31 @@ describe('Library Catalog', () => { }); test('code lookup returns albums matching library code', async () => { - const res = await auth.get('/library').query({ code_letters: 'BUI', code_artist_number: '1' }).expect(200); + const res = await auth + .get('/library') + .query({ code_letters: 'BU', code_artist_number: '60' }) + .expect(200); expectArray(res); expect(res.body.length).toBeGreaterThan(0); res.body.forEach((album) => { expectFields(album, 'id', 'code_letters', 'code_artist_number', 'code_number', 'artist_name', 'album_title'); - expect(album.code_letters).toBe('BUI'); - expect(album.code_artist_number).toBe(1); + expect(album.code_letters).toBe('BU'); + expect(album.code_artist_number).toBe(60); }); }); test('code lookup with code_number returns specific album', async () => { const res = await auth .get('/library') - .query({ code_letters: 'BUI', code_artist_number: '1', code_number: 1 }) + .query({ code_letters: 'BU', code_artist_number: '60', code_number: 1 }) .expect(200); expectArray(res); expect(res.body.length).toBeLessThanOrEqual(1); if (res.body.length > 0) { - expect(res.body[0].code_letters).toBe('BUI'); - expect(res.body[0].code_artist_number).toBe(1); + expect(res.body[0].code_letters).toBe('BU'); + expect(res.body[0].code_artist_number).toBe(60); expect(res.body[0].code_number).toBe(1); } }); From effca35bd16cace17ddc08a99defd53f921ee93d Mon Sep 17 00:00:00 2001 From: Jake Bromberg Date: Sat, 28 Feb 2026 11:09:35 -0800 Subject: [PATCH 4/4] Format integration test with Prettier --- tests/integration/library.spec.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/integration/library.spec.js b/tests/integration/library.spec.js index f5f4084..c054cd3 100644 --- a/tests/integration/library.spec.js +++ b/tests/integration/library.spec.js @@ -70,10 +70,7 @@ describe('Library Catalog', () => { }); test('code lookup returns albums matching library code', async () => { - const res = await auth - .get('/library') - .query({ code_letters: 'BU', code_artist_number: '60' }) - .expect(200); + const res = await auth.get('/library').query({ code_letters: 'BU', code_artist_number: '60' }).expect(200); expectArray(res); expect(res.body.length).toBeGreaterThan(0);