From 6be9024ce5c30c24ea5c687befcad99d27d02cb0 Mon Sep 17 00:00:00 2001 From: Patrick Sachmann <20001160+psachmann@users.noreply.github.com> Date: Tue, 11 Jun 2024 13:34:39 +0200 Subject: [PATCH] EW-893 adding unit tests --- .../services/account-db.service.spec.ts | 41 +++++++++++++++++++ .../domain/services/account-db.service.ts | 4 +- .../services/account-idm.service.spec.ts | 41 ++++++++++++++++++- .../domain/services/account.service.spec.ts | 22 ++++++++++ .../account.repo.integration.spec.ts | 35 ++++++++++++++-- .../account/repo/micro-orm/account.repo.ts | 14 ++++++- src/services/user/hooks/userService.js | 6 +-- .../user/hooks/userService.hooks.test.js | 28 +++++++------ 8 files changed, 166 insertions(+), 25 deletions(-) diff --git a/apps/server/src/modules/account/domain/services/account-db.service.spec.ts b/apps/server/src/modules/account/domain/services/account-db.service.spec.ts index 24a9b6f82e1..e874e687868 100644 --- a/apps/server/src/modules/account/domain/services/account-db.service.spec.ts +++ b/apps/server/src/modules/account/domain/services/account-db.service.spec.ts @@ -1,3 +1,4 @@ +import { faker } from '@faker-js/faker'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { IdentityManagementService } from '@infra/identity-management'; import { ObjectId } from '@mikro-orm/mongodb'; @@ -926,6 +927,7 @@ describe('AccountDbService', () => { }); }); }); + describe('findMany', () => { describe('when find many one time', () => { const setup = () => { @@ -960,4 +962,43 @@ describe('AccountDbService', () => { }); }); }); + + describe('isUniqueEmail', () => { + describe('when email is unique', () => { + const setup = () => { + const email = faker.internet.email(); + + accountRepo.findByUsername.mockResolvedValue(null); + + return { email }; + }; + + it('should return true', async () => { + const { email } = setup(); + + const result = await accountService.isUniqueEmail(email); + + expect(result).toBe(true); + }); + }); + + describe('when email is not unique', () => { + const setup = () => { + const email = faker.internet.email(); + const mockTeacherAccount = accountDoFactory.build(); + + accountRepo.findByUsername.mockResolvedValue(mockTeacherAccount); + + return { email, mockTeacherAccount }; + }; + + it('should return false', async () => { + const { email } = setup(); + + const result = await accountService.isUniqueEmail(email); + + expect(result).toBe(false); + }); + }); + }); }); diff --git a/apps/server/src/modules/account/domain/services/account-db.service.ts b/apps/server/src/modules/account/domain/services/account-db.service.ts index 1dcacfc2db9..24debf60092 100644 --- a/apps/server/src/modules/account/domain/services/account-db.service.ts +++ b/apps/server/src/modules/account/domain/services/account-db.service.ts @@ -149,8 +149,8 @@ export class AccountServiceDb extends AbstractAccountService { } public async isUniqueEmail(email: string): Promise { - const foundUsers = await this.userRepo.findByEmail(email); - const isUnique = foundUsers.length === 0; + const account = await this.accountRepo.findByUsername(email); + const isUnique = !account; return isUnique; } diff --git a/apps/server/src/modules/account/domain/services/account-idm.service.spec.ts b/apps/server/src/modules/account/domain/services/account-idm.service.spec.ts index 734143b331c..492b3269bf5 100644 --- a/apps/server/src/modules/account/domain/services/account-idm.service.spec.ts +++ b/apps/server/src/modules/account/domain/services/account-idm.service.spec.ts @@ -1,3 +1,4 @@ +import { faker } from '@faker-js/faker'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { MongoMemoryDatabaseModule } from '@infra/database'; import { IdentityManagementOauthService, IdentityManagementService } from '@infra/identity-management'; @@ -7,8 +8,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { EntityNotFoundError } from '@shared/common'; import { IdmAccount } from '@shared/domain/interface'; import { Logger } from '@src/core/logger'; -import { AccountConfig } from '../../account-config'; import { Account, AccountSave } from '..'; +import { AccountConfig } from '../../account-config'; import { AccountIdmToDoMapper, AccountIdmToDoMapperDb } from '../../repo/micro-orm/mapper'; import { AccountServiceIdm } from './account-idm.service'; @@ -532,4 +533,42 @@ describe('AccountIdmService', () => { await expect(accountIdmService.findMany(0, 0)).rejects.toThrow(NotImplementedException); }); }); + + describe('isUniqueEmail', () => { + describe('when email is unique', () => { + const setup = () => { + const email = faker.internet.email(); + + idmServiceMock.findAccountsByUsername.mockResolvedValue([[], 0]); + + return { email }; + }; + + it('should return true', async () => { + const { email } = setup(); + + const result = await accountIdmService.isUniqueEmail(email); + + expect(result).toBe(true); + }); + }); + + describe('when email is not unique', () => { + const setup = () => { + const email = faker.internet.email(); + + idmServiceMock.findAccountsByUsername.mockResolvedValue([[mockIdmAccount], 1]); + + return { email }; + }; + + it('should return false', async () => { + const { email } = setup(); + + const result = await accountIdmService.isUniqueEmail(email); + + expect(result).toBe(false); + }); + }); + }); }); diff --git a/apps/server/src/modules/account/domain/services/account.service.spec.ts b/apps/server/src/modules/account/domain/services/account.service.spec.ts index 7a997c6dc9e..cd2cbeea0e7 100644 --- a/apps/server/src/modules/account/domain/services/account.service.spec.ts +++ b/apps/server/src/modules/account/domain/services/account.service.spec.ts @@ -1,3 +1,4 @@ +import { faker } from '@faker-js/faker'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { MikroORM } from '@mikro-orm/core'; import { ObjectId } from '@mikro-orm/mongodb'; @@ -28,6 +29,7 @@ import { IdmCallbackLoggableException } from '../error'; import { AccountServiceDb } from './account-db.service'; import { AccountServiceIdm } from './account-idm.service'; import { AccountService } from './account.service'; +import { AbstractAccountService } from './account.service.abstract'; describe('AccountService', () => { let module: TestingModule; @@ -2081,4 +2083,24 @@ describe('AccountService', () => { }); }); }); + + describe('isUniqueEmail', () => { + describe('when checking if email is unique', () => { + const setup = () => { + const email = faker.internet.email(); + const accountImpl = Reflect.get(accountService, 'accountImpl') as DeepMocked; + const isUniqueEmailSpy = jest.spyOn(accountImpl, 'isUniqueEmail'); + + return { email, isUniqueEmailSpy }; + }; + + it('should call the underlying account service implementation', async () => { + const { email, isUniqueEmailSpy } = setup(); + + await accountService.isUniqueEmail(email); + + expect(isUniqueEmailSpy).toHaveBeenCalledWith(email); + }); + }); + }); }); diff --git a/apps/server/src/modules/account/repo/micro-orm/account.repo.integration.spec.ts b/apps/server/src/modules/account/repo/micro-orm/account.repo.integration.spec.ts index c6fc186c726..4fbdd62fe32 100644 --- a/apps/server/src/modules/account/repo/micro-orm/account.repo.integration.spec.ts +++ b/apps/server/src/modules/account/repo/micro-orm/account.repo.integration.spec.ts @@ -4,11 +4,11 @@ import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; import { User } from '@shared/domain/entity'; import { cleanupCollections, userFactory } from '@shared/testing'; -import { AccountRepo } from './account.repo'; import { AccountEntity } from '../../domain/entity/account.entity'; -import { AccountDoToEntityMapper } from './mapper/account-do-to-entity.mapper'; -import { AccountEntityToDoMapper } from './mapper'; import { accountDoFactory, accountFactory } from '../../testing'; +import { AccountRepo } from './account.repo'; +import { AccountEntityToDoMapper } from './mapper'; +import { AccountDoToEntityMapper } from './mapper/account-do-to-entity.mapper'; describe('account repo', () => { let module: TestingModule; @@ -115,6 +115,35 @@ describe('account repo', () => { }); }); + describe('findByUsername', () => { + describe('When username is given', () => { + const setup = async () => { + const accountToFind = accountFactory.build(); + + await em.persistAndFlush(accountToFind); + em.clear(); + + return accountToFind; + }; + + it('should find user by username', async () => { + const accountToFind = await setup(); + + const account = await repo.findByUsername(accountToFind.username); + + expect(account?.username).toEqual(accountToFind.username); + }); + }); + + describe('When username is not given', () => { + it('should return null', async () => { + const account = await repo.findByUsername(''); + + expect(account).toBeNull(); + }); + }); + }); + describe('findByUsernameAndSystemId', () => { describe('When username and systemId are given', () => { const setup = async () => { diff --git a/apps/server/src/modules/account/repo/micro-orm/account.repo.ts b/apps/server/src/modules/account/repo/micro-orm/account.repo.ts index d6566afa1c0..38c48f558f6 100644 --- a/apps/server/src/modules/account/repo/micro-orm/account.repo.ts +++ b/apps/server/src/modules/account/repo/micro-orm/account.repo.ts @@ -3,10 +3,10 @@ import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; import { SortOrder } from '@shared/domain/interface'; import { Counted, EntityId } from '@shared/domain/types'; -import { AccountEntity } from '../../domain/entity/account.entity'; -import { AccountDoToEntityMapper } from './mapper/account-do-to-entity.mapper'; import { Account } from '../../domain/account'; +import { AccountEntity } from '../../domain/entity/account.entity'; import { AccountEntityToDoMapper } from './mapper'; +import { AccountDoToEntityMapper } from './mapper/account-do-to-entity.mapper'; import { AccountScope } from './scope/account-scope'; @Injectable() @@ -66,6 +66,16 @@ export class AccountRepo { return AccountEntityToDoMapper.mapToDo(entity); } + public async findByUsername(username: string): Promise { + const entity = await this.em.findOne(AccountEntity, { username }); + + if (!entity) { + return null; + } + + return AccountEntityToDoMapper.mapToDo(entity); + } + public async findByUsernameAndSystemId(username: string, systemId: EntityId | ObjectId): Promise { const entity = await this.em.findOne(AccountEntity, { username, systemId: new ObjectId(systemId) }); diff --git a/src/services/user/hooks/userService.js b/src/services/user/hooks/userService.js index 2895120b6ec..7a76abc3f57 100644 --- a/src/services/user/hooks/userService.js +++ b/src/services/user/hooks/userService.js @@ -69,11 +69,9 @@ const checkUniqueEmail = async (hook) => { return Promise.resolve(hook); } - // get userId of user entry to edit - const editUserId = hook.id ? hook.id.toString() : undefined; - const unique = await hook.app.service('nest-account-service').isUniqueEmail(email, editUserId); + const isUnique = await hook.app.service('nest-account-service').isUniqueEmail(email); - if (unique) { + if (isUnique) { return hook; } throw new BadRequest(`Die E-Mail Adresse ist bereits in Verwendung!`); diff --git a/test/services/user/hooks/userService.hooks.test.js b/test/services/user/hooks/userService.hooks.test.js index f779b6a4291..98a49c0594a 100644 --- a/test/services/user/hooks/userService.hooks.test.js +++ b/test/services/user/hooks/userService.hooks.test.js @@ -153,16 +153,18 @@ describe('generateRegistrationLink', () => { const expectedErrorMessage = 'Roles must be exactly of length one if generateRegistrationLink=true is set.'; - const getAppMock = (registrationlinkMock) => ({ - service: (service) => { - if (service === '/registrationlink') { - return { - create: async (data) => registrationlinkMock(data), - }; - } - throw new Error('unknown service'); - }, - }); + const getAppMock = (registrationlinkMock) => { + return { + service: (service) => { + if (service === '/registrationlink') { + return { + create: async (data) => registrationlinkMock(data), + }; + } + throw new Error('unknown service'); + }, + }; + }; it('throws an error if roles is not defined', async () => { const context = { @@ -439,7 +441,6 @@ describe('checkUniqueEmail', () => { const currentTs = Date.now(); const currentEmail = `current.${currentTs}@account.de`; - const updatedEmail = `Current.${currentTs}@Account.DE`; const changedEmail = `Changed.${currentTs}@Account.DE`; const mockUser = { firstName: 'Test', @@ -450,13 +451,14 @@ describe('checkUniqueEmail', () => { it('fails because of duplicate email', async () => { const expectedErrorMessage = `Die E-Mail Adresse ist bereits in Verwendung!`; - await testObjects.createTestUser({ email: currentEmail }); + const user = await testObjects.createTestUser(); + await app.service('nest-account-service').save({ username: user.email, password: 'password', userId: user._id }); const context = { app, data: { ...mockUser, - email: updatedEmail, + email: user.email, }, };