Merge pull request #1217 from gradido/test-create-user-mutation

feat: Test Create User Mutation
This commit is contained in:
Moriz Wahl 2021-12-29 23:50:00 +01:00 committed by GitHub
commit a6eee850d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 290 additions and 43 deletions

View File

@ -509,7 +509,7 @@ jobs:
- name: backend | docker-compose
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mariadb database
- name: backend Unit tests | test
run: cd database && yarn && cd ../backend && yarn && yarn test
run: cd database && yarn && yarn build && cd ../backend && yarn && yarn CI_worklfow_test
# run: docker-compose -f docker-compose.yml -f docker-compose.test.yml exec -T backend yarn test
##########################################################################
# COVERAGE CHECK BACKEND #################################################
@ -520,7 +520,7 @@ jobs:
report_name: Coverage Backend
type: lcov
result_path: ./backend/coverage/lcov.info
min_coverage: 37
min_coverage: 40
token: ${{ github.token }}
##############################################################################

View File

@ -5,6 +5,11 @@ module.exports = {
collectCoverage: true,
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**'],
moduleNameMapper: {
'@entity/(.*)': '<rootDir>/../database/entity/$1',
'@entity/(.*)': '<rootDir>/../database/build/entity/$1',
// This is hack to fix a problem with the library `ts-mysql-migrate` which does differentiate between its ts/js state
'@dbTools/(.*)':
process.env.NODE_ENV === 'development'
? '<rootDir>/../database/src/$1'
: '<rootDir>/../database/build/src/$1',
},
}

View File

@ -13,7 +13,8 @@
"start": "node build/index.js",
"dev": "nodemon -w src --ext ts --exec ts-node src/index.ts",
"lint": "eslint . --ext .js,.ts",
"test": "jest --runInBand --coverage "
"CI_worklfow_test": "jest --runInBand --coverage ",
"test": "NODE_ENV=development jest --runInBand --coverage "
},
"dependencies": {
"@types/jest": "^27.0.2",
@ -59,6 +60,7 @@
"typescript": "^4.3.4"
},
"_moduleAliases": {
"@entity": "../database/build/entity"
"@entity": "../database/build/entity",
"@dbTools": "../database/build/src"
}
}

View File

@ -321,7 +321,5 @@ function isCreationValid(creations: number[], amount: number, creationDate: Date
async function hasActivatedEmail(email: string): Promise<boolean> {
const repository = getCustomRepository(LoginUserRepository)
const user = await repository.findByEmail(email)
let emailActivate = false
if (user) emailActivate = user.emailChecked
return user ? user.emailChecked : false
}

View File

@ -0,0 +1,232 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import { GraphQLError } from 'graphql'
import createServer from '../../server/createServer'
import { resetDB, initialize } from '@dbTools/helpers'
import { getRepository } from 'typeorm'
import { LoginUser } from '@entity/LoginUser'
import { LoginUserBackup } from '@entity/LoginUserBackup'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
import { User } from '@entity/User'
import CONFIG from '../../config'
import { sendEMail } from '../../util/sendEMail'
jest.mock('../../util/sendEMail', () => {
return {
__esModule: true,
sendEMail: jest.fn(),
}
})
let mutate: any
let con: any
beforeAll(async () => {
const server = await createServer({})
con = server.con
mutate = createTestClient(server.apollo).mutate
await initialize()
await resetDB()
})
describe('UserResolver', () => {
describe('createUser', () => {
const variables = {
email: 'peter@lustig.de',
firstName: 'Peter',
lastName: 'Lustig',
language: 'de',
publisherId: 1234,
}
const mutation = gql`
mutation (
$email: String!
$firstName: String!
$lastName: String!
$language: String!
$publisherId: Int
) {
createUser(
email: $email
firstName: $firstName
lastName: $lastName
language: $language
publisherId: $publisherId
)
}
`
let result: any
let emailOptIn: string
let newUser: User
beforeAll(async () => {
result = await mutate({ mutation, variables })
})
afterAll(async () => {
await resetDB()
})
it('returns success', () => {
expect(result).toEqual(expect.objectContaining({ data: { createUser: 'success' } }))
})
describe('valid input data', () => {
let loginUser: LoginUser[]
let user: User[]
let loginUserBackup: LoginUserBackup[]
let loginEmailOptIn: LoginEmailOptIn[]
beforeAll(async () => {
loginUser = await getRepository(LoginUser).createQueryBuilder('login_user').getMany()
user = await getRepository(User).createQueryBuilder('state_user').getMany()
loginUserBackup = await getRepository(LoginUserBackup)
.createQueryBuilder('login_user_backup')
.getMany()
loginEmailOptIn = await getRepository(LoginEmailOptIn)
.createQueryBuilder('login_email_optin')
.getMany()
newUser = user[0]
emailOptIn = loginEmailOptIn[0].verificationCode.toString()
})
describe('filling all tables', () => {
it('saves the user in login_user table', () => {
expect(loginUser).toEqual([
{
id: expect.any(Number),
email: 'peter@lustig.de',
firstName: 'Peter',
lastName: 'Lustig',
username: '',
description: '',
password: '0',
pubKey: null,
privKey: null,
emailHash: expect.any(Buffer),
createdAt: expect.any(Date),
emailChecked: false,
passphraseShown: false,
language: 'de',
disabled: false,
groupId: 1,
publisherId: 1234,
},
])
})
it('saves the user in state_user table', () => {
expect(user).toEqual([
{
id: expect.any(Number),
indexId: 0,
groupId: 0,
pubkey: expect.any(Buffer),
email: 'peter@lustig.de',
firstName: 'Peter',
lastName: 'Lustig',
username: '',
disabled: false,
},
])
})
it('saves the user in login_user_backup table', () => {
expect(loginUserBackup).toEqual([
{
id: expect.any(Number),
passphrase: expect.any(String),
userId: loginUser[0].id,
mnemonicType: 2,
},
])
})
it('creates an email optin', () => {
expect(loginEmailOptIn).toEqual([
{
id: expect.any(Number),
userId: loginUser[0].id,
verificationCode: expect.any(String),
emailOptInTypeId: 1,
createdAt: expect.any(Date),
resendCount: 0,
updatedAt: expect.any(Date),
},
])
})
})
})
describe('account activation email', () => {
it('sends an account activation email', () => {
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(/\$1/g, emailOptIn)
expect(sendEMail).toBeCalledWith({
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
to: `${newUser.firstName} ${newUser.lastName} <${newUser.email}>`,
subject: 'Gradido: E-Mail Überprüfung',
text:
expect.stringContaining(`Hallo ${newUser.firstName} ${newUser.lastName},`) &&
expect.stringContaining(activationLink),
})
})
})
describe('email already exists', () => {
it('throws an error', async () => {
await expect(mutate({ mutation, variables })).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('User already exists.')],
}),
)
})
})
describe('unknown language', () => {
it('sets "de" as default language', async () => {
await mutate({
mutation,
variables: { ...variables, email: 'bibi@bloxberg.de', language: 'es' },
})
await expect(
getRepository(LoginUser).createQueryBuilder('login_user').getMany(),
).resolves.toEqual(
expect.arrayContaining([
expect.objectContaining({
email: 'bibi@bloxberg.de',
language: 'de',
}),
]),
)
})
})
describe('no publisher id', () => {
it('sets publisher id to null', async () => {
await mutate({
mutation,
variables: { ...variables, email: 'raeuber@hotzenplotz.de', publisherId: undefined },
})
await expect(
getRepository(LoginUser).createQueryBuilder('login_user').getMany(),
).resolves.toEqual(
expect.arrayContaining([
expect.objectContaining({
email: 'raeuber@hotzenplotz.de',
publisherId: null,
}),
]),
)
})
})
})
})
afterAll(async () => {
await resetDB(true)
await con.close()
})

View File

@ -26,6 +26,7 @@ import { signIn } from '../../apis/KlicktippController'
import { RIGHTS } from '../../auth/RIGHTS'
import { ServerUserRepository } from '../../typeorm/repository/ServerUser'
import { ROLE_ADMIN } from '../../auth/ROLES'
import { randomBytes } from 'crypto'
const EMAIL_OPT_IN_RESET_PASSWORD = 2
const EMAIL_OPT_IN_REGISTER = 1
@ -432,7 +433,7 @@ export class UserResolver {
dbUser.lastName = lastName
dbUser.username = username
// TODO this field has no null allowed unlike the loginServer table
dbUser.pubkey = Buffer.alloc(32, 0) // default to 0000...
dbUser.pubkey = Buffer.from(randomBytes(32)) // Buffer.alloc(32, 0) default to 0000...
// dbUser.pubkey = keyPair[0]
await queryRunner.manager.save(dbUser).catch((er) => {
@ -471,13 +472,13 @@ export class UserResolver {
return 'success'
}
private async sendAccountActivationEmail(
private sendAccountActivationEmail(
activationLink: string,
firstName: string,
lastName: string,
email: string,
) {
const emailSent = await sendEMail({
): Promise<boolean> {
return sendEMail({
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
to: `${firstName} ${lastName} <${email}>`,
subject: 'Gradido: E-Mail Überprüfung',
@ -492,7 +493,6 @@ export class UserResolver {
Mit freundlichen Grüßen,
dein Gradido-Team`,
})
return emailSent
}
@Mutation(() => Boolean)

View File

@ -47,7 +47,8 @@
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
"paths": { /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"@entity/*": ["../database/entity/*"]
"@entity/*": ["../database/entity/*"],
"@dbTools/*": ["../database/src/*"]
},
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */

View File

@ -4,6 +4,5 @@ DB_USER=root
DB_PASSWORD=
DB_DATABASE=gradido_community
MIGRATIONS_TABLE=migrations
MIGRATIONS_DIRECTORY=./migrations/
TYPEORM_SEEDING_FACTORIES=src/factories/**/*{.ts,.js}

View File

@ -1,2 +0,0 @@
// For production you need to put your env file in here.
// Please copy the dist file from the root folder in here and rename it to .env

View File

@ -10,9 +10,9 @@
"scripts": {
"build": "tsc --build",
"clean": "tsc --build --clean",
"up": "cd build && node src/index.js up",
"down": "cd build && node src/index.js down",
"reset": "cd build && node src/index.js reset",
"up": "node build/src/index.js up",
"down": "node build/src/index.js down",
"reset": "node build/src/index.js reset",
"dev_up": "ts-node src/index.ts up",
"dev_down": "ts-node src/index.ts down",
"dev_reset": "ts-node src/index.ts reset",

View File

@ -13,7 +13,6 @@ const database = {
const migrations = {
MIGRATIONS_TABLE: process.env.MIGRATIONS_TABLE || 'migrations',
MIGRATIONS_DIRECTORY: process.env.MIGRATIONS_DIRECTORY || './migrations/',
}
const CONFIG = { ...database, ...migrations }

34
database/src/helpers.ts Normal file
View File

@ -0,0 +1,34 @@
import CONFIG from './config'
import { createPool, PoolConfig } from 'mysql'
import { Migration } from 'ts-mysql-migrate'
import path from 'path'
const poolConfig: PoolConfig = {
host: CONFIG.DB_HOST,
port: CONFIG.DB_PORT,
user: CONFIG.DB_USER,
password: CONFIG.DB_PASSWORD,
database: CONFIG.DB_DATABASE,
}
// Pool?
const pool = createPool(poolConfig)
// Create & Initialize Migrations
const migration = new Migration({
conn: pool,
tableName: CONFIG.MIGRATIONS_TABLE,
silent: true,
dir: path.join(__dirname, '..', 'migrations'),
})
const initialize = async (): Promise<void> => {
await migration.initialize()
}
const resetDB = async (closePool = false): Promise<void> => {
await migration.reset() // use for resetting database
if (closePool) pool.end()
}
export { resetDB, pool, migration, initialize }

View File

@ -1,7 +1,4 @@
import 'reflect-metadata'
import { createPool, PoolConfig } from 'mysql'
import { Migration } from 'ts-mysql-migrate'
import CONFIG from './config'
import prepare from './prepare'
import connection from './typeorm/connection'
import { useSeeding, runSeeder } from 'typeorm-seeding'
@ -10,30 +7,12 @@ import { CreateBibiBloxbergSeed } from './seeds/users/bibi-bloxberg.seed'
import { CreateRaeuberHotzenplotzSeed } from './seeds/users/raeuber-hotzenplotz.seed'
import { CreateBobBaumeisterSeed } from './seeds/users/bob-baumeister.seed'
import { DecayStartBlockSeed } from './seeds/decay-start-block.seed'
import { resetDB, pool, migration } from './helpers'
const run = async (command: string) => {
// Database actions not supported by our migration library
await prepare()
// Database connection for Migrations
const poolConfig: PoolConfig = {
host: CONFIG.DB_HOST,
port: CONFIG.DB_PORT,
user: CONFIG.DB_USER,
password: CONFIG.DB_PASSWORD,
database: CONFIG.DB_DATABASE,
}
// Pool?
const pool = createPool(poolConfig)
// Create & Initialize Migrations
const migration = new Migration({
conn: pool,
tableName: CONFIG.MIGRATIONS_TABLE,
dir: CONFIG.MIGRATIONS_DIRECTORY,
})
// Database connection for TypeORM
const con = await connection()
if (!con || !con.isConnected) {
@ -52,7 +31,7 @@ const run = async (command: string) => {
break
case 'reset':
// TODO protect from production
await migration.reset() // use for resetting database
await resetDB() // use for resetting database
break
case 'seed':
// TODO protect from production