From 953e1b2e113245186451e53c58afe2828f0afe47 Mon Sep 17 00:00:00 2001 From: Bouncey Date: Sat, 23 Feb 2019 21:43:51 +0000 Subject: [PATCH] feat: Add tests for challenge>buildUserUpdate --- api-server/server/boot/challenge.js | 8 +- .../server/boot_tests/challenge.test.js | 178 ++++++++++++++---- api-server/server/boot_tests/fixtures.js | 83 ++++++++ 3 files changed, 227 insertions(+), 42 deletions(-) create mode 100644 api-server/server/boot_tests/fixtures.js diff --git a/api-server/server/boot/challenge.js b/api-server/server/boot/challenge.js index 094e6668b0..758a35fa5b 100644 --- a/api-server/server/boot/challenge.js +++ b/api-server/server/boot/challenge.js @@ -74,10 +74,14 @@ const jsProjects = [ 'aa2e6f85cab2ab736c9a9b24' ]; -function buildUserUpdate(user, challengeId, _completedChallenge, timezone) { +export function buildUserUpdate( + user, + challengeId, + _completedChallenge, + timezone +) { const { files } = _completedChallenge; let completedChallenge = {}; - if (jsProjects.includes(challengeId)) { completedChallenge = { ..._completedChallenge, diff --git a/api-server/server/boot_tests/challenge.test.js b/api-server/server/boot_tests/challenge.test.js index 9d14296e71..68d173684b 100644 --- a/api-server/server/boot_tests/challenge.test.js +++ b/api-server/server/boot_tests/challenge.test.js @@ -1,9 +1,10 @@ /* global describe xdescribe it expect */ -import { isEqual } from 'lodash'; +import { isEqual, first, find } from 'lodash'; import sinon from 'sinon'; import { mockReq, mockRes } from 'sinon-express-mock'; import { + buildUserUpdate, buildChallengeUrl, createChallengeUrlResolver, createRedirectToCurrentChallenge, @@ -11,48 +12,144 @@ import { isValidChallengeCompletion } from '../boot/challenge'; -const firstChallengeUrl = '/learn/the/first/challenge'; -const requestedChallengeUrl = '/learn/my/actual/challenge'; -const mockChallenge = { - id: '123abc', - block: 'actual', - superBlock: 'my', - dashedName: 'challenge' -}; -const mockFirstChallenge = { - id: '456def', - block: 'first', - superBlock: 'the', - dashedName: 'challenge' -}; -const mockUser = { - username: 'camperbot', - currentChallengeId: '123abc' -}; -const mockApp = { - models: { - Challenge: { - find() { - return firstChallengeUrl; - }, - findById(id, cb) { - return id === mockChallenge.id - ? cb(null, mockChallenge) - : cb(new Error('challenge not found')); - } - } - } -}; -const mockGetFirstChallenge = () => firstChallengeUrl; -const firstChallengeQuery = { - // first challenge of the first block of the first superBlock - where: { challengeOrder: 0, superOrder: 1, order: 0 } -}; +import { + firstChallengeUrl, + requestedChallengeUrl, + mockChallenge, + mockFirstChallenge, + mockUser, + mockApp, + mockGetFirstChallenge, + firstChallengeQuery, + mockCompletedChallenge, + mockCompletedChallenges +} from './fixtures'; describe('boot/challenge', () => { xdescribe('backendChallengeCompleted'); - xdescribe('buildUserUpdate'); + describe('buildUserUpdate', () => { + it('returns an Object with a nested "completedChallenges" property', () => { + const result = buildUserUpdate( + mockUser, + '123abc', + mockCompletedChallenge, + 'UTC' + ); + expect(result).toHaveProperty('updateData.$set.completedChallenges'); + }); + + it('preserves file contents if the completed challenge is a JS Project', () => { + const jsChallengeId = 'aa2e6f85cab2ab736c9a9b24'; + const completedChallenge = { + ...mockCompletedChallenge, + completedDate: Date.now(), + id: jsChallengeId + }; + const result = buildUserUpdate( + mockUser, + jsChallengeId, + completedChallenge, + 'UTC' + ); + const firstCompletedChallenge = first( + result.updateData.$set.completedChallenges + ); + + expect(firstCompletedChallenge).toEqual(completedChallenge); + }); + + it('preserves the original completed date of a challenge', () => { + const completedChallengeId = 'aaa48de84e1ecc7c742e1124'; + const completedChallenge = { + ...mockCompletedChallenge, + completedDate: Date.now(), + id: completedChallengeId + }; + const originalCompletion = find( + mockCompletedChallenges, + x => x.id === completedChallengeId + ).completedDate; + const result = buildUserUpdate( + mockUser, + completedChallengeId, + completedChallenge, + 'UTC' + ); + + const firstCompletedChallenge = first( + result.updateData.$set.completedChallenges + ); + + expect(firstCompletedChallenge.completedDate).toEqual(originalCompletion); + }); + + it('does not attempt to update progressTimestamps for a previously completed challenge', () => { + const completedChallengeId = 'aaa48de84e1ecc7c742e1124'; + const completedChallenge = { + ...mockCompletedChallenge, + completedDate: Date.now(), + id: completedChallengeId + }; + const { updateData } = buildUserUpdate( + mockUser, + completedChallengeId, + completedChallenge, + 'UTC' + ); + + const hasProgressTimestamps = + '$push' in updateData && 'progressTimestamps' in updateData.$push; + expect(hasProgressTimestamps).toBe(false); + }); + + it('provides a progressTimestamps update for new challenge completion', () => { + expect.assertions(2); + const { updateData } = buildUserUpdate( + mockUser, + '123abc', + mockCompletedChallenge, + 'UTC' + ); + expect(updateData).toHaveProperty('$push'); + expect(updateData.$push).toHaveProperty('progressTimestamps'); + }); + + it('removes repeat completions from the completedChallenges array', () => { + const completedChallengeId = 'aaa48de84e1ecc7c742e1124'; + const completedChallenge = { + ...mockCompletedChallenge, + completedDate: Date.now(), + id: completedChallengeId + }; + const { + updateData: { + $set: { completedChallenges } + } + } = buildUserUpdate( + mockUser, + completedChallengeId, + completedChallenge, + 'UTC' + ); + + expect(completedChallenges.length).toEqual( + mockCompletedChallenges.length + ); + }); + + it('adds newly completed challenges to the completedChallenges array', () => { + const { + updateData: { + $set: { completedChallenges } + } + } = buildUserUpdate(mockUser, '123abc', mockCompletedChallenge, 'UTC'); + + expect(completedChallenges.length).toEqual( + mockCompletedChallenges.length + 1 + ); + }); + }); describe('buildChallengeUrl', () => { it('resolves the correct Url for the provided challenge', () => { @@ -122,6 +219,7 @@ describe('boot/challenge', () => { expect(result).toEqual(firstChallengeUrl); }); + it('returns the learn base if no challenges found', async () => { const result = await getFirstChallenge(createMockChallengeModel(false)); @@ -212,7 +310,7 @@ describe('boot/challenge', () => { }); }); - xdescribe('modernChallengeCompleted', () => {}); + xdescribe('modernChallengeCompleted'); xdescribe('projectCompleted'); diff --git a/api-server/server/boot_tests/fixtures.js b/api-server/server/boot_tests/fixtures.js new file mode 100644 index 0000000000..8c94335c25 --- /dev/null +++ b/api-server/server/boot_tests/fixtures.js @@ -0,0 +1,83 @@ +export const firstChallengeUrl = '/learn/the/first/challenge'; +export const requestedChallengeUrl = '/learn/my/actual/challenge'; +export const mockChallenge = { + id: '123abc', + block: 'actual', + superBlock: 'my', + dashedName: 'challenge' +}; +export const mockFirstChallenge = { + id: '456def', + block: 'first', + superBlock: 'the', + dashedName: 'challenge' +}; +export const mockCompletedChallenge = { + id: '890xyz', + challengeType: 0, + files: [ + { + contents: 'file contents', + key: 'indexfile', + name: 'index', + path: 'index.file', + ext: 'file' + } + ], + completedDate: Date.now() +}; +export const mockCompletedChallenges = [ + { + id: 'bd7123c8c441eddfaeb5bdef', + completedDate: 1538052380328.0 + }, + { + id: '587d7dbd367417b2b2512bb4', + completedDate: 1547472893032.0, + files: [] + }, + { + id: 'aaa48de84e1ecc7c742e1124', + completedDate: 1541678430790.0, + files: [ + { + contents: + "function palindrome(str) {\n const clean = str.replace(/[\\W_]/g, '').toLowerCase()\n const revStr = clean.split('').reverse().join('');\n return clean === revStr;\n}\n\n\n\npalindrome(\"eye\");\n", + ext: 'js', + path: 'index.js', + name: 'index', + key: 'indexjs' + } + ] + }, + { + id: '5a24c314108439a4d4036164', + completedDate: 1543845124143.0, + files: [] + } +]; +export const mockUser = { + username: 'camperbot', + currentChallengeId: '123abc', + timezone: 'UTC', + completedChallenges: mockCompletedChallenges +}; +export const mockApp = { + models: { + Challenge: { + find() { + return firstChallengeUrl; + }, + findById(id, cb) { + return id === mockChallenge.id + ? cb(null, mockChallenge) + : cb(new Error('challenge not found')); + } + } + } +}; +export const mockGetFirstChallenge = () => firstChallengeUrl; +export const firstChallengeQuery = { + // first challenge of the first block of the first superBlock + where: { challengeOrder: 0, superOrder: 1, order: 0 } +};