Merge pull request #1619 from gradido/seed-in-backend

refactor: Seed in Backend
This commit is contained in:
Moriz Wahl 2022-03-16 19:23:52 +01:00 committed by GitHub
commit 2ddf07f112
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 470 additions and 114 deletions

View File

@ -3,7 +3,7 @@ module.exports = {
verbose: true,
preset: 'ts-jest',
collectCoverage: true,
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**'],
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**'],
setupFiles: ['<rootDir>/test/testSetup.ts'],
moduleNameMapper: {
'@/(.*)': '<rootDir>/src/$1',

View File

@ -13,7 +13,8 @@
"start": "node build/src/index.js",
"dev": "nodemon -w src --ext ts --exec ts-node src/index.ts",
"lint": "eslint --max-warnings=0 --ext .js,.ts .",
"test": "TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles"
"test": "TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles",
"seed": "TZ=UTC ts-node src/seeds/index.ts"
},
"dependencies": {
"@types/jest": "^27.0.2",
@ -42,6 +43,7 @@
},
"devDependencies": {
"@types/express": "^4.17.12",
"@types/faker": "^5.5.9",
"@types/jsonwebtoken": "^8.5.2",
"@types/node": "^16.10.3",
"@types/nodemailer": "^6.4.4",
@ -54,6 +56,7 @@
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-promise": "^5.1.0",
"faker": "^5.5.3",
"nodemon": "^2.0.7",
"prettier": "^2.3.1",
"ts-node": "^10.0.0",

View File

@ -1,9 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { testEnvironment, createUser, headerPushMock, cleanDB, resetToken } from '@test/helpers'
import { createUserMutation, setPasswordMutation } from '@test/graphql'
import gql from 'graphql-tag'
import { testEnvironment, headerPushMock, resetToken, cleanDB } from '@test/helpers'
import { createUserFactory } from '@/seeds/factory/user'
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { createUser, setPassword } from '@/seeds/graphql/mutations'
import { login, logout } from '@/seeds/graphql/queries'
import { GraphQLError } from 'graphql'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
import { User } from '@entity/User'
@ -11,8 +13,6 @@ import CONFIG from '@/config'
import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
// import { klicktippSignIn } from '@/apis/KlicktippController'
jest.setTimeout(1000000)
jest.mock('@/mailer/sendAccountActivationEmail', () => {
return {
__esModule: true,
@ -31,24 +31,6 @@ jest.mock('@/apis/KlicktippController', () => {
let mutate: any, query: any, con: any
const loginQuery = gql`
query ($email: String!, $password: String!, $publisherId: Int) {
login(email: $email, password: $password, publisherId: $publisherId) {
email
firstName
lastName
language
coinanimation
klickTipp {
newsletterState
}
hasElopage
publisherId
isAdmin
}
}
`
beforeAll(async () => {
const testEnv = await testEnvironment()
mutate = testEnv.mutate
@ -77,7 +59,7 @@ describe('UserResolver', () => {
beforeAll(async () => {
jest.clearAllMocks()
result = await mutate({ mutation: createUserMutation, variables })
result = await mutate({ mutation: createUser, variables })
})
afterAll(async () => {
@ -149,7 +131,7 @@ describe('UserResolver', () => {
describe('email already exists', () => {
it('throws an error', async () => {
await expect(mutate({ mutation: createUserMutation, variables })).resolves.toEqual(
await expect(mutate({ mutation: createUser, variables })).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('User already exists.')],
}),
@ -160,7 +142,7 @@ describe('UserResolver', () => {
describe('unknown language', () => {
it('sets "de" as default language', async () => {
await mutate({
mutation: createUserMutation,
mutation: createUser,
variables: { ...variables, email: 'bibi@bloxberg.de', language: 'es' },
})
await expect(User.find()).resolves.toEqual(
@ -177,7 +159,7 @@ describe('UserResolver', () => {
describe('no publisher id', () => {
it('sets publisher id to null', async () => {
await mutate({
mutation: createUserMutation,
mutation: createUser,
variables: { ...variables, email: 'raeuber@hotzenplotz.de', publisherId: undefined },
})
await expect(User.find()).resolves.toEqual(
@ -208,11 +190,11 @@ describe('UserResolver', () => {
let newUser: any
beforeAll(async () => {
await mutate({ mutation: createUserMutation, variables: createUserVariables })
await mutate({ mutation: createUser, variables: createUserVariables })
const loginEmailOptIn = await LoginEmailOptIn.find()
emailOptIn = loginEmailOptIn[0].verificationCode.toString()
result = await mutate({
mutation: setPasswordMutation,
mutation: setPassword,
variables: { code: emailOptIn, password: 'Aa12345_' },
})
newUser = await User.find()
@ -252,11 +234,11 @@ describe('UserResolver', () => {
describe('no valid password', () => {
beforeAll(async () => {
await mutate({ mutation: createUserMutation, variables: createUserVariables })
await mutate({ mutation: createUser, variables: createUserVariables })
const loginEmailOptIn = await LoginEmailOptIn.find()
emailOptIn = loginEmailOptIn[0].verificationCode.toString()
result = await mutate({
mutation: setPasswordMutation,
mutation: setPassword,
variables: { code: emailOptIn, password: 'not-valid' },
})
})
@ -280,9 +262,9 @@ describe('UserResolver', () => {
describe('no valid optin code', () => {
beforeAll(async () => {
await mutate({ mutation: createUserMutation, variables: createUserVariables })
await mutate({ mutation: createUser, variables: createUserVariables })
result = await mutate({
mutation: setPasswordMutation,
mutation: setPassword,
variables: { code: 'not valid', password: 'Aa12345_' },
})
})
@ -303,7 +285,7 @@ describe('UserResolver', () => {
describe('login', () => {
const variables = {
email: 'peter@lustig.de',
email: 'bibi@bloxberg.de',
password: 'Aa12345_',
publisherId: 1234,
}
@ -316,7 +298,7 @@ describe('UserResolver', () => {
describe('no users in database', () => {
beforeAll(async () => {
result = await query({ query: loginQuery, variables })
result = await query({ query: login, variables })
})
it('throws an error', () => {
@ -330,14 +312,8 @@ describe('UserResolver', () => {
describe('user is in database and correct login data', () => {
beforeAll(async () => {
await createUser(mutate, {
email: 'peter@lustig.de',
firstName: 'Peter',
lastName: 'Lustig',
language: 'de',
publisherId: 1234,
})
result = await query({ query: loginQuery, variables })
await createUserFactory(mutate, bibiBloxberg)
result = await query({ query: login, variables })
})
afterAll(async () => {
@ -350,15 +326,15 @@ describe('UserResolver', () => {
data: {
login: {
coinanimation: true,
email: 'peter@lustig.de',
firstName: 'Peter',
email: 'bibi@bloxberg.de',
firstName: 'Bibi',
hasElopage: false,
isAdmin: false,
klickTipp: {
newsletterState: false,
},
language: 'de',
lastName: 'Lustig',
lastName: 'Bloxberg',
publisherId: 1234,
},
},
@ -373,13 +349,7 @@ describe('UserResolver', () => {
describe('user is in database and wrong password', () => {
beforeAll(async () => {
await createUser(mutate, {
email: 'peter@lustig.de',
firstName: 'Peter',
lastName: 'Lustig',
language: 'de',
publisherId: 1234,
})
await createUserFactory(mutate, bibiBloxberg)
})
afterAll(async () => {
@ -388,7 +358,7 @@ describe('UserResolver', () => {
it('returns an error', () => {
expect(
query({ query: loginQuery, variables: { ...variables, password: 'wrong' } }),
query({ query: login, variables: { ...variables, password: 'wrong' } }),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('No user with this credentials')],
@ -399,16 +369,10 @@ describe('UserResolver', () => {
})
describe('logout', () => {
const logoutQuery = gql`
query {
logout
}
`
describe('unauthenticated', () => {
it('throws an error', async () => {
resetToken()
await expect(query({ query: logoutQuery })).resolves.toEqual(
await expect(query({ query: logout })).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('401 Unauthorized')],
}),
@ -418,19 +382,13 @@ describe('UserResolver', () => {
describe('authenticated', () => {
const variables = {
email: 'peter@lustig.de',
email: 'bibi@bloxberg.de',
password: 'Aa12345_',
}
beforeAll(async () => {
await createUser(mutate, {
email: 'peter@lustig.de',
firstName: 'Peter',
lastName: 'Lustig',
language: 'de',
publisherId: 1234,
})
await query({ query: loginQuery, variables })
await createUserFactory(mutate, bibiBloxberg)
await query({ query: login, variables })
})
afterAll(async () => {
@ -438,7 +396,7 @@ describe('UserResolver', () => {
})
it('returns true', async () => {
await expect(query({ query: logoutQuery })).resolves.toEqual(
await expect(query({ query: logout })).resolves.toEqual(
expect.objectContaining({
data: { logout: 'true' },
errors: undefined,

View File

@ -0,0 +1,43 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { createUser, setPassword } from '@/seeds/graphql/mutations'
import { User } from '@entity/User'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
import { ServerUser } from '@entity/ServerUser'
import { UserInterface } from '@/seeds/users/UserInterface'
export const createUserFactory = async (mutate: any, user: UserInterface): Promise<void> => {
await mutate({ mutation: createUser, variables: user })
let dbUser = await User.findOneOrFail({ where: { email: user.email } })
if (user.emailChecked) {
const optin = await LoginEmailOptIn.findOneOrFail({ where: { userId: dbUser.id } })
await mutate({
mutation: setPassword,
variables: { password: 'Aa12345_', code: optin.verificationCode },
})
}
// refetch data
dbUser = await User.findOneOrFail({ where: { email: user.email } })
if (user.createdAt || user.deletedAt) {
if (user.createdAt) dbUser.createdAt = user.createdAt
if (user.deletedAt) dbUser.deletedAt = user.deletedAt
await dbUser.save()
}
if (user.isAdmin) {
const admin = new ServerUser()
admin.username = dbUser.firstName
admin.password = 'please_refactor'
admin.email = dbUser.email
admin.role = 'admin'
admin.activated = 1
admin.lastLogin = new Date()
admin.created = dbUser.createdAt
admin.modified = dbUser.createdAt
await admin.save()
}
}

View File

@ -0,0 +1,9 @@
export const GdtEntryType = {
FORM: 'FORM',
CVS: 'CVS',
ELOPAGE: 'ELOPAGE',
ELOPAGE_PUBLISHER: 'ELOPAGE_PUBLISHER',
DIGISTORE: 'DIGISTORE',
CVS2: 'CVS2',
GLOBAL_MODIFICATOR: 'GLOBAL_MODIFICATOR',
}

View File

@ -0,0 +1,71 @@
import gql from 'graphql-tag'
export const subscribeNewsletter = gql`
mutation ($email: String!, $language: String!) {
subscribeNewsletter(email: $email, language: $language)
}
`
export const unsubscribeNewsletter = gql`
mutation ($email: String!) {
unsubscribeNewsletter(email: $email)
}
`
export const setPassword = gql`
mutation ($code: String!, $password: String!) {
setPassword(code: $code, password: $password)
}
`
export const updateUserInfos = gql`
mutation (
$firstName: String
$lastName: String
$password: String
$passwordNew: String
$locale: String
$coinanimation: Boolean
) {
updateUserInfos(
firstName: $firstName
lastName: $lastName
password: $password
passwordNew: $passwordNew
language: $locale
coinanimation: $coinanimation
)
}
`
export const createUser = gql`
mutation (
$firstName: String!
$lastName: String!
$email: String!
$language: String!
$publisherId: Int
) {
createUser(
email: $email
firstName: $firstName
lastName: $lastName
language: $language
publisherId: $publisherId
)
}
`
export const sendCoins = gql`
mutation ($email: String!, $amount: Decimal!, $memo: String!) {
sendCoins(email: $email, amount: $amount, memo: $memo)
}
`
export const createTransactionLink = gql`
mutation ($amount: Decimal!, $memo: String!) {
createTransactionLink(amount: $amount, memo: $memo) {
code
}
}
`

View File

@ -0,0 +1,144 @@
import gql from 'graphql-tag'
export const login = gql`
query ($email: String!, $password: String!, $publisherId: Int) {
login(email: $email, password: $password, publisherId: $publisherId) {
email
firstName
lastName
language
coinanimation
klickTipp {
newsletterState
}
hasElopage
publisherId
isAdmin
}
}
`
export const verifyLogin = gql`
query {
verifyLogin {
email
firstName
lastName
language
coinanimation
klickTipp {
newsletterState
}
hasElopage
publisherId
isAdmin
}
}
`
export const logout = gql`
query {
logout
}
`
export const transactionsQuery = gql`
query (
$currentPage: Int = 1
$pageSize: Int = 25
$order: Order = DESC
$onlyCreations: Boolean = false
) {
transactionList(
currentPage: $currentPage
pageSize: $pageSize
order: $order
onlyCreations: $onlyCreations
) {
balanceGDT
count
balance
decayStartBlock
transactions {
id
typeId
amount
balance
balanceDate
memo
linkedUser {
firstName
lastName
}
decay {
decay
start
end
duration
}
}
}
}
`
export const sendResetPasswordEmail = gql`
query ($email: String!) {
sendResetPasswordEmail(email: $email)
}
`
export const listGDTEntriesQuery = gql`
query ($currentPage: Int!, $pageSize: Int!) {
listGDTEntries(currentPage: $currentPage, pageSize: $pageSize) {
count
gdtEntries {
id
amount
date
comment
gdtEntryType
factor
gdt
}
gdtSum
}
}
`
export const communityInfo = gql`
query {
getCommunityInfo {
name
description
registerUrl
url
}
}
`
export const communities = gql`
query {
communities {
id
name
url
description
registerUrl
}
}
`
export const queryTransactionLink = gql`
query ($code: String!) {
queryTransactionLink(code: $code) {
amount
memo
createdAt
validUntil
user {
firstName
publisherId
}
}
}
`

View File

@ -0,0 +1,64 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import createServer from '../server/createServer'
import { createTestClient } from 'apollo-server-testing'
import { name, internet, random } from 'faker'
import { users } from './users/index'
import { createUserFactory } from './factory/user'
import { entities } from '@entity/index'
const context = {
token: '',
setHeaders: {
push: (value: string): void => {
context.token = value
},
// eslint-disable-next-line @typescript-eslint/no-empty-function
forEach: (): void => {},
},
}
export const cleanDB = async () => {
// this only works as lond we do not have foreign key constraints
for (let i = 0; i < entities.length; i++) {
await resetEntity(entities[i])
}
}
const resetEntity = async (entity: any) => {
const items = await entity.find({ withDeleted: true })
if (items.length > 0) {
const ids = items.map((i: any) => i.id)
await entity.delete(ids)
}
}
const run = async () => {
const server = await createServer(context)
const testClient = createTestClient(server.apollo)
const { mutate } = testClient
const { con } = server
await cleanDB()
// seed the standard users
for (let i = 0; i < users.length; i++) {
await createUserFactory(mutate, users[i])
}
// seed 100 random users
for (let i = 0; i < 100; i++) {
await createUserFactory(mutate, {
firstName: name.firstName(),
lastName: name.lastName(),
email: internet.email(),
language: random.boolean() ? 'en' : 'de',
})
}
await con.close()
}
run()

View File

@ -0,0 +1,12 @@
export interface UserInterface {
email?: string
firstName?: string
lastName?: string
// description?: string
createdAt?: Date
emailChecked?: boolean
language?: string
deletedAt?: Date
publisherId?: number
isAdmin?: boolean
}

View File

@ -0,0 +1,11 @@
import { UserInterface } from './UserInterface'
export const bibiBloxberg: UserInterface = {
email: 'bibi@bloxberg.de',
firstName: 'Bibi',
lastName: 'Bloxberg',
// description: 'Hex Hex',
emailChecked: true,
language: 'de',
publisherId: 1234,
}

View File

@ -0,0 +1,10 @@
import { UserInterface } from './UserInterface'
export const bobBaumeister: UserInterface = {
email: 'bob@baumeister.de',
firstName: 'Bob',
lastName: 'der Baumeister',
// description: 'Können wir das schaffen? Ja, wir schaffen das!',
emailChecked: true,
language: 'de',
}

View File

@ -0,0 +1,12 @@
import { UserInterface } from './UserInterface'
export const garrickOllivander: UserInterface = {
email: 'garrick@ollivander.com',
firstName: 'Garrick',
lastName: 'Ollivander',
// description: `Curious ... curious ...
// Renowned wandmaker Mr Ollivander owns the wand shop Ollivanders: Makers of Fine Wands Since 382 BC in Diagon Alley. His shop is widely considered the best place to purchase a wand.`,
createdAt: new Date('2022-01-10T10:23:17'),
emailChecked: false,
language: 'en',
}

View File

@ -0,0 +1,15 @@
import { peterLustig } from './peter-lustig'
import { bibiBloxberg } from './bibi-bloxberg'
import { bobBaumeister } from './bob-baumeister'
import { raeuberHotzenplotz } from './raeuber-hotzenplotz'
import { stephenHawking } from './stephen-hawking'
import { garrickOllivander } from './garrick-ollivander'
export const users = [
peterLustig,
bibiBloxberg,
bobBaumeister,
raeuberHotzenplotz,
stephenHawking,
garrickOllivander,
]

View File

@ -0,0 +1,12 @@
import { UserInterface } from './UserInterface'
export const peterLustig: UserInterface = {
email: 'peter@lustig.de',
firstName: 'Peter',
lastName: 'Lustig',
// description: 'Latzhose und Nickelbrille',
createdAt: new Date('2020-11-25T10:48:43'),
emailChecked: true,
language: 'de',
isAdmin: true,
}

View File

@ -0,0 +1,10 @@
import { UserInterface } from './UserInterface'
export const raeuberHotzenplotz: UserInterface = {
email: 'raeuber@hotzenplotz.de',
firstName: 'Räuber',
lastName: 'Hotzenplotz',
// description: 'Pfefferpistole',
emailChecked: true,
language: 'de',
}

View File

@ -0,0 +1,12 @@
import { UserInterface } from './UserInterface'
export const stephenHawking: UserInterface = {
email: 'stephen@hawking.uk',
firstName: 'Stephen',
lastName: 'Hawking',
// description: 'A Brief History of Time',
emailChecked: true,
createdAt: new Date('1942-01-08T09:17:52'),
deletedAt: new Date('2018-03-14T09:17:52'),
language: 'en',
}

View File

@ -1,25 +0,0 @@
import gql from 'graphql-tag'
export const createUserMutation = gql`
mutation (
$email: String!
$firstName: String!
$lastName: String!
$language: String!
$publisherId: Int
) {
createUser(
email: $email
firstName: $firstName
lastName: $lastName
language: $language
publisherId: $publisherId
)
}
`
export const setPasswordMutation = gql`
mutation ($code: String!, $password: String!) {
setPassword(code: $code, password: $password)
}
`

View File

@ -4,9 +4,6 @@
import { createTestClient } from 'apollo-server-testing'
import createServer from '../src/server/createServer'
import { initialize } from '@dbTools/helpers'
import { createUserMutation, setPasswordMutation } from './graphql'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
import { User } from '@entity/User'
import { entities } from '@entity/index'
export const headerPushMock = jest.fn((t) => {
@ -46,19 +43,6 @@ export const resetEntity = async (entity: any) => {
}
}
export const createUser = async (mutate: any, user: any) => {
// resetToken()
await mutate({ mutation: createUserMutation, variables: user })
const dbUser = await User.findOne({ where: { email: user.email } })
if (!dbUser) throw new Error('Ups, no user found')
const optin = await LoginEmailOptIn.findOne({ where: { userId: dbUser.id } })
if (!optin) throw new Error('Ups, no optin found')
await mutate({
mutation: setPasswordMutation,
variables: { password: 'Aa12345_', code: optin.verificationCode },
})
}
export const resetToken = () => {
context.token = ''
}

View File

@ -4,3 +4,4 @@
// eslint-disable-next-line @typescript-eslint/no-empty-function
console.info = () => {}
jest.setTimeout(1000000)

View File

@ -811,6 +811,11 @@
"@types/qs" "*"
"@types/serve-static" "*"
"@types/faker@^5.5.9":
version "5.5.9"
resolved "https://registry.yarnpkg.com/@types/faker/-/faker-5.5.9.tgz#588ede92186dc557bff8341d294335d50d255f0c"
integrity sha512-uCx6mP3UY5SIO14XlspxsGjgaemrxpssJI0Ol+GfhxtcKpv9pgRZYsS4eeKeHVLje6Qtc8lGszuBI461+gVZBA==
"@types/fs-capacitor@*":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/fs-capacitor/-/fs-capacitor-2.0.0.tgz#17113e25817f584f58100fb7a08eed288b81956e"
@ -2510,6 +2515,11 @@ express@^4.17.1:
utils-merge "1.0.1"
vary "~1.1.2"
faker@^5.5.3:
version "5.5.3"
resolved "https://registry.yarnpkg.com/faker/-/faker-5.5.3.tgz#c57974ee484431b25205c2c8dc09fda861e51e0e"
integrity sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"