From 7190d0462d93a0af4ff8342871783b36b3889c42 Mon Sep 17 00:00:00 2001 From: Kevin Monisit Date: Thu, 24 Oct 2024 10:40:24 -0400 Subject: [PATCH 1/2] Clearer error messages; more QR safe gaurds --- src/functions/attend-event/handler.ts | 10 +++++++ src/functions/update/handler.ts | 41 ++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/functions/attend-event/handler.ts b/src/functions/attend-event/handler.ts index 5127cc2..e3d1ea7 100644 --- a/src/functions/attend-event/handler.ts +++ b/src/functions/attend-event/handler.ts @@ -41,6 +41,16 @@ const attendEvent: ValidatedEventAPIGatewayProxyEvent = async (ev }; } + if (attendEvent.registration_status != 'checked_in') { + return { + statusCode: 409, + body: JSON.stringify({ + statusCode: 409, + message: 'User has not checked in. Current status is ' + attendEvent.registration_status, + }), + }; + } + // ensure that only directors/organizers (auth_email) can call this route const authUser = await users.findOne({ email: event.body.auth_email }); if (!authUser) { diff --git a/src/functions/update/handler.ts b/src/functions/update/handler.ts index 2f31f21..d52a2b8 100644 --- a/src/functions/update/handler.ts +++ b/src/functions/update/handler.ts @@ -10,6 +10,9 @@ import * as dotenv from 'dotenv'; import { Document, WithId } from 'mongodb'; dotenv.config({ path: path.resolve(process.cwd(), '.env') }); +const CHECK_IN_START_DATE = new Date('2024-10-26T10:30:00'); +const CHECK_IN_CUT_OFF = new Date(CHECK_IN_START_DATE.getTime() + 3 * 24 * 60 * 60 * 1000); // 3 days after check-in start + const update: ValidatedEventAPIGatewayProxyEvent = async (event) => { // if user email = auth email, you're updating auth user // need to follow FSM for registration status @@ -67,8 +70,16 @@ const update: ValidatedEventAPIGatewayProxyEvent = async (event) } // validate updates - const isValidUpdates = validateUpdates(event.body.updates, updatedUser.registration_status, updatedUser); - if (!isValidUpdates) { + const validationResult = validateUpdates(event.body.updates, updatedUser.registration_status, updatedUser); + if (typeof validationResult === 'string') { + return { + statusCode: 400, + body: JSON.stringify({ + statusCode: 400, + message: validationResult, + }), + }; + } else if (!validationResult) { return { statusCode: 400, body: JSON.stringify({ @@ -135,13 +146,29 @@ function isValidRegistrationStatusUpdate(current: string, goal: string): boolean // return true or false whether the proposed update is valid or not -function validateUpdates(updates: Updates, registrationStatus?: string, user?: WithId): boolean { +function validateUpdates(updates: Updates, registrationStatus?: string, user?: WithId): boolean | string { const setUpdates = updates.$set; if (setUpdates) { if ('registration_status' in setUpdates) { + const currentDate = new Date(); const goalStatus = setUpdates.registration_status as string; - if (!isValidRegistrationStatusUpdate(registrationStatus || 'unregistered', goalStatus)) return false; - //validates for unregistered users going to registered status + + if (goalStatus === 'checked_in') { + if (currentDate > CHECK_IN_CUT_OFF) + return `Registration is closed. The cutoff date was ${CHECK_IN_CUT_OFF.toLocaleString()}.`; + } + + const atleastRegistered = ['confirmed', 'waitlist', 'registered', 'coming'].includes( + registrationStatus || 'unregistered' + ); + if (goalStatus === 'checked_in' && atleastRegistered) { + if (currentDate >= CHECK_IN_START_DATE || registrationStatus === 'confirmed') return true; + else + return `Current status of this user is ${registrationStatus}. Check-in will be available after ${CHECK_IN_START_DATE.toLocaleString()}.`; + } + + if (!isValidRegistrationStatusUpdate(registrationStatus || 'unregistered', goalStatus)) + return `Invalid registration status update from ${registrationStatus} to ${goalStatus}`; if ((registrationStatus === undefined || registrationStatus == 'unregistered') && goalStatus === 'registered') { if ( @@ -164,11 +191,11 @@ function validateUpdates(updates: Updates, registrationStatus?: string, user?: W 'phone_number', ].some((registrationField) => !user[registrationField] || user[registrationField] === '') ) - return false; + return 'Missing required fields'; } else return true; } if (['_id', 'password', 'discord', 'created_at', 'registered_at'].some((lockedProp) => lockedProp in setUpdates)) - return false; + return 'Cannot update locked fields'; return true; } From b46eaba15c06b4e38b1c499c566f4b5668117028 Mon Sep 17 00:00:00 2001 From: Kevin Monisit Date: Thu, 24 Oct 2024 12:10:03 -0400 Subject: [PATCH 2/2] Work on improving error messages and event safegaurds --- src/functions/attend-event/handler.ts | 20 ++++++++++---------- tests/attend-event.test.ts | 26 ++++++++++++++++++++++++++ tests/update.test.ts | 11 +++++++++-- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/functions/attend-event/handler.ts b/src/functions/attend-event/handler.ts index e3d1ea7..8a95080 100644 --- a/src/functions/attend-event/handler.ts +++ b/src/functions/attend-event/handler.ts @@ -41,16 +41,6 @@ const attendEvent: ValidatedEventAPIGatewayProxyEvent = async (ev }; } - if (attendEvent.registration_status != 'checked_in') { - return { - statusCode: 409, - body: JSON.stringify({ - statusCode: 409, - message: 'User has not checked in. Current status is ' + attendEvent.registration_status, - }), - }; - } - // ensure that only directors/organizers (auth_email) can call this route const authUser = await users.findOne({ email: event.body.auth_email }); if (!authUser) { @@ -72,6 +62,16 @@ const attendEvent: ValidatedEventAPIGatewayProxyEvent = async (ev }; } + if (attendEvent.registration_status != 'checked_in') { + return { + statusCode: 409, + body: JSON.stringify({ + statusCode: 409, + message: 'User has not checked in. Current status is ' + attendEvent.registration_status, + }), + }; + } + // conditions to check a user into events during hackathon const hackEvent = event.body.event; diff --git a/tests/attend-event.test.ts b/tests/attend-event.test.ts index 3703a12..25cf6da 100644 --- a/tests/attend-event.test.ts +++ b/tests/attend-event.test.ts @@ -92,6 +92,7 @@ describe('Attend-Event tests', () => { }, }, }, + registration_status: 'checked_in', }) .mockReturnValueOnce({ role: { @@ -117,6 +118,7 @@ describe('Attend-Event tests', () => { findOneMock .mockReturnValueOnce({ day_of: {}, + registration_status: 'checked_in', }) .mockReturnValueOnce({ role: { @@ -135,4 +137,28 @@ describe('Attend-Event tests', () => { expect(result.statusCode).toBe(200); expect(JSON.parse(result.body).message).toBe('user successfully checked into event'); }); + + it('user tries to attend event when they are not checked_in', async () => { + findOneMock + .mockReturnValueOnce({ + day_of: {}, + registration_status: 'registered', + }) + .mockReturnValueOnce({ + role: { + hacker: false, + volunteer: true, + judge: false, + sponsor: false, + mentor: false, + organizer: false, + director: true, + }, + }); + const mockEvent = createEvent(userData, path, httpMethod); + const result = await main(mockEvent, mockContext, mockCallback); + + expect(result.statusCode).toBe(409); + expect(JSON.parse(result.body).message).toBe('User has not checked in. Current status is registered'); + }); }); diff --git a/tests/update.test.ts b/tests/update.test.ts index 4974e98..20cce61 100644 --- a/tests/update.test.ts +++ b/tests/update.test.ts @@ -171,7 +171,14 @@ describe('/update endpoint', () => { const mockEvent = createEvent(invalidUpdate, '/update', 'POST'); const res = await main(mockEvent, mockContext, mockCallback); expect(res.statusCode).toBe(400); - expect(JSON.parse(res.body).message).toBe('Bad updates.'); + /* + The reason this message is being commented out is because with the new error messages, + the error message will be different depending on what field is being updated. + + For example, password being update will have different message from registration_status being + updated to a value that is not a part of the graph. + */ + // expect(JSON.parse(res.body).message).toBe('Invalid registration status update from registered to coming'); } }); @@ -227,7 +234,7 @@ describe('/update endpoint', () => { const mockEvent = createEvent(incompleteUserData, '/update', 'POST'); const res = await main(mockEvent, mockContext, mockCallback); expect(res.statusCode).toBe(400); - expect(JSON.parse(res.body).message).toBe('Bad updates.'); + expect(JSON.parse(res.body).message).toBe('Missing required fields'); }); //case 8 it('Completed fields for registration, success', async () => {