From e13c6718d39fcaa5089670edd19dde7fbc0734c2 Mon Sep 17 00:00:00 2001 From: DPDS93CT Date: Sun, 8 Dec 2024 15:36:28 +0100 Subject: [PATCH 1/6] create OxUserBlacklistEntry, OxUserBlacklistRepo --- .../person/domain/ox-user-blacklist-entry.ts | 27 +++++ .../ox-user-blacklist.repo.spec.ts | 106 ++++++++++++++++++ .../persistence/ox-user-blacklist.repo.ts | 101 +++++++++++++++++ src/shared/types/ox-ids.types.ts | 3 + 4 files changed, 237 insertions(+) create mode 100644 src/modules/person/domain/ox-user-blacklist-entry.ts create mode 100644 src/modules/person/persistence/ox-user-blacklist.repo.spec.ts create mode 100644 src/modules/person/persistence/ox-user-blacklist.repo.ts diff --git a/src/modules/person/domain/ox-user-blacklist-entry.ts b/src/modules/person/domain/ox-user-blacklist-entry.ts new file mode 100644 index 000000000..95c4adcbb --- /dev/null +++ b/src/modules/person/domain/ox-user-blacklist-entry.ts @@ -0,0 +1,27 @@ +import { OXEmail, OXUserName } from '../../../shared/types/ox-ids.types.js'; + +export class OxUserBlacklistEntry { + public constructor( + public id: Persisted, + public readonly createdAt: Persisted, + public readonly updatedAt: Persisted, + public email: OXEmail, + public name: string, + public username: OXUserName, + ) {} + + public static construct( + id: string, + createdAt: Date, + updatedAt: Date, + email: OXEmail, + name: string, + username: OXUserName, + ): OxUserBlacklistEntry { + return new OxUserBlacklistEntry(id, createdAt, updatedAt, email, name, username); + } + + public static createNew(email: OXEmail, name: string, username: OXUserName): OxUserBlacklistEntry { + return new OxUserBlacklistEntry(undefined, undefined, undefined, email, name, username); + } +} diff --git a/src/modules/person/persistence/ox-user-blacklist.repo.spec.ts b/src/modules/person/persistence/ox-user-blacklist.repo.spec.ts new file mode 100644 index 000000000..e7a7d8dfe --- /dev/null +++ b/src/modules/person/persistence/ox-user-blacklist.repo.spec.ts @@ -0,0 +1,106 @@ +import { faker } from '@faker-js/faker'; +import { Test, TestingModule } from '@nestjs/testing'; +import { + ConfigTestModule, + DatabaseTestModule, + DEFAULT_TIMEOUT_FOR_TESTCONTAINERS, +} from '../../../../test/utils/index.js'; +import { EntityManager, MikroORM } from '@mikro-orm/core'; +import { OxUserBlacklistRepo } from './ox-user-blacklist.repo.js'; +import { OxUserBlacklistEntry } from '../domain/ox-user-blacklist-entry.js'; +import { OxUserBlacklistEntity } from './ox-user-blacklist.entity.js'; +import { OXEmail, OXUserName } from '../../../shared/types/ox-ids.types.js'; +import { ClassLogger } from '../../../core/logging/class-logger.js'; +import { createMock } from '@golevelup/ts-jest'; + +describe('OxUserBlacklistRepo', () => { + let module: TestingModule; + let sut: OxUserBlacklistRepo; + let orm: MikroORM; + let em: EntityManager; + + beforeAll(async () => { + module = await Test.createTestingModule({ + imports: [ConfigTestModule, DatabaseTestModule.forRoot({ isDatabaseRequired: true })], + providers: [ + OxUserBlacklistRepo, + { + provide: ClassLogger, + useValue: createMock(), + }, + ], + }).compile(); + sut = module.get(OxUserBlacklistRepo); + orm = module.get(MikroORM); + em = module.get(EntityManager); + + await DatabaseTestModule.setupDatabase(orm); + }, DEFAULT_TIMEOUT_FOR_TESTCONTAINERS); + + async function createEntity(email?: OXEmail, name?: string, username?: OXUserName): Promise { + const oxUserBlacklistEntity: OxUserBlacklistEntity = new OxUserBlacklistEntity(); + oxUserBlacklistEntity.email = email ?? faker.internet.email(); + oxUserBlacklistEntity.name = name ?? faker.person.lastName(); + oxUserBlacklistEntity.username = username ?? faker.internet.userName(); + await em.persistAndFlush(oxUserBlacklistEntity); + } + + afterAll(async () => { + await orm.close(); + await module.close(); + }); + + beforeEach(async () => { + await DatabaseTestModule.clearDatabase(orm); + }); + + it('should be defined', () => { + expect(sut).toBeDefined(); + }); + + describe('findByEmail', () => { + describe('when entity can be found by email', () => { + it('should return OxUserBlacklistEntry', async () => { + const fakeEmail: OXEmail = faker.internet.email(); + await createEntity(fakeEmail); + + const findResult: Option> = await sut.findByEmail(fakeEmail); + + if (!findResult) throw Error(); + expect(findResult.email).toStrictEqual(fakeEmail); + }); + }); + + describe('when entity CANNOT be found by email', () => { + it('should return null', async () => { + const findResult: Option> = await sut.findByEmail(faker.internet.email()); + + expect(findResult).toBeNull(); + }); + }); + }); + + describe('findByUsername', () => { + describe('when entity can be found by username', () => { + it('should return OxUserBlacklistEntry', async () => { + const fakeUsername: OXUserName = faker.internet.userName(); + await createEntity(undefined, undefined, fakeUsername); + + const findResult: Option> = await sut.findByUsername(fakeUsername); + + if (!findResult) throw Error(); + expect(findResult.username).toStrictEqual(fakeUsername); + }); + }); + + describe('when entity CANNOT be found by username', () => { + it('should return null', async () => { + const findResult: Option> = await sut.findByUsername( + faker.internet.userName(), + ); + + expect(findResult).toBeNull(); + }); + }); + }); +}); diff --git a/src/modules/person/persistence/ox-user-blacklist.repo.ts b/src/modules/person/persistence/ox-user-blacklist.repo.ts new file mode 100644 index 000000000..7216fd09b --- /dev/null +++ b/src/modules/person/persistence/ox-user-blacklist.repo.ts @@ -0,0 +1,101 @@ +import { EntityManager, RequiredEntityData } from '@mikro-orm/core'; +import { Injectable } from '@nestjs/common'; +import { ClassLogger } from '../../../core/logging/class-logger.js'; +import { OxUserBlacklistEntry } from '../domain/ox-user-blacklist-entry.js'; +import { OxUserBlacklistEntity } from './ox-user-blacklist.entity.js'; +import { DomainError, EntityNotFoundError } from '../../../shared/error/index.js'; +import { OXEmail, OXUserName } from '../../../shared/types/ox-ids.types.js'; + +export function mapAggregateToData( + oxUserBlacklistEntry: OxUserBlacklistEntry, +): RequiredEntityData { + return { + // Don't assign createdAt and updatedAt, they are auto-generated! + id: oxUserBlacklistEntry.id, + email: oxUserBlacklistEntry.email, + name: oxUserBlacklistEntry.name, + username: oxUserBlacklistEntry.username, + }; +} + +function mapEntityToAggregate(entity: OxUserBlacklistEntity): OxUserBlacklistEntry { + return new OxUserBlacklistEntry( + entity.id, + entity.createdAt, + entity.updatedAt, + entity.email, + entity.name, + entity.username, + ); +} + +@Injectable() +export class OxUserBlacklistRepo { + public constructor( + private readonly em: EntityManager, + private readonly logger: ClassLogger, + ) {} + + public async findByEmail(email: OXEmail): Promise>> { + const oxUserBlacklistEntity: Option = await this.em.findOne(OxUserBlacklistEntity, { + email, + }); + if (oxUserBlacklistEntity) { + return mapEntityToAggregate(oxUserBlacklistEntity); + } + + return null; + } + + public async findByUsername(oxUsername: OXUserName): Promise>> { + const oxUserBlacklistEntity: Option = await this.em.findOne(OxUserBlacklistEntity, { + username: oxUsername, + }); + if (oxUserBlacklistEntity) { + return mapEntityToAggregate(oxUserBlacklistEntity); + } + + return null; + } + + /** + * Creates or updates OxUserBlacklistEntity in database. + * @param oxUserBlacklistEntry + */ + public async save( + oxUserBlacklistEntry: OxUserBlacklistEntry, + ): Promise | DomainError> { + if (oxUserBlacklistEntry.id) { + return this.update(oxUserBlacklistEntry); + } else { + return this.create(oxUserBlacklistEntry); + } + } + + private async create(oxUserBlacklistEntry: OxUserBlacklistEntry): Promise> { + const oxUserBlacklistEntity: OxUserBlacklistEntity = this.em.create( + OxUserBlacklistEntity, + mapAggregateToData(oxUserBlacklistEntry), + ); + await this.em.persistAndFlush(oxUserBlacklistEntity); + + return mapEntityToAggregate(oxUserBlacklistEntity); + } + + private async update( + oxUserBlacklistEntry: OxUserBlacklistEntry, + ): Promise | DomainError> { + const oxUserBlacklistEntity: Option = await this.em.findOne(OxUserBlacklistEntity, { + id: oxUserBlacklistEntry.id, + }); + + if (!oxUserBlacklistEntity) { + this.logger.error( + `Could Not Find OxUserBlacklistEntity, oxUserBlacklistEntryId:${oxUserBlacklistEntry.id}`, + ); + return new EntityNotFoundError('OxUserBlacklistEntity'); + } + + return mapEntityToAggregate(oxUserBlacklistEntity); + } +} diff --git a/src/shared/types/ox-ids.types.ts b/src/shared/types/ox-ids.types.ts index 8b06b7158..947c0bc36 100644 --- a/src/shared/types/ox-ids.types.ts +++ b/src/shared/types/ox-ids.types.ts @@ -1,5 +1,8 @@ import { Flavor } from './flavor.types.js'; +declare const oxEmailSymbol: unique symbol; +export type OXEmail = Flavor; + declare const oxUserIdSymbol: unique symbol; export type OXUserID = Flavor; From 5056551b5551a7ab0a1b371d404f46d853c3b709 Mon Sep 17 00:00:00 2001 From: DPDS93CT Date: Mon, 9 Dec 2024 16:10:50 +0100 Subject: [PATCH 2/6] use OxUserBlacklistRepo in UsernameGeneratorService --- ...name-generator.service.integration-spec.ts | 21 ++++++++++- .../domain/username-generator.service.ts | 37 ++++++++----------- .../ox-user-blacklist.repo.spec.ts | 4 +- .../persistence/ox-user-blacklist.repo.ts | 2 +- src/modules/person/person.module.ts | 4 +- 5 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/modules/person/domain/username-generator.service.integration-spec.ts b/src/modules/person/domain/username-generator.service.integration-spec.ts index 77e00397b..6eeb78608 100644 --- a/src/modules/person/domain/username-generator.service.integration-spec.ts +++ b/src/modules/person/domain/username-generator.service.integration-spec.ts @@ -18,11 +18,14 @@ import { ConfigTestModule, } from '../../../../test/utils/index.js'; import { MikroORM } from '@mikro-orm/core'; +import { OxUserBlacklistRepo } from '../persistence/ox-user-blacklist.repo.js'; +import { ClassLogger } from '../../../core/logging/class-logger.js'; -describe('The UsernameGenerator Service', () => { +describe('UsernameGeneratorService', () => { let module: TestingModule; let service: UsernameGeneratorService; let kcUserService: DeepMocked; + let loggerMock: DeepMocked; let em: EntityManager; let orm: MikroORM; @@ -31,12 +34,21 @@ describe('The UsernameGenerator Service', () => { imports: [ConfigTestModule, DatabaseTestModule.forRoot({ isDatabaseRequired: true })], providers: [ UsernameGeneratorService, - { provide: KeycloakUserService, useValue: createMock() }, + OxUserBlacklistRepo, + { + provide: KeycloakUserService, + useValue: createMock(), + }, + { + provide: ClassLogger, + useValue: createMock(), + }, ], }).compile(); orm = module.get(MikroORM); service = module.get(UsernameGeneratorService); kcUserService = module.get(KeycloakUserService); + loggerMock = module.get(ClassLogger); em = module.get(EntityManager); await DatabaseTestModule.setupDatabase(orm); @@ -127,6 +139,7 @@ describe('The UsernameGenerator Service', () => { .mockResolvedValueOnce({ ok: true, value: createMock>() }) .mockResolvedValueOnce({ ok: false, error: new EntityNotFoundError('Not found') }); const generatedUsername: Result = await service.generateUsername('Max', 'Meyer'); + expect(loggerMock.info).toHaveBeenLastCalledWith(`Next Available Username Is:mmeyer1`); expect(generatedUsername).toEqual({ ok: true, value: 'mmeyer1' }); }); @@ -137,6 +150,7 @@ describe('The UsernameGenerator Service', () => { } else return Promise.resolve({ ok: false, error: new EntityNotFoundError('Not found') }); }); const generatedUsername: Result = await service.generateUsername('Max', 'Meyer'); + expect(loggerMock.info).toHaveBeenLastCalledWith(`Next Available Username Is:mmeyer2`); expect(generatedUsername).toEqual({ ok: true, value: 'mmeyer2' }); }); @@ -148,6 +162,7 @@ describe('The UsernameGenerator Service', () => { .mockResolvedValueOnce({ ok: false, error: new EntityNotFoundError('Not found') }) .mockResolvedValueOnce({ ok: true, value: createMock>() }); const generatedUsername: Result = await service.generateUsername('Renate', 'Bergmann'); + expect(loggerMock.info).toHaveBeenLastCalledWith(`Next Available Username Is:rbergmann3`); expect(generatedUsername).toEqual({ ok: true, value: 'rbergmann3' }); }); @@ -156,6 +171,7 @@ describe('The UsernameGenerator Service', () => { await expect(service.generateUsername('Maximilian', 'Mustermann')).rejects.toStrictEqual( new KeycloakClientError('Could not reach'), ); + expect(loggerMock.info).toHaveBeenCalledTimes(0); }); it('should return error if username can not be generated (cleaned names are of length 0)', async () => { @@ -199,6 +215,7 @@ describe('The UsernameGenerator Service', () => { const generatedUsername: Result = await service.generateUsername('Max', 'Meyer'); // Assert: The generated username should have the counter appended + expect(loggerMock.info).toHaveBeenLastCalledWith(`Next Available Username Is:mmeyer1`); expect(generatedUsername).toEqual({ ok: true, value: 'mmeyer1' }); }); }); diff --git a/src/modules/person/domain/username-generator.service.ts b/src/modules/person/domain/username-generator.service.ts index 0a869038e..dbe0148b0 100644 --- a/src/modules/person/domain/username-generator.service.ts +++ b/src/modules/person/domain/username-generator.service.ts @@ -8,14 +8,16 @@ import { InvalidAttributeLengthError, } from '../../../shared/error/index.js'; import { isDIN91379A, toDIN91379SearchForm } from '../../../shared/util/din-91379-validation.js'; -import { OxUserBlacklistEntity } from '../persistence/ox-user-blacklist.entity.js'; -import { EntityManager } from '@mikro-orm/postgresql'; +import { OxUserBlacklistRepo } from '../persistence/ox-user-blacklist.repo.js'; +import { OxUserBlacklistEntry } from './ox-user-blacklist-entry.js'; +import { ClassLogger } from '../../../core/logging/class-logger.js'; @Injectable() export class UsernameGeneratorService { public constructor( - private readonly em: EntityManager, + private readonly logger: ClassLogger, private kcUserService: KeycloakUserService, + private oxUserBlacklistRepo: OxUserBlacklistRepo, ) {} public async generateUsername(firstname: string, lastname: string): Promise> { @@ -80,22 +82,17 @@ export class UsernameGeneratorService { while (await this.usernameExists(calculatedUsername + counter)) { counter = counter + 1; } - /* eslint-disable no-await-in-loop */ - return calculatedUsername + counter; - } - - public async findByOxUsername(username: string): Promise { - const person: Option = await this.em.findOne(OxUserBlacklistEntity, { - username: username, - }); - if (person) { - return person; - } + this.logger.info(`Next Available Username Is:${calculatedUsername + counter}`); - return null; + return calculatedUsername + counter; } - public async usernameExists(username: string): Promise { + /** + * This method can throw errors e.g. if Keycloak search fails. + * @param username + * @private + */ + private async usernameExists(username: string): Promise { // Check Keycloak const searchResult: Result, DomainError> = await this.kcUserService.findOne({ username }); if (searchResult.ok) { @@ -105,12 +102,8 @@ export class UsernameGeneratorService { } // Check OX Blacklist for the username. If it exists then return true. - const oxUser: OxUserBlacklistEntity | null = await this.findByOxUsername(username); - - if (oxUser) { - return true; // Username exists in the blacklist - } + const oxUser: Option> = await this.oxUserBlacklistRepo.findByOxUsername(username); - return false; // Username is available + return !!oxUser; } } diff --git a/src/modules/person/persistence/ox-user-blacklist.repo.spec.ts b/src/modules/person/persistence/ox-user-blacklist.repo.spec.ts index e7a7d8dfe..6e9cdaeb6 100644 --- a/src/modules/person/persistence/ox-user-blacklist.repo.spec.ts +++ b/src/modules/person/persistence/ox-user-blacklist.repo.spec.ts @@ -86,7 +86,7 @@ describe('OxUserBlacklistRepo', () => { const fakeUsername: OXUserName = faker.internet.userName(); await createEntity(undefined, undefined, fakeUsername); - const findResult: Option> = await sut.findByUsername(fakeUsername); + const findResult: Option> = await sut.findByOxUsername(fakeUsername); if (!findResult) throw Error(); expect(findResult.username).toStrictEqual(fakeUsername); @@ -95,7 +95,7 @@ describe('OxUserBlacklistRepo', () => { describe('when entity CANNOT be found by username', () => { it('should return null', async () => { - const findResult: Option> = await sut.findByUsername( + const findResult: Option> = await sut.findByOxUsername( faker.internet.userName(), ); diff --git a/src/modules/person/persistence/ox-user-blacklist.repo.ts b/src/modules/person/persistence/ox-user-blacklist.repo.ts index 7216fd09b..b58ab40ec 100644 --- a/src/modules/person/persistence/ox-user-blacklist.repo.ts +++ b/src/modules/person/persistence/ox-user-blacklist.repo.ts @@ -47,7 +47,7 @@ export class OxUserBlacklistRepo { return null; } - public async findByUsername(oxUsername: OXUserName): Promise>> { + public async findByOxUsername(oxUsername: OXUserName): Promise>> { const oxUserBlacklistEntity: Option = await this.em.findOne(OxUserBlacklistEntity, { username: oxUsername, }); diff --git a/src/modules/person/person.module.ts b/src/modules/person/person.module.ts index 45afb2505..98365fd68 100644 --- a/src/modules/person/person.module.ts +++ b/src/modules/person/person.module.ts @@ -10,6 +10,7 @@ import { RolleFactory } from '../rolle/domain/rolle.factory.js'; import { ServiceProviderRepo } from '../service-provider/repo/service-provider.repo.js'; import { OrganisationRepository } from '../organisation/persistence/organisation.repository.js'; import { EventModule } from '../../core/eventbus/event.module.js'; +import { OxUserBlacklistRepo } from './persistence/ox-user-blacklist.repo.js'; @Module({ imports: [KeycloakAdministrationModule, LoggerModule.register(PersonModule.name), EventModule], providers: [ @@ -21,7 +22,8 @@ import { EventModule } from '../../core/eventbus/event.module.js'; OrganisationRepository, RolleFactory, ServiceProviderRepo, + OxUserBlacklistRepo, ], - exports: [PersonService, PersonFactory, PersonRepository], + exports: [PersonService, PersonFactory, PersonRepository, OxUserBlacklistRepo], }) export class PersonModule {} From 3527442d1e8ed625c7b921b078b97844c146dab4 Mon Sep 17 00:00:00 2001 From: DPDS93CT Date: Tue, 10 Dec 2024 07:27:56 +0100 Subject: [PATCH 3/6] fix test-cases which use UsernameGenerator to provide OxUserBlacklistRepo --- src/modules/email/persistence/email.repo.spec.ts | 2 ++ .../api/dbiam-personenkontext.controller.integration-spec.ts | 4 ++++ .../dbiam-personenkontext.repo.integration-spec.ts | 2 ++ .../internal-dbiam-personenkontext.repo.integration-spec.ts | 2 ++ .../personenkontext-specifications.integration-spec.ts | 4 ++++ 5 files changed, 14 insertions(+) diff --git a/src/modules/email/persistence/email.repo.spec.ts b/src/modules/email/persistence/email.repo.spec.ts index 98722fa20..3113e04e4 100644 --- a/src/modules/email/persistence/email.repo.spec.ts +++ b/src/modules/email/persistence/email.repo.spec.ts @@ -27,6 +27,7 @@ import { PersonAlreadyHasEnabledEmailAddressError } from '../error/person-alread import { UserLockRepository } from '../../keycloak-administration/repository/user-lock.repository.js'; import { PersonEmailResponse } from '../../person/api/person-email-response.js'; import { generatePassword } from '../../../shared/util/password-generator.js'; +import { OxUserBlacklistRepo } from '../../person/persistence/ox-user-blacklist.repo.js'; describe('EmailRepo', () => { let module: TestingModule; @@ -42,6 +43,7 @@ describe('EmailRepo', () => { imports: [ConfigTestModule, DatabaseTestModule.forRoot({ isDatabaseRequired: true })], providers: [ UsernameGeneratorService, + OxUserBlacklistRepo, EmailRepo, EmailFactory, PersonFactory, diff --git a/src/modules/personenkontext/api/dbiam-personenkontext.controller.integration-spec.ts b/src/modules/personenkontext/api/dbiam-personenkontext.controller.integration-spec.ts index def47061c..7949c00fd 100644 --- a/src/modules/personenkontext/api/dbiam-personenkontext.controller.integration-spec.ts +++ b/src/modules/personenkontext/api/dbiam-personenkontext.controller.integration-spec.ts @@ -13,6 +13,7 @@ import { DatabaseTestModule, DoFactory, KeycloakConfigTestModule, + LoggingTestModule, MapperTestModule, } from '../../../../test/utils/index.js'; import { GlobalValidationPipe } from '../../../shared/validation/index.js'; @@ -36,6 +37,7 @@ import { PersonFactory } from '../../person/domain/person.factory.js'; import { UsernameGeneratorService } from '../../person/domain/username-generator.service.js'; import { PersonenkontextMigrationRuntype } from '../domain/personenkontext.enums.js'; import { generatePassword } from '../../../shared/util/password-generator.js'; +import { OxUserBlacklistRepo } from '../../person/persistence/ox-user-blacklist.repo.js'; describe('dbiam Personenkontext API', () => { let app: INestApplication; @@ -56,6 +58,7 @@ describe('dbiam Personenkontext API', () => { DatabaseTestModule.forRoot({ isDatabaseRequired: true }), PersonenKontextApiModule, KeycloakAdministrationModule, + LoggingTestModule, ], providers: [ { @@ -82,6 +85,7 @@ describe('dbiam Personenkontext API', () => { }, PersonFactory, UsernameGeneratorService, + OxUserBlacklistRepo, ], }) .overrideModule(KeycloakConfigModule) diff --git a/src/modules/personenkontext/persistence/dbiam-personenkontext.repo.integration-spec.ts b/src/modules/personenkontext/persistence/dbiam-personenkontext.repo.integration-spec.ts index 31431ec91..4de57a5eb 100644 --- a/src/modules/personenkontext/persistence/dbiam-personenkontext.repo.integration-spec.ts +++ b/src/modules/personenkontext/persistence/dbiam-personenkontext.repo.integration-spec.ts @@ -41,6 +41,7 @@ import { } from '../../../../test/utils/organisation-test-helper.js'; import { UserLockRepository } from '../../keycloak-administration/repository/user-lock.repository.js'; import { generatePassword } from '../../../shared/util/password-generator.js'; +import { OxUserBlacklistRepo } from '../../person/persistence/ox-user-blacklist.repo.js'; describe('dbiam Personenkontext Repo', () => { let module: TestingModule; @@ -94,6 +95,7 @@ describe('dbiam Personenkontext Repo', () => { PersonFactory, PersonRepository, UsernameGeneratorService, + OxUserBlacklistRepo, RolleFactory, RolleRepo, ServiceProviderRepo, diff --git a/src/modules/personenkontext/persistence/internal-dbiam-personenkontext.repo.integration-spec.ts b/src/modules/personenkontext/persistence/internal-dbiam-personenkontext.repo.integration-spec.ts index bbc54f564..45c51706e 100644 --- a/src/modules/personenkontext/persistence/internal-dbiam-personenkontext.repo.integration-spec.ts +++ b/src/modules/personenkontext/persistence/internal-dbiam-personenkontext.repo.integration-spec.ts @@ -26,6 +26,7 @@ import { ServiceProviderRepo } from '../../service-provider/repo/service-provide import { DBiamPersonenkontextRepoInternal } from './internal-dbiam-personenkontext.repo.js'; import { UserLockRepository } from '../../keycloak-administration/repository/user-lock.repository.js'; import { generatePassword } from '../../../shared/util/password-generator.js'; +import {OxUserBlacklistRepo} from "../../person/persistence/ox-user-blacklist.repo.js"; describe('dbiam Personenkontext Repo', () => { let module: TestingModule; @@ -74,6 +75,7 @@ describe('dbiam Personenkontext Repo', () => { PersonFactory, PersonRepository, UsernameGeneratorService, + OxUserBlacklistRepo, RolleFactory, RolleRepo, ServiceProviderRepo, diff --git a/src/modules/personenkontext/specification/personenkontext-specifications.integration-spec.ts b/src/modules/personenkontext/specification/personenkontext-specifications.integration-spec.ts index 6f354d629..ab7e868ba 100644 --- a/src/modules/personenkontext/specification/personenkontext-specifications.integration-spec.ts +++ b/src/modules/personenkontext/specification/personenkontext-specifications.integration-spec.ts @@ -3,6 +3,7 @@ import { ConfigTestModule, DatabaseTestModule, KeycloakConfigTestModule, + LoggingTestModule, MapperTestModule, } from '../../../../test/utils/index.js'; import { RolleRepo } from '../../rolle/repo/rolle.repo.js'; @@ -27,6 +28,7 @@ import { OrganisationRepository } from '../../organisation/persistence/organisat import { EventService } from '../../../core/eventbus/index.js'; import { EmailRepo } from '../../email/persistence/email.repo.js'; import { Organisation } from '../../organisation/domain/organisation.js'; +import { OxUserBlacklistRepo } from '../../person/persistence/ox-user-blacklist.repo.js'; function createPersonenkontext( this: void, @@ -69,11 +71,13 @@ describe('PersonenkontextSpecifications Integration', () => { DatabaseTestModule.forRoot({ isDatabaseRequired: true }), KeycloakAdministrationModule, MapperTestModule, + LoggingTestModule, ], providers: [ PersonRepository, PersonFactory, UsernameGeneratorService, + OxUserBlacklistRepo, PersonenkontextFactory, { provide: EmailRepo, From b2c71f5b09858cdff4ad6fc5ce04048b54d21c81 Mon Sep 17 00:00:00 2001 From: DPDS93CT Date: Tue, 10 Dec 2024 09:31:15 +0100 Subject: [PATCH 4/6] add test-cases for OxUserBlacklistRepo --- .../ox-user-blacklist.repo.spec.ts | 112 +++++++++++++++++- .../persistence/ox-user-blacklist.repo.ts | 3 + ...m-personenkontext.repo.integration-spec.ts | 2 +- 3 files changed, 115 insertions(+), 2 deletions(-) diff --git a/src/modules/person/persistence/ox-user-blacklist.repo.spec.ts b/src/modules/person/persistence/ox-user-blacklist.repo.spec.ts index 6e9cdaeb6..b30a3508b 100644 --- a/src/modules/person/persistence/ox-user-blacklist.repo.spec.ts +++ b/src/modules/person/persistence/ox-user-blacklist.repo.spec.ts @@ -6,12 +6,14 @@ import { DEFAULT_TIMEOUT_FOR_TESTCONTAINERS, } from '../../../../test/utils/index.js'; import { EntityManager, MikroORM } from '@mikro-orm/core'; -import { OxUserBlacklistRepo } from './ox-user-blacklist.repo.js'; +import { mapAggregateToData, OxUserBlacklistRepo } from './ox-user-blacklist.repo.js'; import { OxUserBlacklistEntry } from '../domain/ox-user-blacklist-entry.js'; import { OxUserBlacklistEntity } from './ox-user-blacklist.entity.js'; import { OXEmail, OXUserName } from '../../../shared/types/ox-ids.types.js'; import { ClassLogger } from '../../../core/logging/class-logger.js'; import { createMock } from '@golevelup/ts-jest'; +import { DomainError } from '../../../shared/error/domain.error.js'; +import { EntityNotFoundError } from '../../../shared/error/entity-not-found.error.js'; describe('OxUserBlacklistRepo', () => { let module: TestingModule; @@ -45,6 +47,25 @@ describe('OxUserBlacklistRepo', () => { await em.persistAndFlush(oxUserBlacklistEntity); } + async function createOxUserBlacklistEntry( + email?: OXEmail, + name?: string, + username?: OXUserName, + ): Promise { + const oxUserBlacklistEntry: OxUserBlacklistEntry = OxUserBlacklistEntry.createNew( + email ?? faker.internet.email(), + name ?? faker.person.lastName(), + username ?? faker.internet.userName(), + ); + const mappedOxUserBlacklistEntity: OxUserBlacklistEntity = em.create( + OxUserBlacklistEntity, + mapAggregateToData(oxUserBlacklistEntry), + ); + await em.persistAndFlush(mappedOxUserBlacklistEntity); + + return mappedOxUserBlacklistEntity; + } + afterAll(async () => { await orm.close(); await module.close(); @@ -103,4 +124,93 @@ describe('OxUserBlacklistRepo', () => { }); }); }); + + describe('save', () => { + beforeEach(() => { + jest.restoreAllMocks(); + }); + describe('when OxUserBlacklistEntry has an id and can be found', () => { + it('should call the update method and return the updated OxUserBlacklistEntry', async () => { + const existingEntity: OxUserBlacklistEntity = await createOxUserBlacklistEntry(); + const updatedEmail: OXEmail = faker.internet.email(); + const updatedName: string = faker.person.lastName(); + const updatedUsername: OXUserName = faker.internet.userName(); + + const updatedEntry: OxUserBlacklistEntry = OxUserBlacklistEntry.construct( + existingEntity.id, + existingEntity.createdAt, + existingEntity.updatedAt, + updatedEmail, + updatedName, + updatedUsername, + ); + + const result: OxUserBlacklistEntry | DomainError = await sut.save(updatedEntry); + if (result instanceof DomainError) throw Error(); + + const foundOxUserBlacklistEntity: Option = await em.findOne( + OxUserBlacklistEntity, + { + id: existingEntity.id, + }, + ); + if (!foundOxUserBlacklistEntity) throw Error(); + + expect(foundOxUserBlacklistEntity.id).toStrictEqual(existingEntity.id); + expect(foundOxUserBlacklistEntity.email).toStrictEqual(updatedEmail); + expect(foundOxUserBlacklistEntity.name).toStrictEqual(updatedName); + expect(foundOxUserBlacklistEntity.username).toStrictEqual(updatedUsername); + }); + }); + + describe('when OxUserBlacklistEntry has an id and CANNOT be found', () => { + it('should call the update method and return EntityNotFoundError', async () => { + const updatedEmail: OXEmail = faker.internet.email(); + const updatedName: string = faker.person.lastName(); + const updatedUsername: OXUserName = faker.internet.userName(); + + const updatedEntry: OxUserBlacklistEntry = OxUserBlacklistEntry.construct( + faker.string.uuid(), + faker.date.past(), + faker.date.recent(), + updatedEmail, + updatedName, + updatedUsername, + ); + + const result: OxUserBlacklistEntry | DomainError = await sut.save(updatedEntry); + if (result instanceof OxUserBlacklistEntry) throw Error(); + + expect(result).toStrictEqual(new EntityNotFoundError('OxUserBlacklistEntity')); + }); + }); + + describe('when OxUserBlacklistEntry does not have an id', () => { + it('should call the create method and return the created OxUserBlacklistEntry', async () => { + const newEmail: OXEmail = faker.internet.email(); + const newName: string = faker.person.lastName(); + const newUsername: OXUserName = faker.internet.userName(); + const newEntry: OxUserBlacklistEntry = OxUserBlacklistEntry.createNew( + newEmail, + newName, + newUsername, + ); + + const result: OxUserBlacklistEntry | DomainError = await sut.save(newEntry); + if (result instanceof DomainError) throw Error(); + + const foundOxUserBlacklistEntity: Option = await em.findOne( + OxUserBlacklistEntity, + { + email: newEmail, + }, + ); + if (!foundOxUserBlacklistEntity) throw Error(); + + expect(foundOxUserBlacklistEntity.email).toStrictEqual(newEmail); + expect(foundOxUserBlacklistEntity.name).toStrictEqual(newName); + expect(foundOxUserBlacklistEntity.username).toStrictEqual(newUsername); + }); + }); + }); }); diff --git a/src/modules/person/persistence/ox-user-blacklist.repo.ts b/src/modules/person/persistence/ox-user-blacklist.repo.ts index b58ab40ec..10df9c2e1 100644 --- a/src/modules/person/persistence/ox-user-blacklist.repo.ts +++ b/src/modules/person/persistence/ox-user-blacklist.repo.ts @@ -96,6 +96,9 @@ export class OxUserBlacklistRepo { return new EntityNotFoundError('OxUserBlacklistEntity'); } + oxUserBlacklistEntity.assign(mapAggregateToData(oxUserBlacklistEntry)); + await this.em.persistAndFlush(oxUserBlacklistEntity); + return mapEntityToAggregate(oxUserBlacklistEntity); } } diff --git a/src/modules/personenkontext/persistence/internal-dbiam-personenkontext.repo.integration-spec.ts b/src/modules/personenkontext/persistence/internal-dbiam-personenkontext.repo.integration-spec.ts index 45c51706e..9edc6cbcc 100644 --- a/src/modules/personenkontext/persistence/internal-dbiam-personenkontext.repo.integration-spec.ts +++ b/src/modules/personenkontext/persistence/internal-dbiam-personenkontext.repo.integration-spec.ts @@ -26,7 +26,7 @@ import { ServiceProviderRepo } from '../../service-provider/repo/service-provide import { DBiamPersonenkontextRepoInternal } from './internal-dbiam-personenkontext.repo.js'; import { UserLockRepository } from '../../keycloak-administration/repository/user-lock.repository.js'; import { generatePassword } from '../../../shared/util/password-generator.js'; -import {OxUserBlacklistRepo} from "../../person/persistence/ox-user-blacklist.repo.js"; +import { OxUserBlacklistRepo } from '../../person/persistence/ox-user-blacklist.repo.js'; describe('dbiam Personenkontext Repo', () => { let module: TestingModule; From 55299a05e01fdd64914edadbee571a7d7478dead Mon Sep 17 00:00:00 2001 From: DPDS93CT Date: Thu, 12 Dec 2024 11:50:05 +0100 Subject: [PATCH 5/6] fix imports in modules and test-files --- src/console/console.module.spec.ts | 5 ++++- src/console/console.module.ts | 4 ++-- .../db-seed.console-mocked-db-seed-repo.integration-spec.ts | 2 ++ src/console/dbseed/db-seed.console.integration-spec.ts | 3 ++- .../dbseed/domain/db-seed.service.integration-spec.ts | 3 ++- src/modules/person/person.module.ts | 2 +- 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/console/console.module.spec.ts b/src/console/console.module.spec.ts index 89818d0a7..c0f0bf291 100644 --- a/src/console/console.module.spec.ts +++ b/src/console/console.module.spec.ts @@ -2,13 +2,16 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ConsoleModule } from './console.module.js'; import { DbConsole } from './db.console.js'; import { DbInitConsole } from './db-init.console.js'; +import { LoggingTestModule } from '../../test/utils/index.js'; +import { PersonModule } from '../modules/person/person.module.js'; +import { KeycloakAdministrationModule } from '../modules/keycloak-administration/keycloak-administration.module.js'; describe('ConsoleModule', () => { let module: TestingModule; beforeAll(async () => { module = await Test.createTestingModule({ - imports: [ConsoleModule], + imports: [PersonModule, KeycloakAdministrationModule, ConsoleModule, LoggingTestModule], }).compile(); }); diff --git a/src/console/console.module.ts b/src/console/console.module.ts index 512ed8491..b27112b58 100644 --- a/src/console/console.module.ts +++ b/src/console/console.module.ts @@ -11,7 +11,7 @@ import { DbConsole } from './db.console.js'; import { DbInitConsole } from './db-init.console.js'; import { LoggerModule } from '../core/logging/logger.module.js'; import { KeycloakAdministrationModule } from '../modules/keycloak-administration/keycloak-administration.module.js'; -import { UsernameGeneratorService } from '../modules/person/domain/username-generator.service.js'; +//import { UsernameGeneratorService } from '../modules/person/domain/username-generator.service.js'; import { KeycloakConfigModule } from '../modules/keycloak-administration/keycloak-config.module.js'; import { OrganisationModule } from '../modules/organisation/organisation.module.js'; import { RolleModule } from '../modules/rolle/rolle.module.js'; @@ -93,7 +93,7 @@ import { KeycloakConsoleModule } from './keycloak/keycloak-console.module.js'; DbInitMigrationConsole, DbCreateMigrationConsole, DbApplyMigrationConsole, - UsernameGeneratorService, + //UsernameGeneratorService, DbSeedDataGeneratorConsole, ], }) diff --git a/src/console/dbseed/db-seed.console-mocked-db-seed-repo.integration-spec.ts b/src/console/dbseed/db-seed.console-mocked-db-seed-repo.integration-spec.ts index 1d31758f2..6a9447b2f 100644 --- a/src/console/dbseed/db-seed.console-mocked-db-seed-repo.integration-spec.ts +++ b/src/console/dbseed/db-seed.console-mocked-db-seed-repo.integration-spec.ts @@ -25,6 +25,7 @@ import { DBiamPersonenkontextService } from '../../modules/personenkontext/domai import { DbSeedReferenceRepo } from './repo/db-seed-reference.repo.js'; import { PersonenKontextModule } from '../../modules/personenkontext/personenkontext.module.js'; import { LdapClient } from '../../core/ldap/domain/ldap-client.js'; +import { OxUserBlacklistRepo } from '../../modules/person/persistence/ox-user-blacklist.repo.js'; describe('DbSeedConsoleMockedDbSeedRepo', () => { let module: TestingModule; @@ -50,6 +51,7 @@ describe('DbSeedConsoleMockedDbSeedRepo', () => { providers: [ UsernameGeneratorService, DBiamPersonenkontextRepo, + OxUserBlacklistRepo, DbSeedConsole, DbSeedService, DBiamPersonenkontextService, diff --git a/src/console/dbseed/db-seed.console.integration-spec.ts b/src/console/dbseed/db-seed.console.integration-spec.ts index b449da2d8..67c12f9b6 100644 --- a/src/console/dbseed/db-seed.console.integration-spec.ts +++ b/src/console/dbseed/db-seed.console.integration-spec.ts @@ -19,6 +19,7 @@ import { RolleModule } from '../../modules/rolle/rolle.module.js'; import { ServiceProviderModule } from '../../modules/service-provider/service-provider.module.js'; import { DbSeedModule } from './db-seed.module.js'; import { PersonenKontextModule } from '../../modules/personenkontext/personenkontext.module.js'; +import { OxUserBlacklistRepo } from '../../modules/person/persistence/ox-user-blacklist.repo.js'; describe('DbSeedConsoleIntegration', () => { let module: TestingModule; @@ -41,7 +42,7 @@ describe('DbSeedConsoleIntegration', () => { ServiceProviderModule, PersonenKontextModule, ], - providers: [UsernameGeneratorService, DBiamPersonenkontextRepo], + providers: [UsernameGeneratorService, DBiamPersonenkontextRepo, OxUserBlacklistRepo], }) .overrideModule(KeycloakConfigModule) .useModule(KeycloakConfigTestModule.forRoot({ isKeycloakRequired: true })) diff --git a/src/console/dbseed/domain/db-seed.service.integration-spec.ts b/src/console/dbseed/domain/db-seed.service.integration-spec.ts index db936bad3..cf1ae8f90 100644 --- a/src/console/dbseed/domain/db-seed.service.integration-spec.ts +++ b/src/console/dbseed/domain/db-seed.service.integration-spec.ts @@ -21,6 +21,7 @@ import { PersonModule } from '../../../modules/person/person.module.js'; import { DbSeedModule } from '../db-seed.module.js'; import { PersonenKontextModule } from '../../../modules/personenkontext/personenkontext.module.js'; import { VornameForPersonWithTrailingSpaceError } from '../../../modules/person/domain/vorname-with-trailing-space.error.js'; +import { OxUserBlacklistRepo } from '../../../modules/person/persistence/ox-user-blacklist.repo.js'; describe('DbSeedServiceIntegration', () => { let module: TestingModule; @@ -42,7 +43,7 @@ describe('DbSeedServiceIntegration', () => { LoggingTestModule, PersonenKontextModule, ], - providers: [UsernameGeneratorService, DBiamPersonenkontextRepo], + providers: [UsernameGeneratorService, DBiamPersonenkontextRepo, OxUserBlacklistRepo], }) .overrideModule(KeycloakConfigModule) .useModule(KeycloakConfigTestModule.forRoot({ isKeycloakRequired: true })) diff --git a/src/modules/person/person.module.ts b/src/modules/person/person.module.ts index 98365fd68..5854b2cca 100644 --- a/src/modules/person/person.module.ts +++ b/src/modules/person/person.module.ts @@ -24,6 +24,6 @@ import { OxUserBlacklistRepo } from './persistence/ox-user-blacklist.repo.js'; ServiceProviderRepo, OxUserBlacklistRepo, ], - exports: [PersonService, PersonFactory, PersonRepository, OxUserBlacklistRepo], + exports: [PersonService, PersonFactory, PersonRepository], }) export class PersonModule {} From ca6dec318d3c6e75591a1ae8241a42149e5af371 Mon Sep 17 00:00:00 2001 From: DPDS93CT Date: Thu, 12 Dec 2024 12:57:16 +0100 Subject: [PATCH 6/6] rm unused import --- src/console/console.module.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/console/console.module.ts b/src/console/console.module.ts index b27112b58..6aba134c7 100644 --- a/src/console/console.module.ts +++ b/src/console/console.module.ts @@ -11,7 +11,6 @@ import { DbConsole } from './db.console.js'; import { DbInitConsole } from './db-init.console.js'; import { LoggerModule } from '../core/logging/logger.module.js'; import { KeycloakAdministrationModule } from '../modules/keycloak-administration/keycloak-administration.module.js'; -//import { UsernameGeneratorService } from '../modules/person/domain/username-generator.service.js'; import { KeycloakConfigModule } from '../modules/keycloak-administration/keycloak-config.module.js'; import { OrganisationModule } from '../modules/organisation/organisation.module.js'; import { RolleModule } from '../modules/rolle/rolle.module.js'; @@ -93,7 +92,6 @@ import { KeycloakConsoleModule } from './keycloak/keycloak-console.module.js'; DbInitMigrationConsole, DbCreateMigrationConsole, DbApplyMigrationConsole, - //UsernameGeneratorService, DbSeedDataGeneratorConsole, ], })