From 95cab662b4edd7e7abe96781141d7a69225fd466 Mon Sep 17 00:00:00 2001 From: Spencer Spenst Date: Tue, 25 Jul 2023 15:03:55 -0700 Subject: [PATCH] Remove codes from api/stats (#954) --- helpers/validateSolution.ts | 5 - pages/api/stats/index.ts | 23 +- tests/pages/api/stats/stats.codes.test.ts | 759 ---------------------- tests/pages/api/stats/stats.test.ts | 42 +- 4 files changed, 47 insertions(+), 782 deletions(-) delete mode 100644 tests/pages/api/stats/stats.codes.test.ts diff --git a/helpers/validateSolution.ts b/helpers/validateSolution.ts index cf388a375..8f6722c76 100644 --- a/helpers/validateSolution.ts +++ b/helpers/validateSolution.ts @@ -23,11 +23,6 @@ export default function validateSolution(directions: Direction[], level: Level) const direction = directions[i]; - // account for bad user input from the API - if (!(direction in Direction)) { - return false; - } - // validate and update position with direction pos = pos.add(directionToPosition(direction)); diff --git a/pages/api/stats/index.ts b/pages/api/stats/index.ts index f5ed05062..3b808af31 100644 --- a/pages/api/stats/index.ts +++ b/pages/api/stats/index.ts @@ -1,5 +1,5 @@ import AchievementInfo from '@root/constants/achievementInfo'; -import Direction, { getDirectionFromCode } from '@root/constants/direction'; +import Direction from '@root/constants/direction'; import getDifficultyEstimate from '@root/helpers/getDifficultyEstimate'; import User from '@root/models/db/user'; import { AttemptContext } from '@root/models/schemas/playAttemptSchema'; @@ -35,8 +35,7 @@ export default withAuth({ GET: {}, PUT: { body: { - codes: ValidArray(false), - directions: ValidArray(false), + directions: ValidArray(), levelId: ValidObjectId(), matchId: ValidType('string', false), } @@ -47,16 +46,16 @@ export default withAuth({ return res.status(200).json(stats); } else if (req.method === 'PUT') { - const { codes, directions, levelId, matchId } = req.body; + const { directions, levelId, matchId } = req.body; - if (!directions && !codes) { - return res.status(400).json({ - error: 'No directions or codes provided', - }); + for (const direction of directions) { + if (!(direction in Direction)) { + return res.status(400).json({ + error: `Invalid direction provided: ${direction}`, + }); + } } - const dirs: Direction[] = directions ?? codes.map((code: string) => getDirectionFromCode(code)); - const ts = TimerUtil.getTs(); const session = await mongoose.startSession(); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -78,13 +77,13 @@ export default withAuth({ throw new Error(resTrack.json.error); } - if (!validateSolution(dirs, level)) { + if (!validateSolution(directions, level)) { resTrack.status = 400; resTrack.json.error = `Invalid solution provided for level ${levelId}`; throw new Error(resTrack.json.error); } - const moves = dirs.length; + const moves = directions.length; // ensure no stats are saved for draft levels if (level.isDraft || level.leastMoves === 0) { diff --git a/tests/pages/api/stats/stats.codes.test.ts b/tests/pages/api/stats/stats.codes.test.ts deleted file mode 100644 index 5fd235199..000000000 --- a/tests/pages/api/stats/stats.codes.test.ts +++ /dev/null @@ -1,759 +0,0 @@ -import Stat from '@root/models/db/stat'; -import { enableFetchMocks } from 'jest-fetch-mock'; -import { Types } from 'mongoose'; -import { testApiHandler } from 'next-test-api-route-handler'; -import { Logger } from 'winston'; -import TestId from '../../../../constants/testId'; -import { logger } from '../../../../helpers/logger'; -import dbConnect, { dbDisconnect } from '../../../../lib/dbConnect'; -import { getTokenCookieValue } from '../../../../lib/getTokenCookie'; -import { NextApiRequestWithAuth } from '../../../../lib/withAuth'; -import { LevelModel, RecordModel, StatModel, UserModel } from '../../../../models/mongoose'; -import { processQueueMessages } from '../../../../pages/api/internal-jobs/worker'; -import handler from '../../../../pages/api/stats/index'; -import unpublishLevelHandler from '../../../../pages/api/unpublish/[id]'; - -beforeAll(async () => { - await dbConnect(); -}); -afterEach(() => { - jest.restoreAllMocks(); -}); -afterAll(async () => { - await dbDisconnect(); -}); -enableFetchMocks(); - -describe('Testing stats api', () => { - test('Wrong HTTP method should fail', async () => { - jest.spyOn(logger, 'error').mockImplementation(() => ({} as Logger)); - - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'PATCH', - cookies: { - token: getTokenCookieValue(TestId.USER), - }, - body: { - - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - expect(response.error).toBe('Method not allowed'); - expect(res.status).toBe(405); - }, - }); - }); - test('Doing a PUT with empty body should error', async () => { - jest.spyOn(logger, 'error').mockImplementation(() => ({} as Logger)); - - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'PUT', - cookies: { - token: getTokenCookieValue(TestId.USER), - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - expect(response.error).toBe('Bad request'); - expect(res.status).toBe(400); - }, - }); - }); - test('Doing a PUT with empty body should error', async () => { - jest.spyOn(logger, 'error').mockImplementation(() => ({} as Logger)); - - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'PUT', - cookies: { - token: getTokenCookieValue(TestId.USER), - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - expect(response.error).toBe('Bad request'); - expect(res.status).toBe(400); - }, - }); - }); - test('Doing a PUT with a body but no params should error', async () => { - jest.spyOn(logger, 'error').mockImplementation(() => ({} as Logger)); - - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'PUT', - cookies: { - token: getTokenCookieValue(TestId.USER), - }, - body: { - - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - expect(response.error).toBe('Invalid body.levelId'); - expect(res.status).toBe(400); - }, - }); - }); - test('Doing a PUT with a body but malformed level solution should error', async () => { - jest.spyOn(logger, 'error').mockImplementation(() => ({} as Logger)); - - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'PUT', - cookies: { - token: getTokenCookieValue(TestId.USER), - }, - body: { - codes: '12345', - levelId: TestId.LEVEL - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - expect(response.error).toBe('Invalid body.codes'); - expect(res.status).toBe(400); - }, - }); - }); - test('Doing a PUT on an unknown level should error', async () => { - jest.spyOn(logger, 'error').mockImplementation(() => ({} as Logger)); - const levelId = new Types.ObjectId(); - - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'PUT', - cookies: { - token: getTokenCookieValue(TestId.USER), - }, - body: { - codes: ['ArrowRight'], - levelId: levelId, - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - expect(response.error).toBe(`Error finding level ${levelId.toString()}`); - expect(res.status).toBe(404); - }, - }); - }); - test('Doing a PUT with a body but incorrect level solution should be OK', async () => { - jest.spyOn(logger, 'error').mockImplementation(() => ({} as Logger)); - - const codeTests = [ - ['ArrowLeft'], // test left border - ['WRONG', 'ArrowRight', 'ArrowRight', 'ArrowRight', 'ArrowRight'], // test invalid - ['ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowLeft'], // tries to go over hole - ['ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowDown', 'ArrowDown', 'ArrowDown'], // tries to push directional movable where it cant be pushed - ['ArrowRight', 'ArrowRight', 'ArrowRight', 'ArrowRight', 'ArrowRight'], // tries to push directional movable where it cant be pushed because of edge - ['ArrowDown'], // run into wall - ['ArrowRight', 'ArrowDown', 'ArrowRight', 'ArrowRight'], // tries to push directional movable where it cant be pushed because something in way - ['ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowLeft'], // push movable into wall - ]; - - for (const codeTest of codeTests) { - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'PUT', - cookies: { - token: getTokenCookieValue(TestId.USER), - }, - body: { - codes: codeTest, - levelId: TestId.LEVEL - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - const lvl = await LevelModel.findById(TestId.LEVEL); - - expect(lvl.leastMoves).toBe(20); - expect(response.error).toBe(`Invalid solution provided for level ${TestId.LEVEL}`); - expect(res.status).toBe(400); - const u = await UserModel.findById(TestId.USER); - - expect(u.calc_records).toEqual(2); // initializes with 1 - }, - }); - } - }); - - test('Doing a PUT from USER with correct level solution (that is long, 14 steps) on a draft level should be OK', async () => { - await LevelModel.findByIdAndUpdate(TestId.LEVEL, { - $set: { - isDraft: true, - } - }); - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'PUT', - cookies: { - token: getTokenCookieValue(TestId.USER), - }, - - body: { - codes: ['ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowRight', 'ArrowUp', 'ArrowLeft', 'ArrowLeft', 'ArrowDown', 'ArrowDown', 'ArrowRight', 'ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowDown'], - levelId: TestId.LEVEL - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - expect(response.error).toBeUndefined(); - expect(response.success).toBe(true); - expect(res.status).toBe(200); - const lvl = await LevelModel.findById(TestId.LEVEL); - - expect(lvl.leastMoves).toBe(14); - const u = await UserModel.findById(TestId.USER); - - expect(u.calc_records).toEqual(2); - }, - }); - }); - test('Doing ANOTHER PUT from USER with correct level solution (that is long, 14 steps) on a draft level should be OK', async () => { - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'PUT', - cookies: { - token: getTokenCookieValue(TestId.USER), - }, - - body: { - codes: ['ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowRight', 'ArrowUp', 'ArrowLeft', 'ArrowLeft', 'ArrowDown', 'ArrowDown', 'ArrowRight', 'ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowDown'], - levelId: TestId.LEVEL - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - expect(response.error).toBeUndefined(); - expect(response.success).toBe(true); - expect(res.status).toBe(200); - const lvl = await LevelModel.findById(TestId.LEVEL); - - expect(lvl.leastMoves).toBe(14); - const u = await UserModel.findById(TestId.USER); - - expect(u.calc_records).toEqual(2); - }, - }); - }); - test('Doing ANOTHER PUT with USERB with correct level solution (that is long, 14 steps) with a DIFFERENT user on a draft level should FAIL', async () => { - jest.spyOn(logger, 'error').mockImplementation(() => ({} as Logger)); - - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'PUT', - cookies: { - token: getTokenCookieValue(TestId.USER_B), - }, - body: { - codes: ['ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowRight', 'ArrowUp', 'ArrowLeft', 'ArrowLeft', 'ArrowDown', 'ArrowDown', 'ArrowRight', 'ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowDown'], - levelId: TestId.LEVEL, - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - expect(response.error).toBe(`Unauthorized access for level ${TestId.LEVEL}`); - expect(res.status).toBe(401); - const u = await UserModel.findById(TestId.USER); - - expect(u.calc_records).toEqual(2); - }, - }); - }); - test('Doing a PUT from USER with a correct level solution (that is 12 steps) on a published level should be OK', async () => { - const u = await UserModel.findById(TestId.USER); - - expect(u.calc_records).toEqual(2); - await LevelModel.findByIdAndUpdate(TestId.LEVEL, { - $set: { - isDraft: false, - } - }); - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'PUT', - cookies: { - token: getTokenCookieValue(TestId.USER), - }, - - body: { - codes: ['ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowDown', 'ArrowRight', 'ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowLeft', 'ArrowUp', 'ArrowDown', 'ArrowDown'], - levelId: TestId.LEVEL - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - expect(response.error).toBeUndefined(); - expect(response.success).toBe(true); - expect(res.status).toBe(200); - const lvl = await LevelModel.findById(TestId.LEVEL); - - expect(lvl.leastMoves).toBe(12); - const u = await UserModel.findById(TestId.USER); - - expect(u.calc_records).toEqual(2); // +0 since this is the same owner of the level - }, - }); - }); - test('Doing a GET should return a stats object', async () => { - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'GET', - cookies: { - token: getTokenCookieValue(TestId.USER), - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - await processQueueMessages(); - expect(response.error).toBeUndefined(); - expect(response.length).toBe(2); - expect(response.some((s: Stat) => s.attempts === 2 && s.moves === 12)).toBeTruthy(); - expect(response.some((s: Stat) => s.attempts === 1 && s.moves === 80)).toBeTruthy(); - expect(res.status).toBe(200); - - const lvl = await LevelModel.findById(TestId.LEVEL); - - expect(lvl.leastMoves).toBe(12); - expect(lvl.calc_stats_players_beaten).toBe(1); - }, - }); - }); - test('Doing a PUT with a USERB user with a level solution (that is 14 steps) should be OK', async () => { - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'PUT', - cookies: { - token: getTokenCookieValue(TestId.USER_B), - }, - body: { - codes: ['ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowRight', 'ArrowUp', 'ArrowLeft', 'ArrowLeft', 'ArrowDown', 'ArrowDown', 'ArrowRight', 'ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowDown'], - levelId: TestId.LEVEL - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - await processQueueMessages(); - expect(response.error).toBeUndefined(); - expect(response.success).toBe(true); - expect(res.status).toBe(200); - const lvl = await LevelModel.findById(TestId.LEVEL); - - expect(lvl.leastMoves).toBe(12); - expect(lvl.calc_stats_players_beaten).toBe(1); // still hasn't won since 14 steps > minimum - - const b = await UserModel.findById(TestId.USER_B); - - expect(b.calc_records).toEqual(0); - }, - }); - }); - test('For the second time, doing a PUT with a USERB user with a level solution (that is 14 steps) should be OK and increment their attempts', async () => { - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'PUT', - cookies: { - token: getTokenCookieValue(TestId.USER_B), - }, - body: { - codes: ['ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowRight', 'ArrowUp', 'ArrowLeft', 'ArrowLeft', 'ArrowDown', 'ArrowDown', 'ArrowRight', 'ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowDown'], - levelId: TestId.LEVEL - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - await processQueueMessages(); - expect(response.error).toBeUndefined(); - expect(response.success).toBe(true); - expect(res.status).toBe(200); - const lvl = await LevelModel.findById(TestId.LEVEL); - - expect(lvl.leastMoves).toBe(12); - expect(lvl.calc_stats_players_beaten).toBe(1); // still hasn't won since 14 steps > minimum - - const stat = await StatModel.findOne({ userId: TestId.USER_B, levelId: TestId.LEVEL }); - - expect(stat.attempts).toBe(3); - - const b = await UserModel.findById(TestId.USER_B); - - expect(b.calc_records).toEqual(0); - }, - }); - }); - test('Doing a PUT with a USER_B user with a level solution (that is 12 steps) should be OK', async () => { - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'PUT', - cookies: { - token: getTokenCookieValue(TestId.USER_B), - }, - body: { - codes: ['ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowDown', 'ArrowRight', 'ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowLeft', 'ArrowUp', 'ArrowDown', 'ArrowDown'], - levelId: TestId.LEVEL - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - await processQueueMessages(); - expect(response.error).toBeUndefined(); - expect(response.success).toBe(true); - expect(res.status).toBe(200); - const lvl = await LevelModel.findById(TestId.LEVEL); - - expect(lvl.leastMoves).toBe(12); - expect(lvl.calc_stats_players_beaten).toBe(2); - - const b = await UserModel.findById(TestId.USER_B); - - expect(b.calc_records).toEqual(0); - }, - }); - }); - test('Test what happens when the DB has an error in the middle of a transaction from USERB (it should undo all the queries)', async () => { - // The findOne that api/stats checks for a stat existing already, let's make this fail by returning a promise that errors - - jest.spyOn(logger, 'error').mockImplementation(() => ({} as Logger)); - jest.spyOn(StatModel, 'updateOne').mockRejectedValueOnce(new Error('Test DB error')); - - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'PUT', - cookies: { - token: getTokenCookieValue(TestId.USER_B), - }, - body: { - codes: [ 'ArrowRight', 'ArrowDown', 'ArrowRight', 'ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowDown', 'ArrowDown'], - levelId: TestId.LEVEL - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - await processQueueMessages(); - expect(response.error).toBe('Internal server error'); - - expect(res.status).toBe(500); - const lvl = await LevelModel.findById(TestId.LEVEL); - - expect(lvl.leastMoves).toBe(12); - expect(lvl.calc_stats_players_beaten).toBe(2); - // get records - const records = await RecordModel.find({ levelId: TestId.LEVEL }, {}, { sort: { moves: 1 } }); - - expect(records.length).toBe(2); // should still be 2 records - expect(records[0].moves).toBe(12); - expect(records[1].moves).toBe(20); - - // get user - const u = await UserModel.findById(TestId.USER); - const b = await UserModel.findById(TestId.USER_B); - - expect(u.score).toBe(2); - expect(b.score).toBe(1); - - expect(b.calc_records).toEqual(0); - }, - }); - }); - test('Doing a PUT with USER_C user with correct minimum level solution should be OK', async () => { - const c = await UserModel.findById(TestId.USER_C); - - expect(c.score).toBe(1); - expect(c.calc_records).toBe(1); - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'PUT', - cookies: { - token: getTokenCookieValue(TestId.USER_C), - }, - body: { - codes: [ 'ArrowRight', 'ArrowDown', 'ArrowRight', 'ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowDown', 'ArrowDown'], - levelId: TestId.LEVEL - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - await processQueueMessages(); - expect(response.error).toBeUndefined(); - expect(response.success).toBe(true); - expect(res.status).toBe(200); - const lvl = await LevelModel.findById(TestId.LEVEL); - - expect(lvl.leastMoves).toBe(8); - expect(lvl.calc_stats_players_beaten).toBe(1); - // get records - const records = await RecordModel.find({ levelId: TestId.LEVEL }, {}, { sort: { moves: 1 } }); - - expect(records[0].moves).toBe(8); - expect(records[0].userId.toString()).toBe(TestId.USER_C); - expect(records[1].moves).toBe(12); - expect(records[2].moves).toBe(20); - expect(records.length).toBe(3); - - // get stat model for level - const stat = await StatModel.findOne({ userId: TestId.USER_C, levelId: TestId.LEVEL }); - - expect(stat.moves).toBe(8); - - // get user - const u = await UserModel.findById(TestId.USER); - const b = await UserModel.findById(TestId.USER_B); - const c = await UserModel.findById(TestId.USER_C); - - expect(u.score).toBe(1); // user a should have lost points - expect(u.calc_records).toBe(2); - expect(b.score).toBe(0); - expect(b.calc_records).toBe(0); - expect(c.score).toBe(2); // note that User C initializes with a score of 1 - expect(c.calc_records).toBe(2); // +1! - }, - }); - }); - test('REPEATING doing a PUT with TESTB user with correct minimum level solution should be OK and idempotent', async () => { - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'PUT', - cookies: { - token: getTokenCookieValue(TestId.USER_B), - }, - body: { - codes: [ 'ArrowRight', 'ArrowDown', 'ArrowRight', 'ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowDown', 'ArrowDown'], - levelId: TestId.LEVEL - }, - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await handler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - await processQueueMessages(); - expect(response.error).toBeUndefined(); - expect(response.success).toBe(true); - expect(res.status).toBe(200); - const lvl = await LevelModel.findById(TestId.LEVEL); - - expect(lvl.leastMoves).toBe(8); - expect(lvl.calc_stats_players_beaten).toBe(2); - // get records - const records = await RecordModel.find({ levelId: TestId.LEVEL }, {}, { sort: { moves: 1 } }); - - expect(records.length).toBe(3); - expect(records[0].moves).toBe(8); - expect(records[1].moves).toBe(12); - expect(records[2].moves).toBe(20); - - // get user - const u = await UserModel.findById(TestId.USER); - const b = await UserModel.findById(TestId.USER_B); - const c = await UserModel.findById(TestId.USER_C); - - expect(u.score).toBe(1); // user a should have lost points - expect(u.calc_records).toBe(2); - expect(b.score).toBe(1); // +1 - expect(b.calc_records).toBe(0); - expect(c.score).toBe(2); // note that User C initializes with a score of 1 - expect(c.calc_records).toBe(2); - }, - }); - }); - test('Unpublishing a level and make sure calc records get updated', async () => { - const user = await UserModel.findById(TestId.USER_C); - - expect(user.calc_records).toBe(2); - await testApiHandler({ - handler: async (_, res) => { - const req: NextApiRequestWithAuth = { - method: 'POST', - cookies: { - token: getTokenCookieValue(TestId.USER), - }, - query: { - id: TestId.LEVEL, - }, - - headers: { - 'content-type': 'application/json', - }, - } as unknown as NextApiRequestWithAuth; - - await unpublishLevelHandler(req, res); - }, - test: async ({ fetch }) => { - const res = await fetch(); - const response = await res.json(); - - await processQueueMessages(); - expect(response.error).toBeUndefined(); - expect(res.status).toBe(200); - expect(response.updated).toBe(true); - const b = await UserModel.findById(TestId.USER_C); - - expect(b.calc_records).toBe(1); - }, - }); - }); -}); diff --git a/tests/pages/api/stats/stats.test.ts b/tests/pages/api/stats/stats.test.ts index 4407b5b58..d72de6ea4 100644 --- a/tests/pages/api/stats/stats.test.ts +++ b/tests/pages/api/stats/stats.test.ts @@ -131,7 +131,7 @@ describe('Testing stats api', () => { const res = await fetch(); const response = await res.json(); - expect(response.error).toBe('Invalid body.levelId'); + expect(response.error).toBe('Invalid body.directions, body.levelId'); expect(res.status).toBe(400); }, }); @@ -197,12 +197,42 @@ describe('Testing stats api', () => { }, }); }); - test('Doing a PUT with a body but incorrect level solution should be OK', async () => { + test('Doing a PUT with an invalid direction should 400', async () => { + jest.spyOn(logger, 'error').mockImplementation(() => ({} as Logger)); + const levelId = new Types.ObjectId(); + + await testApiHandler({ + handler: async (_, res) => { + const req: NextApiRequestWithAuth = { + method: 'PUT', + cookies: { + token: getTokenCookieValue(TestId.USER), + }, + body: { + directions: [5, Direction.RIGHT], + levelId: levelId, + }, + headers: { + 'content-type': 'application/json', + }, + } as unknown as NextApiRequestWithAuth; + + await handler(req, res); + }, + test: async ({ fetch }) => { + const res = await fetch(); + const response = await res.json(); + + expect(response.error).toBe('Invalid direction provided: 5'); + expect(res.status).toBe(400); + }, + }); + }); + test('Doing a PUT with a body but incorrect level solution should 400', async () => { jest.spyOn(logger, 'error').mockImplementation(() => ({} as Logger)); - const codeTests = [ + const directionTests = [ [Direction.LEFT], // test left border - [5, Direction.RIGHT, Direction.RIGHT, Direction.RIGHT, Direction.RIGHT], // test invalid [Direction.RIGHT, Direction.RIGHT, Direction.DOWN, Direction.DOWN, Direction.LEFT, Direction.LEFT], // tries to go over hole [Direction.RIGHT, Direction.RIGHT, Direction.DOWN, Direction.DOWN, Direction.DOWN, Direction.DOWN], // tries to push directional movable where it cant be pushed [Direction.RIGHT, Direction.RIGHT, Direction.RIGHT, Direction.RIGHT, Direction.RIGHT], // tries to push directional movable where it cant be pushed because of edge @@ -211,7 +241,7 @@ describe('Testing stats api', () => { [Direction.RIGHT, Direction.RIGHT, Direction.DOWN, Direction.LEFT], // push movable into wall ]; - for (const codeTest of codeTests) { + for (const directionTest of directionTests) { await testApiHandler({ handler: async (_, res) => { const req: NextApiRequestWithAuth = { @@ -220,7 +250,7 @@ describe('Testing stats api', () => { token: getTokenCookieValue(TestId.USER), }, body: { - directions: codeTest, + directions: directionTest, levelId: TestId.LEVEL }, headers: {