From bbcb97abfbb1a80918cc331cb9cbca93fc3ad448 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 7 Jul 2023 17:55:52 +0200 Subject: [PATCH] additional tests for userRoles and userContact --- backend/src/graphql/model/UserAdmin.ts | 37 ++- .../src/graphql/resolver/UserResolver.test.ts | 215 +++++++++++++++++- backend/src/graphql/resolver/UserResolver.ts | 28 +-- backend/src/seeds/factory/user.ts | 4 +- backend/src/seeds/graphql/queries.ts | 17 ++ .../0068-add_user_roles_table/UserRole.ts | 4 +- 6 files changed, 259 insertions(+), 46 deletions(-) diff --git a/backend/src/graphql/model/UserAdmin.ts b/backend/src/graphql/model/UserAdmin.ts index 8e7526a61..59a0d871f 100644 --- a/backend/src/graphql/model/UserAdmin.ts +++ b/backend/src/graphql/model/UserAdmin.ts @@ -53,29 +53,24 @@ export class UserAdmin { @Field(() => [String], { nullable: true }) roles: string[] | null + + @Field(() => Boolean) + isAdmin(): boolean { + if (this.roles) { + return this.roles.includes(ROLE_NAMES.ROLE_NAME_ADMIN) + } + return false + } + + @Field(() => Boolean) + isModerator(): boolean { + if (this.roles) { + return this.roles.includes(ROLE_NAMES.ROLE_NAME_MODERATOR) + } + return false + } } -export function isAdmin(user: UserAdmin): boolean { - if (user.roles) { - for (const role of user.roles) { - if (role === ROLE_NAMES.ROLE_NAME_ADMIN) { - return true - } - } - } - return false -} - -export function isModerator(user: UserAdmin): boolean { - if (user.roles) { - for (const role of user.roles) { - if (role === ROLE_NAMES.ROLE_NAME_MODERATOR) { - return true - } - } - } - return false -} @ObjectType() export class SearchUsersResult { diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 84e2370e4..48fbfe3b7 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -56,6 +56,7 @@ import { searchUsers, user as userQuery, checkUsername, + userContact, } from '@/seeds/graphql/queries' import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' import { bobBaumeister } from '@/seeds/users/bob-baumeister' @@ -699,13 +700,15 @@ describe('UserResolver', () => { firstName: 'Bibi', hasElopage: false, id: expect.any(Number), - roles: expect.any(Array), klickTipp: { newsletterState: false, }, language: 'de', lastName: 'Bloxberg', publisherId: 1234, + roles: [], + isAdmin: false, + isModerator: false, }, }, }), @@ -976,7 +979,9 @@ describe('UserResolver', () => { }, hasElopage: false, publisherId: 1234, - roles: expect.any(Array), + roles: [], + isAdmin: false, + isModerator: false, }, }, }), @@ -992,6 +997,35 @@ describe('UserResolver', () => { }), ) }) + + it('returns usercontact object', async () => { + await expect( + query({ + query: userContact, + variables: { + userId: user[0].id, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + userContact: { + id: expect.any(Number), + type: UserContactType.USER_CONTACT_EMAIL, + userId: user[0].id, + email: 'bibi@bloxberg.de', + emailOptInTypeId: expect.any(Number), + emailResendCount: expect.any(Number), + emailChecked: expect.any(Boolean), + phone: null, + createdAt: expect.any(Date), + updatedAt: expect.any(Date), + deletedAt: null, + }, + }, + }), + ) + }) }) }) }) @@ -1417,6 +1451,7 @@ describe('UserResolver', () => { expect.objectContaining({ firstName: 'Peter', lastName: 'Lustig', + role: ROLE_NAMES.ROLE_NAME_ADMIN, }), ]), }, @@ -1498,13 +1533,15 @@ describe('UserResolver', () => { firstName: 'Bibi', hasElopage: false, id: expect.any(Number), - roles: expect.any(Array), klickTipp: { newsletterState: false, }, language: 'de', lastName: 'Bloxberg', publisherId: 1234, + roles: [], + isAdmin: false, + isModerator: false, }, }, }), @@ -1536,7 +1573,7 @@ describe('UserResolver', () => { }) describe('authenticated', () => { - describe('without admin rights', () => { + describe('with user rights', () => { beforeAll(async () => { user = await userFactory(testEnv, bibiBloxberg) await mutate({ @@ -1564,8 +1601,45 @@ describe('UserResolver', () => { }) }) + describe('with moderator rights', () => { + beforeAll(async () => { + user = await userFactory(testEnv, bibiBloxberg) + admin = await userFactory(testEnv, peterLustig) + + // set Moderator-Role for Peter + const userRole = await UserRole.findOneOrFail({ where: { userId: admin.id } }) + userRole.role = ROLE_NAMES.ROLE_NAME_MODERATOR + userRole.userId = admin.id + await UserRole.save(userRole) + + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + }) + + afterAll(async () => { + await cleanDB() + resetToken() + }) + + it('returns an error', async () => { + await expect( + mutate({ + mutation: setUserRole, + variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_ADMIN }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('401 Unauthorized')], + }), + ) + }) + }) + describe('with admin rights', () => { beforeAll(async () => { + user = await userFactory(testEnv, bibiBloxberg) admin = await userFactory(testEnv, peterLustig) await mutate({ mutation: login, @@ -1578,7 +1652,26 @@ describe('UserResolver', () => { resetToken() }) + it('returns user with new moderator-role', async () => { + const result = await mutate({ + mutation: setUserRole, + variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_MODERATOR }, + }) + expect(result).toEqual( + expect.objectContaining({ + data: { + setUserRole: ROLE_NAMES.ROLE_NAME_MODERATOR, + }, + }), + ) + }) + describe('user to get a new role does not exist', () => { + afterAll(async () => { + await cleanDB() + resetToken() + }) + it('throws an error', async () => { jest.clearAllMocks() await expect( @@ -1601,11 +1694,21 @@ describe('UserResolver', () => { describe('change role with success', () => { beforeAll(async () => { user = await userFactory(testEnv, bibiBloxberg) + admin = await userFactory(testEnv, peterLustig) + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + }) + + afterAll(async () => { + await cleanDB() + resetToken() }) describe('user gets new role', () => { describe('to admin', () => { - it('returns date string', async () => { + it('returns admin-rolename', async () => { const result = await mutate({ mutation: setUserRole, variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_ADMIN }, @@ -1613,7 +1716,41 @@ describe('UserResolver', () => { expect(result).toEqual( expect.objectContaining({ data: { - setUserRole: expect.any(String), + setUserRole: ROLE_NAMES.ROLE_NAME_ADMIN, + }, + }), + ) + }) + + it('stores the ADMIN_USER_ROLE_SET event in the database', async () => { + const userContact = await UserContact.findOneOrFail({ + where: { email: 'bibi@bloxberg.de' }, + relations: ['user'], + }) + const adminContact = await UserContact.findOneOrFail({ + where: { email: 'peter@lustig.de' }, + relations: ['user'], + }) + await expect(DbEvent.find()).resolves.toContainEqual( + expect.objectContaining({ + type: EventType.ADMIN_USER_ROLE_SET, + affectedUserId: userContact.user.id, + actingUserId: adminContact.user.id, + }), + ) + }) + }) + + describe('to moderator', () => { + it('returns date string', async () => { + const result = await mutate({ + mutation: setUserRole, + variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_MODERATOR }, + }) + expect(result).toEqual( + expect.objectContaining({ + data: { + setUserRole: ROLE_NAMES.ROLE_NAME_MODERATOR, }, }), ) @@ -1656,7 +1793,21 @@ describe('UserResolver', () => { }) describe('change role with error', () => { - describe('is own role', () => { + beforeAll(async () => { + user = await userFactory(testEnv, bibiBloxberg) + admin = await userFactory(testEnv, peterLustig) + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + }) + + afterAll(async () => { + await cleanDB() + resetToken() + }) + + describe('his own role', () => { it('throws an error', async () => { jest.clearAllMocks() await expect( @@ -1672,6 +1823,29 @@ describe('UserResolver', () => { }) }) + describe('to not allowed role', () => { + it('throws an error', async () => { + jest.clearAllMocks() + await expect( + mutate({ + mutation: setUserRole, + variables: { userId: user.id, role: 'unknown rolename' }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Not allowed to set user role=')], + }), + ) + }) + + it('logs the error thrown', () => { + expect(logger.error).toBeCalledWith( + 'Not allowed to set user role=', + 'unknown rolename', + ) + }) + }) + describe('user has already role to be set', () => { describe('to admin', () => { it('throws an error', async () => { @@ -1700,6 +1874,33 @@ describe('UserResolver', () => { }) }) + describe('to moderator', () => { + it('throws an error', async () => { + jest.clearAllMocks() + await mutate({ + mutation: setUserRole, + variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_MODERATOR }, + }) + await expect( + mutate({ + mutation: setUserRole, + variables: { userId: user.id, role: ROLE_NAMES.ROLE_NAME_MODERATOR }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('User already has role=')], + }), + ) + }) + + it('logs the error thrown', () => { + expect(logger.error).toBeCalledWith( + 'User already has role=', + ROLE_NAMES.ROLE_NAME_MODERATOR, + ) + }) + }) + describe('to usual user', () => { it('throws an error', async () => { jest.clearAllMocks() diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index efad48330..17872ef70 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -69,6 +69,7 @@ import { findUserByIdentifier } from './util/findUserByIdentifier' import { findUsers } from './util/findUsers' import { getKlicktippState } from './util/getKlicktippState' import { validateAlias } from './util/validateAlias' +import { UserContact } from '../model/UserContact' const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl'] const DEFAULT_LANGUAGE = 'de' @@ -223,6 +224,7 @@ export class UserResolver { // check if user with email still exists? email = email.trim().toLowerCase() if (await checkEmailExists(email)) { + // console.log('email still exists! email', email) const foundUser = await findUserByEmail(email) logger.info('DbUser.findOne', email, foundUser) @@ -355,7 +357,6 @@ export class UserResolver { } else { await EVENT_USER_REGISTER(dbUser) } - console.log('createUser dbUser=', dbUser) return new User(dbUser) } @@ -723,44 +724,33 @@ export class UserResolver { default: throw new LogError('Not allowed to set user role=', role) } - console.log('1') const user = await DbUser.findOne({ where: { id: userId }, relations: ['userRoles'], }) - console.log('2') // user exists ? if (!user) { throw new LogError('Could not find user with given ID', userId) } - console.log('3') // administrator user changes own role? const moderator = getUser(context) - console.log('4') if (moderator.id === userId) { throw new LogError('Administrator can not change his own role') } - console.log('5') // if user role(s) should be deleted by role=null as parameter if (role === null && user.userRoles) { - console.log('6') if (user.userRoles.length > 0) { - console.log('7') // remove all roles of the user await UserRole.delete({ userId: user.id }) - console.log('8') user.userRoles.length = 0 } else if (user.userRoles.length === 0) { - console.log('9') throw new LogError('User is already an usual user') } } else if (isUserInRole(user, role)) { - console.log('10') throw new LogError('User already has role=', role) } // if role shoud be set if (role) { - console.log('11 ', role) if (user.userRoles === undefined) { user.userRoles = [] as UserRole[] } @@ -774,9 +764,7 @@ export class UserResolver { } // await user.save() await EVENT_ADMIN_USER_ROLE_SET(user, moderator) - console.log('12 ') const newUser = await DbUser.findOne({ where: { id: userId }, relations: ['userRoles'] }) - console.log('13 ', newUser) return newUser?.userRoles ? newUser.userRoles[0].role : null } @@ -858,6 +846,18 @@ export class UserResolver { async user(@Arg('identifier') identifier: string): Promise { return new User(await findUserByIdentifier(identifier)) } + + @Authorized([RIGHTS.USER]) + @Query(() => UserContact) + async userContact(@Arg('userId', () => Int) userId: number): Promise { + return new UserContact( + await DbUserContact.findOneOrFail({ + where: { userId }, + withDeleted: true, + relations: ['user'], + }), + ) + } } export async function findUserByEmail(email: string): Promise { diff --git a/backend/src/seeds/factory/user.ts b/backend/src/seeds/factory/user.ts index 47d20f47c..4d1d59f14 100644 --- a/backend/src/seeds/factory/user.ts +++ b/backend/src/seeds/factory/user.ts @@ -19,10 +19,10 @@ export const userFactory = async ( createUser: { id }, }, } = await mutate({ mutation: createUser, variables: user }) - console.log('creatUser:', { id }, { user }) + // console.log('after creatUser:', { id }, { user }) // get user from database let dbUser = await User.findOneOrFail({ where: { id }, relations: ['emailContact', 'userRoles'] }) - console.log('dbUser:', dbUser) + // console.log('dbUser:', dbUser) const emailContact = dbUser.emailContact // console.log('emailContact:', emailContact) diff --git a/backend/src/seeds/graphql/queries.ts b/backend/src/seeds/graphql/queries.ts index 73e20b535..16fc3aba8 100644 --- a/backend/src/seeds/graphql/queries.ts +++ b/backend/src/seeds/graphql/queries.ts @@ -387,3 +387,20 @@ export const user = gql` } } ` +export const userContact = gql` + query ($userId: Int!) { + userContact(userId: $userId) { + id + type + userId + email + emailOptInTypeId + emailResendCount + emailChecked + phone + createdAt + updatedAt + deletedAt + } + } +` diff --git a/database/entity/0068-add_user_roles_table/UserRole.ts b/database/entity/0068-add_user_roles_table/UserRole.ts index 4734de8d9..0de0e27a2 100644 --- a/database/entity/0068-add_user_roles_table/UserRole.ts +++ b/database/entity/0068-add_user_roles_table/UserRole.ts @@ -18,7 +18,7 @@ export class UserRole extends BaseEntity { @Column({ name: 'updated_at', nullable: true, default: null, type: 'datetime' }) updatedAt: Date | null - @ManyToOne(() => User, (user) => user.userRoles) + @ManyToOne(() => User, (user) => user.userRoles, { nullable: true }) @JoinColumn({ name: 'user_id' }) - user: User + user: User | null }