Skip to content
Open
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
10 changes: 5 additions & 5 deletions apps/backend/controllers/djs.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type binBody = {
export const addToBin: RequestHandler<object, unknown, binBody> = async (req, res, next) => {
if (req.body.album_id === undefined || req.body.dj_id === undefined) {
console.error('Bad Request, Missing Album Identifier: album_id');
res.status(400).send('Bad Request, Missing DJ or album identifier: album_id');
res.status(400).json({ message: 'Bad Request, Missing DJ or album identifier: album_id' });
} else {
const bin_entry: NewBinEntry = {
dj_id: req.body.dj_id,
Expand Down Expand Up @@ -40,7 +40,7 @@ export type binQuery = {
export const deleteFromBin: RequestHandler<object, unknown, unknown, binQuery> = async (req, res, next) => {
if (req.query.album_id === undefined || req.query.dj_id === undefined) {
console.error('Bad Request, Missing Bin Entry Identifier: album_id or dj_id');
res.status(400).send('Bad Request, Missing Bin Entry Identifier: album_id or dj_id');
res.status(400).json({ message: 'Bad Request, Missing Bin Entry Identifier: album_id or dj_id' });
} else {
try {
//check that the dj_id === dj_id of bin entry
Expand All @@ -56,7 +56,7 @@ export const deleteFromBin: RequestHandler<object, unknown, unknown, binQuery> =
export const getBin: RequestHandler<object, unknown, object, { dj_id: string }> = async (req, res, next) => {
if (req.query.dj_id === undefined) {
console.error('Bad Request, Missing DJ Identifier: dj_id');
res.status(400).send('Bad Request, Missing DJ Identifier: dj_id');
res.status(400).json({ message: 'Bad Request, Missing DJ Identifier: dj_id' });
} else {
try {
const dj_bin = await DJService.getBinFromDB(req.query.dj_id);
Expand All @@ -72,7 +72,7 @@ export const getBin: RequestHandler<object, unknown, object, { dj_id: string }>
export const getPlaylistsForDJ: RequestHandler<object, unknown, object, { dj_id: string }> = async (req, res, next) => {
if (req.query.dj_id === undefined) {
console.error('Bad Request, Missing DJ Identifier: dj_id');
res.status(400).send('Bad Request, Missing DJ Identifier: dj_id');
res.status(400).json({ message: 'Bad Request, Missing DJ Identifier: dj_id' });
} else {
try {
const playlists = await DJService.getPlaylistsForDJ(req.query.dj_id);
Expand All @@ -88,7 +88,7 @@ export const getPlaylistsForDJ: RequestHandler<object, unknown, object, { dj_id:
export const getPlaylist: RequestHandler<object, unknown, object, { playlist_id: string }> = async (req, res, next) => {
if (req.query.playlist_id === undefined) {
console.error('Bad Request, Missing Playlist Identifier: playlist_id');
res.status(400).send('Bad Request, Missing Playlist Identifier: playlist_id');
res.status(400).json({ message: 'Bad Request, Missing Playlist Identifier: playlist_id' });
} else {
try {
const playlist = await DJService.getPlaylist(parseInt(req.query.playlist_id));
Expand Down
18 changes: 10 additions & 8 deletions apps/backend/controllers/flowsheet.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export const getLatest: RequestHandler = async (req, res, next) => {
res.status(200).json(latest[0]);
} else {
console.error('No Tracks found');
res.status(404).send('Error: No Tracks found');
res.status(404).json({ message: 'No Tracks found' });
}
} catch (e) {
console.error('Error: Failed to retrieve track');
Expand Down Expand Up @@ -142,13 +142,13 @@ export const addEntry: RequestHandler = async (req: Request<object, object, FSEn
}
if (latestShow?.end_time !== null) {
console.error('Bad Request, There are no active shows');
res.status(400).send('Bad Request, There are no active shows');
res.status(400).json({ message: 'Bad Request, There are no active shows' });
} else {
if (body.message === undefined) {
// no message passed, so we assume we're adding a track to the flowsheet
if (body.track_title === undefined) {
console.error('Bad Request, Missing query parameter: track_title');
res.status(400).send('Bad Request, Missing query parameter: track_title');
res.status(400).json({ message: 'Bad Request, Missing query parameter: track_title' });
} else {
try {
if (body.album_id !== undefined) {
Expand Down Expand Up @@ -189,7 +189,9 @@ export const addEntry: RequestHandler = async (req: Request<object, object, FSEn
body.track_title === undefined
) {
console.error('Bad Request, Missing Flowsheet Parameters: album_title, artist_name, track_title');
res.status(400).send('Bad Request, Missing Flowsheet Parameters: album_title, artist_name, track_title');
res
.status(400)
.json({ message: 'Bad Request, Missing Flowsheet Parameters: album_title, artist_name, track_title' });
} else {
const fsEntry: NewFSEntry = {
...body,
Expand Down Expand Up @@ -243,7 +245,7 @@ export const deleteEntry: RequestHandler<object, unknown, { entry_id: number }>
const { entry_id } = req.body;
if (entry_id === undefined) {
console.error('Bad Request, Missing entry identifier: entry_id');
res.status(400).send('Bad Request, Missing entry identifier: entry_id');
res.status(400).json({ message: 'Bad Request, Missing entry identifier: entry_id' });
} else {
try {
const removedEntry: FSEntry = await flowsheet_service.removeTrack(entry_id);
Expand Down Expand Up @@ -273,7 +275,7 @@ export const updateEntry: RequestHandler<object, unknown, { entry_id: number; da
const { entry_id, data } = req.body;
if (entry_id === undefined) {
console.error('Bad Request, Missing entry identifier: entry_id');
res.status(400).send('Bad Request, Missing entry identifier: entry_id');
res.status(400).json({ message: 'Bad Request, Missing entry identifier: entry_id' });
} else {
try {
const updatedEntry: FSEntry = await flowsheet_service.updateEntry(entry_id, data);
Expand All @@ -296,7 +298,7 @@ export type JoinRequestBody = {
export const joinShow: RequestHandler = async (req: Request<object, object, JoinRequestBody>, res, next) => {
const current_show = await flowsheet_service.getLatestShow();
if (req.body.dj_id === undefined) {
res.status(400).send('Bad Request, Must include a dj_id to join show');
res.status(400).json({ message: 'Bad Request, Must include a dj_id to join show' });
} else if (current_show?.end_time !== null) {
try {
const show_session: Show = await flowsheet_service.startShow(
Expand Down Expand Up @@ -416,7 +418,7 @@ export const getShowInfo: RequestHandler<object, unknown, object, { show_id: str
const showId = parseInt(req.query.show_id);
if (isNaN(showId)) {
console.error('Bad Request, Missing Show Identifier: show_id');
res.status(400).send('Bad Request, Missing Show Identifier: show_id');
res.status(400).json({ message: 'Bad Request, Missing Show Identifier: show_id' });
} else {
try {
const showInfo = await flowsheet_service.getPlaylist(showId);
Expand Down
37 changes: 17 additions & 20 deletions apps/backend/controllers/library.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export const addAlbum: RequestHandler = async (req: Request<object, object, NewA
(body.artist_name === undefined && body.artist_id === undefined)
) {
res.status(400).json({
status: 400,
message: 'Missing Parameters: album_title, label, genre_id, format_id, artist_name, or artist_id',
});
} else {
Expand All @@ -49,10 +48,10 @@ export const addAlbum: RequestHandler = async (req: Request<object, object, NewA
}
}
if (!artist_id) {
res.status(400);
res.send(
"Artist doesn't exist or hasn't released an album in this genre before. Add a new artist entry to the library"
);
res.status(400).json({
message:
"Artist doesn't exist or hasn't released an album in this genre before. Add a new artist entry to the library",
});
} else {
try {
const new_album: NewAlbum = {
Expand Down Expand Up @@ -98,14 +97,13 @@ export const searchForAlbum: RequestHandler = async (
query.album_title === undefined &&
(query.code_letters === undefined || query.code_artist_number === undefined)
) {
res.status(400);
res.send(
'Missing query parameter. Query must include: artist_name, album_title, or code_letters, code_artist_number, and code_number'
);
res.status(400).json({
message:
'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');
res.status(501).json({ message: 'TODO: Library Code Lookup' });
} else {
try {
const response = await libraryService.fuzzySearchLibrary(query.artist_name, query.album_title, query.n);
Expand All @@ -128,8 +126,7 @@ export const addArtist: RequestHandler = async (req: Request<object, object, New
const { body } = req;
//TODO auto_generate artist code letters and make it an optional parameter
if (body.artist_name === undefined || body.code_letters === undefined || body.genre_id === undefined) {
res.status(400);
res.send('Missing Request Parameters: artist_name, code_letters, or genre_id');
res.status(400).json({ message: 'Missing Request Parameters: artist_name, code_letters, or genre_id' });
} else {
try {
const generatedArtistNumber = await libraryService.generateArtistNumber(body.code_letters, body.genre_id);
Expand Down Expand Up @@ -166,7 +163,7 @@ export const getRotation: RequestHandler = async (req, res, next) => {
export type RotationAddRequest = Omit<NewRotationRelease, 'id'>;
export const addRotation: RequestHandler<object, unknown, NewRotationRelease> = async (req, res, next) => {
if (req.body.album_id === undefined || req.body.rotation_bin === undefined) {
res.status(400).send('Missing Parameters: album_id or rotation_bin');
res.status(400).json({ message: 'Missing Parameters: album_id or rotation_bin' });
} else {
try {
const rotationRelease: RotationRelease = await libraryService.addToRotation(req.body);
Expand All @@ -187,16 +184,16 @@ export const killRotation: RequestHandler<object, unknown, KillRotationRelease>
const { body } = req;

if (body.rotation_id === undefined) {
res.status(400).send('Bad Request, Missing Parameter: rotation_id');
res.status(400).json({ message: 'Bad Request, Missing Parameter: rotation_id' });
} else if (body.kill_date !== undefined && !libraryService.isISODate(body.kill_date)) {
res.status(400).send('Bad Request, Incorrect Date Format: kill_date should be of form YYYY-MM-DD');
res.status(400).json({ message: 'Bad Request, Incorrect Date Format: kill_date should be of form YYYY-MM-DD' });
} else {
try {
const updatedRotation: RotationRelease = await libraryService.killRotationInDB(body.rotation_id, body.kill_date);
if (updatedRotation !== undefined) {
res.status(200).json(updatedRotation);
} else {
res.status(400).json({ status: 400, message: 'Rotation entry not found' });
res.status(400).json({ message: 'Rotation entry not found' });
}
} catch (e) {
console.error('Failed to update rotation kill_date');
Expand All @@ -220,7 +217,7 @@ export const getFormats: RequestHandler = async (req, res, next) => {
export const addFormat: RequestHandler = async (req, res, next) => {
const { body } = req;
if (body.name === undefined) {
res.status(400).send('Bad Request, Missing Parameter: name');
res.status(400).json({ message: 'Bad Request, Missing Parameter: name' });
} else {
try {
const newFormat: NewAlbumFormat = {
Expand All @@ -245,7 +242,7 @@ export const getGenres: RequestHandler = async (req, res) => {
export const addGenre: RequestHandler = async (req, res, next) => {
const { body } = req;
if (body.name === undefined || body.description === undefined) {
res.status(400).send('Bad Request, Parameters name and description are required.');
res.status(400).json({ message: 'Bad Request, Parameters name and description are required.' });
} else {
try {
const newGenre: NewGenre = {
Expand All @@ -271,7 +268,7 @@ export const getAlbum: RequestHandler<object, unknown, unknown, { album_id: stri
const { query } = req;
if (query.album_id === undefined) {
console.error('Bad Request, missing album identifier: album_id');
res.status(400).send('Bad Request, missing album identifier: album_id');
res.status(400).json({ message: 'Bad Request, missing album identifier: album_id' });
} else {
try {
const album = await libraryService.getAlbumFromDB(parseInt(query.album_id));
Expand Down
3 changes: 2 additions & 1 deletion apps/backend/middleware/errorHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ function errorHandler(err: any, req: Request, res: Response, next: NextFunction)
if (error instanceof WxycError) {
res.status(error.statusCode).json({ message: error.message });
} else {
res.status(500).json({ message: error.message });
console.error('Unhandled error:', error);
res.status(500).json({ message: 'Internal server error' });
}
}

Expand Down
58 changes: 58 additions & 0 deletions tests/unit/middleware/errorHandler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import errorHandler from '../../../apps/backend/middleware/errorHandler';
import WxycError from '../../../apps/backend/utils/error';
import { Request, Response, NextFunction } from 'express';

function mockResponse() {
const statusMock = jest.fn().mockReturnThis();
const jsonMock = jest.fn().mockReturnThis();
const res = {
status: statusMock,
json: jsonMock,
} as unknown as Response;
return { res, statusMock, jsonMock };
}

const mockReq = {} as Request;
const mockNext = jest.fn() as NextFunction;

describe('errorHandler middleware', () => {
it('returns { message } with correct status for WxycError', () => {
const { res, statusMock, jsonMock } = mockResponse();
const error = new WxycError('Album not found', 404);

errorHandler(error, mockReq, res, mockNext);

expect(statusMock).toHaveBeenCalledWith(404);
expect(jsonMock).toHaveBeenCalledWith({ message: 'Album not found' });
});

it('returns generic message for non-WxycError (does not leak internals)', () => {
const { res, statusMock, jsonMock } = mockResponse();
const error = new Error('SELECT * FROM users failed: connection refused');

errorHandler(error, mockReq, res, mockNext);

expect(statusMock).toHaveBeenCalledWith(500);
expect(jsonMock).toHaveBeenCalledWith({ message: 'Internal server error' });
});

it('handles non-Error values thrown', () => {
const { res, statusMock, jsonMock } = mockResponse();

errorHandler('something broke', mockReq, res, mockNext);

expect(statusMock).toHaveBeenCalledWith(500);
expect(jsonMock).toHaveBeenCalledWith({ message: 'Internal server error' });
});

it('logs non-WxycError errors to console', () => {
const { res } = mockResponse();
const error = new Error('db connection lost');
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();

errorHandler(error, mockReq, res, mockNext);

expect(consoleSpy).toHaveBeenCalledWith('Unhandled error:', error);
consoleSpy.mockRestore();
});
});