fix(api): generate required properties for new users (#51822)

This commit is contained in:
Oliver Eyton-Williams
2023-10-07 12:50:30 +02:00
committed by GitHub
parent fac5c52be2
commit 948b7c52c2
6 changed files with 167 additions and 68 deletions
+3 -4
View File
@@ -1,7 +1,7 @@
import request from 'supertest';
import { build } from './src/app';
import { defaultUser } from './src/utils/default-user';
import { createUserInput } from './src/utils/create-user';
type FastifyTestInstance = Awaited<ReturnType<typeof build>>;
@@ -84,9 +84,8 @@ export async function devLogin(): Promise<string[]> {
await fastifyTestInstance.prisma.user.create({
data: {
...defaultUser,
id: defaultUserId,
email: defaultUserEmail
...createUserInput(defaultUserEmail),
id: defaultUserId
}
});
const res = await superRequest('/auth/dev-callback', { method: 'GET' });
+84 -4
View File
@@ -1,15 +1,95 @@
import { setupServer, superRequest } from '../../jest.utils';
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { defaultUserEmail, setupServer, superRequest } from '../../jest.utils';
describe('dev login', () => {
setupServer();
it('should create an account if one does not exist', async () => {
beforeEach(async () => {
await fastifyTestInstance.prisma.user.deleteMany({
where: { email: 'foo@bar.com' }
where: { email: defaultUserEmail }
});
});
afterAll(async () => {
await fastifyTestInstance.prisma.user.deleteMany({
where: { email: defaultUserEmail }
});
});
it('should create an account if one does not exist', async () => {
const res = await superRequest('/auth/dev-callback', { method: 'GET' });
const count = await fastifyTestInstance.prisma.user.count({
where: { email: defaultUserEmail }
});
const res = await superRequest('/auth/dev-callback', { method: 'GET' });
expect(count).toBe(1);
expect(res.body).toStrictEqual({ statusCode: 200 });
expect(res.status).toBe(200);
});
it('should populate the user with the correct data', async () => {
const uuidRe = /^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$/;
const fccUuidRe = /^fcc-[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$/;
await superRequest('/auth/dev-callback', { method: 'GET' });
const user = await fastifyTestInstance.prisma.user.findFirstOrThrow({
where: { email: defaultUserEmail }
});
expect(user).toMatchObject({
about: '',
acceptedPrivacyTerms: false,
completedChallenges: [],
currentChallengeId: '',
email: defaultUserEmail,
emailVerified: true,
externalId: expect.stringMatching(uuidRe),
is2018DataVisCert: false,
is2018FullStackCert: false,
isApisMicroservicesCert: false,
isBackEndCert: false,
isBanned: false,
isCheater: false,
isDataAnalysisPyCertV7: false,
isDataVisCert: false,
isDonating: false,
isFrontEndCert: false,
isFrontEndLibsCert: false,
isFullStackCert: false,
isHonest: false,
isInfosecCertV7: false,
isInfosecQaCert: false,
isJsAlgoDataStructCert: false,
isMachineLearningPyCertV7: false,
isQaCertV7: false,
isRelationalDatabaseCertV8: false,
isCollegeAlgebraPyCertV8: false,
isRespWebDesignCert: false,
isSciCompPyCertV7: false,
keyboardShortcuts: false,
location: '',
name: '',
unsubscribeId: '',
picture: '',
profileUI: {
isLocked: false,
showAbout: false,
showCerts: false,
showDonation: false,
showHeatMap: false,
showLocation: false,
showName: false,
showPoints: false,
showPortfolio: false,
showTimeLine: false
},
progressTimestamps: [],
sendQuincyEmail: false,
theme: 'default',
username: expect.stringMatching(fccUuidRe),
usernameDisplay: expect.stringMatching(fccUuidRe)
});
expect(user.username).toBe(user.usernameDisplay);
});
});
+2 -2
View File
@@ -4,7 +4,7 @@ import {
FastifyRequest
} from 'fastify';
import { defaultUser } from '../utils/default-user';
import { createUserInput } from '../utils/create-user';
import { AUTH0_DOMAIN, HOME_LOCATION } from '../utils/env';
declare module 'fastify' {
@@ -41,7 +41,7 @@ const findOrCreateUser = async (fastify: FastifyInstance, email: string) => {
return (
existingUser ??
(await fastify.prisma.user.create({
data: { ...defaultUser, email },
data: createUserInput(email),
select: { id: true }
}))
);
+2 -3
View File
@@ -6,7 +6,7 @@ import type { Prisma } from '@prisma/client';
import { ObjectId } from 'mongodb';
import _ from 'lodash';
import { defaultUser } from '../utils/default-user';
import { createUserInput } from '../utils/create-user';
import {
defaultUserId,
defaultUserEmail,
@@ -18,8 +18,7 @@ import { JWT_SECRET } from '../utils/env';
// This is used to build a test user.
const testUserData: Prisma.userCreateInput = {
...defaultUser,
email: defaultUserEmail,
...createUserInput(defaultUserEmail),
username: 'foobar',
usernameDisplay: 'Foo Bar',
progressTimestamps: [1520002973119, 1520440323273],
+76
View File
@@ -0,0 +1,76 @@
import crypto from 'node:crypto';
import { type Prisma } from '@prisma/client';
/**
* Creates the necessary data to create a new user.
* @param email The email address of the new user.
* @returns Default data for a new user.
*/
export function createUserInput(email: string): Prisma.userCreateInput {
const username = 'fcc-' + crypto.randomUUID();
const externalId = crypto.randomUUID();
// This explicitly includes all array fields. This is not strictly necessary -
// Prisma will return an empty array even if the property is missing, but it's
// probably best to add them to the document, at least until we normalise the
// data.
return {
about: '',
acceptedPrivacyTerms: false,
completedChallenges: [], // TODO(Post-MVP): Omit this from the document? (prisma will always return [])
currentChallengeId: '',
donationEmails: [], // TODO(Post-MVP): Omit this from the document? (prisma will always return [])
email,
emailVerified: true, // this should be true until a user changes their email address
// TODO(Post-MVP): remove externalId?
externalId,
is2018DataVisCert: false,
is2018FullStackCert: false,
isApisMicroservicesCert: false,
isBackEndCert: false,
isBanned: false,
isCheater: false,
isDataAnalysisPyCertV7: false,
isDataVisCert: false,
isDonating: false,
isFrontEndCert: false,
isFrontEndLibsCert: false,
isFullStackCert: false,
isHonest: false,
isInfosecCertV7: false,
isInfosecQaCert: false,
isJsAlgoDataStructCert: false,
isMachineLearningPyCertV7: false,
isQaCertV7: false,
isRelationalDatabaseCertV8: false,
isCollegeAlgebraPyCertV8: false,
isRespWebDesignCert: false,
isSciCompPyCertV7: false,
keyboardShortcuts: false,
location: '',
name: '',
unsubscribeId: '',
partiallyCompletedChallenges: [], // TODO(Post-MVP): Omit this from the document? (prisma will always return [])
picture: '',
portfolio: [], // TODO(Post-MVP): Omit this from the document? (prisma will always return [])
profileUI: {
isLocked: false,
showAbout: false,
showCerts: false,
showDonation: false,
showHeatMap: false,
showLocation: false,
showName: false,
showPoints: false,
showPortfolio: false,
showTimeLine: false
},
progressTimestamps: [], // TODO(Post-MVP): This may need normalising before we can omit it.
savedChallenges: [], // TODO(Post-MVP): Omit this from the document? (prisma will always return [])
sendQuincyEmail: false,
theme: 'default',
username,
usernameDisplay: username,
yearsTopContributor: [] // TODO: Omit this from the document? (prisma will always return [])
};
}
-55
View File
@@ -1,55 +0,0 @@
// TODO: audit this object to find out which properties need to be updated.
import { type Prisma } from '@prisma/client';
export const defaultUser: Omit<Prisma.userCreateInput, 'email'> = {
about: '',
acceptedPrivacyTerms: false,
completedChallenges: [],
currentChallengeId: '',
emailVerified: true, // this should be true until a user changes their email address
externalId: '',
is2018DataVisCert: false,
is2018FullStackCert: false,
isApisMicroservicesCert: false,
isBackEndCert: false,
isBanned: false,
isCheater: false,
isDataAnalysisPyCertV7: false,
isDataVisCert: false,
isDonating: false,
isFrontEndCert: false,
isFrontEndLibsCert: false,
isFullStackCert: false,
isHonest: false,
isInfosecCertV7: false,
isInfosecQaCert: false,
isJsAlgoDataStructCert: false,
isMachineLearningPyCertV7: false,
isQaCertV7: false,
isRelationalDatabaseCertV8: false,
isCollegeAlgebraPyCertV8: false,
isRespWebDesignCert: false,
isSciCompPyCertV7: false,
keyboardShortcuts: false,
location: '',
name: '',
unsubscribeId: '',
picture: '',
profileUI: {
isLocked: false,
showAbout: false,
showCerts: false,
showDonation: false,
showHeatMap: false,
showLocation: false,
showName: false,
showPoints: false,
showPortfolio: false,
showTimeLine: false
},
progressTimestamps: [],
sendQuincyEmail: false,
theme: 'default',
// TODO: generate a UUID like in api-server
username: ''
};