mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge pull request #1217 from gradido/test-create-user-mutation
feat: Test Create User Mutation
This commit is contained in:
commit
a6eee850d0
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@ -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 }}
|
||||
|
||||
##############################################################################
|
||||
|
||||
@ -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',
|
||||
},
|
||||
}
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
232
backend/src/graphql/resolver/UserResolver.test.ts
Normal file
232
backend/src/graphql/resolver/UserResolver.test.ts
Normal 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()
|
||||
})
|
||||
@ -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)
|
||||
|
||||
@ -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. */
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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
|
||||
@ -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",
|
||||
|
||||
@ -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
34
database/src/helpers.ts
Normal 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 }
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user