2021-07-06 19:01:17 +05:30

404 lines
11 KiB
JavaScript

import { first, find } from 'lodash';
import {
buildUserUpdate,
buildChallengeUrl,
createChallengeUrlResolver,
createRedirectToCurrentChallenge,
getFirstChallenge,
isValidChallengeCompletion
} from '../boot/challenge';
import {
firstChallengeUrl,
requestedChallengeUrl,
mockAllChallenges,
mockChallenge,
mockUser,
mockGetFirstChallenge,
mockCompletedChallenge,
mockCompletedChallenges
} from './fixtures';
export const mockReq = opts => {
const req = {};
return { ...req, ...opts };
};
export const mockRes = opts => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
res.redirect = jest.fn().mockReturnValue(res);
res.set = jest.fn().mockReturnValue(res);
res.clearCookie = jest.fn().mockReturnValue(res);
res.cookie = jest.fn().mockReturnValue(res);
return { ...res, ...opts };
};
describe('boot/challenge', () => {
xdescribe('backendChallengeCompleted', () => {});
describe('buildUserUpdate', () => {
it('returns an Object with a nested "completedChallenges" property', () => {
const result = buildUserUpdate(
mockUser,
'123abc',
mockCompletedChallenge,
'UTC'
);
expect(result).toHaveProperty('updateData.$set.completedChallenges');
});
// eslint-disable-next-line max-len
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);
});
// eslint-disable-next-line max-len
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);
});
// eslint-disable-next-line max-len
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
);
});
// eslint-disable-next-line max-len
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', () => {
const result = buildChallengeUrl(mockChallenge);
expect(result).toEqual(requestedChallengeUrl);
});
});
describe('challengeUrlResolver', () => {
it('resolves to the first challenge url by default', async () => {
const challengeUrlResolver = await createChallengeUrlResolver(
mockAllChallenges,
{
_getFirstChallenge: mockGetFirstChallenge
}
);
return challengeUrlResolver().then(url => {
expect(url).toEqual(firstChallengeUrl);
});
}, 10000);
// eslint-disable-next-line max-len
it('returns the first challenge url if the provided id does not relate to a challenge', async () => {
const challengeUrlResolver = await createChallengeUrlResolver(
mockAllChallenges,
{
_getFirstChallenge: mockGetFirstChallenge
}
);
return challengeUrlResolver('not-a-real-challenge').then(url => {
expect(url).toEqual(firstChallengeUrl);
});
});
it('resolves the correct url for the requested challenge', async () => {
const challengeUrlResolver = await createChallengeUrlResolver(
mockAllChallenges,
{
_getFirstChallenge: mockGetFirstChallenge
}
);
return challengeUrlResolver('123abc').then(url => {
expect(url).toEqual(requestedChallengeUrl);
});
});
});
describe('getFirstChallenge', () => {
it('returns the correct challenge url from the model', async () => {
const result = await getFirstChallenge(mockAllChallenges);
expect(result).toEqual(firstChallengeUrl);
});
it('returns the learn base if no challenges found', async () => {
const result = await getFirstChallenge([]);
expect(result).toEqual('/learn');
});
});
describe('isValidChallengeCompletion', () => {
const validObjectId = '5c716d1801013c3ce3aa23e6';
it('declares a 403 for an invalid id in the body', () => {
expect.assertions(2);
const req = mockReq({
body: { id: 'not-a-real-id' }
});
const res = mockRes();
const next = jest.fn();
isValidChallengeCompletion(req, res, next);
expect(res.status).toHaveBeenCalledWith(403);
expect(next).not.toHaveBeenCalled();
});
it('declares a 403 for an invalid challengeType in the body', () => {
expect.assertions(2);
const req = mockReq({
body: { id: validObjectId, challengeType: 'ponyfoo' }
});
const res = mockRes();
const next = jest.fn();
isValidChallengeCompletion(req, res, next);
expect(res.status).toHaveBeenCalledWith(403);
expect(next).not.toHaveBeenCalled();
});
it('declares a 403 for an invalid solution in the body', () => {
expect.assertions(2);
const req = mockReq({
body: {
id: validObjectId,
challengeType: '1',
solution: 'https://not-a-url'
}
});
const res = mockRes();
const next = jest.fn();
isValidChallengeCompletion(req, res, next);
expect(res.status).toHaveBeenCalledWith(403);
expect(next).not.toHaveBeenCalled();
});
it('calls next if the body is valid', () => {
const req = mockReq({
body: {
id: validObjectId,
challengeType: '1',
solution: 'https://www.freecodecamp.org'
}
});
const res = mockRes();
const next = jest.fn();
isValidChallengeCompletion(req, res, next);
expect(next).toHaveBeenCalled();
});
it('calls next if only the id is provided', () => {
const req = mockReq({
body: {
id: validObjectId
}
});
const res = mockRes();
const next = jest.fn();
isValidChallengeCompletion(req, res, next);
expect(next).toHaveBeenCalled();
});
it('can handle an "int" challengeType', () => {
const req = mockReq({
body: {
id: validObjectId,
challengeType: 1
}
});
const res = mockRes();
const next = jest.fn();
isValidChallengeCompletion(req, res, next);
expect(next).toHaveBeenCalled();
});
});
xdescribe('modernChallengeCompleted', () => {});
xdescribe('projectCompleted', () => {});
describe('redirectToCurrentChallenge', () => {
const mockHomeLocation = 'https://www.example.com';
const mockLearnUrl = `${mockHomeLocation}/learn`;
const mockgetParamsFromReq = () => ({
returnTo: mockLearnUrl,
origin: mockHomeLocation,
pathPrefix: ''
});
const mockNormalizeParams = params => params;
it('redirects to the learn base url for non-users', async () => {
const redirectToCurrentChallenge = createRedirectToCurrentChallenge(
() => {},
mockNormalizeParams,
mockgetParamsFromReq
);
const req = mockReq();
const res = mockRes();
const next = jest.fn();
await redirectToCurrentChallenge(req, res, next);
expect(res.redirect).toHaveBeenCalledWith(mockLearnUrl);
});
// eslint-disable-next-line max-len
it('redirects to the url provided by the challengeUrlResolver', async () => {
const challengeUrlResolver = await createChallengeUrlResolver(
mockAllChallenges,
{
_getFirstChallenge: mockGetFirstChallenge
}
);
const expectedUrl = `${mockHomeLocation}${requestedChallengeUrl}`;
const redirectToCurrentChallenge = createRedirectToCurrentChallenge(
challengeUrlResolver,
mockNormalizeParams,
mockgetParamsFromReq
);
const req = mockReq({
user: mockUser
});
const res = mockRes();
const next = jest.fn();
await redirectToCurrentChallenge(req, res, next);
expect(res.redirect).toHaveBeenCalledWith(expectedUrl);
});
// eslint-disable-next-line max-len
it('redirects to the first challenge for users without a currentChallengeId', async () => {
const challengeUrlResolver = await createChallengeUrlResolver(
mockAllChallenges,
{
_getFirstChallenge: mockGetFirstChallenge
}
);
const redirectToCurrentChallenge = createRedirectToCurrentChallenge(
challengeUrlResolver,
mockNormalizeParams,
mockgetParamsFromReq
);
const req = mockReq({
user: { ...mockUser, currentChallengeId: '' }
});
const res = mockRes();
const next = jest.fn();
await redirectToCurrentChallenge(req, res, next);
const expectedUrl = `${mockHomeLocation}${firstChallengeUrl}`;
expect(res.redirect).toHaveBeenCalledWith(expectedUrl);
});
});
});