From 0ee0f611c62ad7720854d26fd86bba5ee97e1d21 Mon Sep 17 00:00:00 2001 From: "Marvin Rode (Cap)" <127723478+marode-cap@users.noreply.github.com> Date: Mon, 9 Sep 2024 08:30:43 +0200 Subject: [PATCH] SPSH-1004 Refactor PersonenkontextEvents (#639) * add LdapEventAdapter * fix tests * fix lint * handle PersonenKontextDeletedEvent in Itslearning, Email and LDAP * remove unnecessary imports in tests * create person-delete-service to solve circular dependencies * fix injects for person-delete-service * adjust test cases after removal of DbiamPKHelperRepo * add test case for email-event-handler * do not delete user from ItsLearning when PK is removed * delete LDAP person by personId * merge main into SPSH-844 * Refactor events * Fix tests * Fix LDAP event handler tests * Add tests for specifications * Fix coverage * Merge branch 'main' of https://github.com/dBildungsplattform/dbildungs-iam-server into spsh-1004 * Delete unused file * Revert some changes * PersonenkontextUpdate ignore failed saves/deletes * Add notice to writing repo methods and logger to update aggregate * Fix test * Fix test after merge --------- Co-authored-by: DPDS93CT Co-authored-by: Philipp Kleybolte --- .../ldap/domain/ldap-client.service.spec.ts | 7 +- src/core/ldap/domain/ldap-client.service.ts | 9 +- .../ldap/domain/ldap-event-handler.spec.ts | 41 ++- src/core/ldap/domain/ldap-event-handler.ts | 21 +- .../email/domain/email-event-handler.spec.ts | 84 +------ .../email/domain/email-event-handler.ts | 24 +- ...onenkontext.controller.integration-spec.ts | 238 +++--------------- .../api/dbiam-personenkontext.controller.ts | 73 +++--- ...ersonenkontexte-update-exception-filter.ts | 8 + .../domain/dbiam-personenkontext.factory.ts | 3 + .../personenkontext-commit.error.spec.ts | 14 +- .../error/personenkontext-commit.error.ts | 11 +- .../domain/personenkontexte-update.spec.ts | 39 +++ .../domain/personenkontexte-update.ts | 38 +-- .../persistence/dbiam-personenkontext.repo.ts | 49 ++-- ...gleiche-rolle-an-klasse-wie-schule.spec.ts | 168 +++++++++++++ .../nur-lehr-und-lern-an-klasse.spec.ts | 73 ++++++ src/modules/utility/event-adapter.spec.ts | 222 ---------------- src/modules/utility/event-adapter.ts | 69 ----- src/modules/utility/utility.module.ts | 20 -- src/server/server.module.ts | 2 - .../events/personenkontext-created.event.ts | 12 - .../events/personenkontext-deleted.event.ts | 11 - .../simple-personenkontext-deleted.event.ts | 13 - 24 files changed, 473 insertions(+), 776 deletions(-) create mode 100644 src/modules/personenkontext/specification/gleiche-rolle-an-klasse-wie-schule.spec.ts create mode 100644 src/modules/personenkontext/specification/nur-lehr-und-lern-an-klasse.spec.ts delete mode 100644 src/modules/utility/event-adapter.spec.ts delete mode 100644 src/modules/utility/event-adapter.ts delete mode 100644 src/modules/utility/utility.module.ts delete mode 100644 src/shared/events/personenkontext-created.event.ts delete mode 100644 src/shared/events/personenkontext-deleted.event.ts delete mode 100644 src/shared/events/simple-personenkontext-deleted.event.ts diff --git a/src/core/ldap/domain/ldap-client.service.spec.ts b/src/core/ldap/domain/ldap-client.service.spec.ts index 7675c0b69..a93bcae06 100644 --- a/src/core/ldap/domain/ldap-client.service.spec.ts +++ b/src/core/ldap/domain/ldap-client.service.spec.ts @@ -22,6 +22,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { LdapClient } from './ldap-client.js'; import { Client, Entry, SearchResult } from 'ldapts'; import { KennungRequiredForSchuleError } from '../../../modules/organisation/specification/error/kennung-required-for-schule.error.js'; +import { PersonID } from '../../../shared/types/aggregate-ids.types.js'; describe('LDAP Client Service', () => { let app: INestApplication; @@ -337,7 +338,7 @@ describe('LDAP Client Service', () => { return clientMock; }); - const result: Result = await ldapClientService.deleteLehrerByPersonId(person); + const result: Result = await ldapClientService.deleteLehrerByPersonId(person.id); expect(result.ok).toBeTruthy(); }); @@ -354,7 +355,7 @@ describe('LDAP Client Service', () => { return clientMock; }); - const result: Result = await ldapClientService.deleteLehrerByPersonId(person); + const result: Result = await ldapClientService.deleteLehrerByPersonId(person.id); expect(result.ok).toBeFalsy(); }); @@ -365,7 +366,7 @@ describe('LDAP Client Service', () => { clientMock.add.mockResolvedValueOnce(); return clientMock; }); - const result: Result = await ldapClientService.deleteLehrerByPersonId(person); + const result: Result = await ldapClientService.deleteLehrerByPersonId(person.id); expect(result.ok).toBeFalsy(); }); diff --git a/src/core/ldap/domain/ldap-client.service.ts b/src/core/ldap/domain/ldap-client.service.ts index 8d978f69f..d82088121 100644 --- a/src/core/ldap/domain/ldap-client.service.ts +++ b/src/core/ldap/domain/ldap-client.service.ts @@ -8,6 +8,7 @@ import { LdapInstanceConfig } from '../ldap-instance-config.js'; import { UsernameRequiredError } from '../../../modules/person/domain/username-required.error.js'; import { Mutex } from 'async-mutex'; import { LdapSearchError } from '../error/ldap-search.error.js'; +import { PersonID } from '../../../shared/types/aggregate-ids.types.js'; export type PersonData = { vorname: string; @@ -164,7 +165,7 @@ export class LdapClientService { return `uid=${referrer},cn=lehrer,ou=${orgaKennung},ou=oeffentlicheSchulen,dc=schule-sh,dc=de`; } - public async deleteLehrerByPersonId(person: PersonData): Promise> { + public async deleteLehrerByPersonId(personId: PersonID): Promise> { return this.mutex.runExclusive(async () => { this.logger.info('LDAP: deleteLehrer'); const client: Client = this.ldapClient.getClient(); @@ -173,7 +174,7 @@ export class LdapClientService { const searchResultLehrer: SearchResult = await client.search(`ou=oeffentlicheSchulen,dc=schule-sh,dc=de`, { scope: 'sub', - filter: `(employeeNumber=${person.id})`, + filter: `(employeeNumber=${personId})`, }); if (!searchResultLehrer.searchEntries[0]) { return { @@ -182,9 +183,9 @@ export class LdapClientService { }; } await client.del(searchResultLehrer.searchEntries[0].dn); - this.logger.info(`LDAP: Successfully deleted lehrer by personId:${person.id}`); + this.logger.info(`LDAP: Successfully deleted lehrer by personId:${personId}`); - return { ok: true, value: person }; + return { ok: true, value: personId }; }); } diff --git a/src/core/ldap/domain/ldap-event-handler.spec.ts b/src/core/ldap/domain/ldap-event-handler.spec.ts index 7c8e1a9e4..90934e47f 100644 --- a/src/core/ldap/domain/ldap-event-handler.spec.ts +++ b/src/core/ldap/domain/ldap-event-handler.spec.ts @@ -13,7 +13,7 @@ import { GlobalValidationPipe } from '../../../shared/validation/global-validati import { LdapModule } from '../ldap.module.js'; import { LdapEventHandler } from './ldap-event-handler.js'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { LdapClientService, PersonData } from './ldap-client.service.js'; +import { LdapClientService } from './ldap-client.service.js'; import { SchuleCreatedEvent } from '../../../shared/events/schule-created.event.js'; import { PersonRepository } from '../../../modules/person/persistence/person.repository.js'; import { RolleRepo } from '../../../modules/rolle/repo/rolle.repo.js'; @@ -25,9 +25,11 @@ import { DBiamPersonenkontextRepo } from '../../../modules/personenkontext/persi import { PersonenkontextFactory } from '../../../modules/personenkontext/domain/personenkontext.factory.js'; import { PersonenkontextUpdatedEvent } from '../../../shared/events/personenkontext-updated.event.js'; import { ClassLogger } from '../../logging/class-logger.js'; -import { PersonenkontextDeletedEvent } from '../../../shared/events/personenkontext-deleted.event.js'; -import { KennungRequiredForSchuleError } from '../../../modules/organisation/specification/error/kennung-required-for-schule.error.js'; import { RootDirectChildrenType } from '../../../modules/organisation/domain/organisation.enums.js'; +import { PersonID } from '../../../shared/types/aggregate-ids.types.js'; +import { PersonDeletedEvent } from '../../../shared/events/person-deleted.event.js'; +import { LdapSearchError } from '../error/ldap-search.error.js'; +import { LdapEntityType } from './ldap.types.js'; describe('LDAP Event Handler', () => { let app: INestApplication; @@ -195,36 +197,31 @@ describe('LDAP Event Handler', () => { }); }); - describe('handlePersonenkontextDeletedEvent', () => { - describe('when calling LdapClientService.deleteLehrer is successful', () => { + describe('handlePersonDeletedEvent', () => { + describe('when calling LdapClientService.deleteLehrerByPersonId is successful', () => { it('should NOT log errors', async () => { - const deletionResult: Result = { + const deletionResult: Result = { ok: true, - value: { - vorname: faker.person.firstName(), - familienname: faker.person.lastName(), - id: faker.string.uuid(), - referrer: faker.internet.userName(), - }, + value: faker.string.uuid(), }; - ldapClientServiceMock.deleteLehrer.mockResolvedValueOnce(deletionResult); + ldapClientServiceMock.deleteLehrerByPersonId.mockResolvedValueOnce(deletionResult); - await ldapEventHandler.handlePersonenkontextDeletedEvent(createMock()); + await ldapEventHandler.handlePersonDeletedEvent(createMock()); expect(loggerMock.error).toHaveBeenCalledTimes(0); }); }); - describe('when calling LdapClientService.deleteLehrer is return error', () => { + describe('when calling LdapClientService.deleteLehrerByPersonId is return error', () => { it('should log errors', async () => { - const error: KennungRequiredForSchuleError = new KennungRequiredForSchuleError(); - const deletionResult: Result = { + const error: LdapSearchError = new LdapSearchError(LdapEntityType.LEHRER); + const deletionResult: Result = { ok: false, error: error, }; - ldapClientServiceMock.deleteLehrer.mockResolvedValueOnce(deletionResult); + ldapClientServiceMock.deleteLehrerByPersonId.mockResolvedValueOnce(deletionResult); - await ldapEventHandler.handlePersonenkontextDeletedEvent(createMock()); + await ldapEventHandler.handlePersonDeletedEvent(createMock()); expect(loggerMock.error).toHaveBeenCalledTimes(1); expect(loggerMock.error).toHaveBeenCalledWith(error.message); @@ -296,7 +293,7 @@ describe('LDAP Event Handler', () => { await ldapEventHandler.handlePersonenkontextUpdatedEvent(event); - expect(ldapClientServiceMock.deleteLehrerByPersonId).toHaveBeenCalledTimes(1); + expect(ldapClientServiceMock.deleteLehrer).toHaveBeenCalledTimes(1); }); describe('when ldap client fails', () => { @@ -348,14 +345,14 @@ describe('LDAP Event Handler', () => { ], [], ); - ldapClientServiceMock.deleteLehrerByPersonId.mockResolvedValueOnce({ + ldapClientServiceMock.deleteLehrer.mockResolvedValueOnce({ ok: false, error: new Error('Error'), }); await ldapEventHandler.handlePersonenkontextUpdatedEvent(event); - expect(ldapClientServiceMock.deleteLehrerByPersonId).toHaveBeenCalledTimes(1); + expect(ldapClientServiceMock.deleteLehrer).toHaveBeenCalledTimes(1); }); }); }); diff --git a/src/core/ldap/domain/ldap-event-handler.ts b/src/core/ldap/domain/ldap-event-handler.ts index 90c7b922f..50bc76f17 100644 --- a/src/core/ldap/domain/ldap-event-handler.ts +++ b/src/core/ldap/domain/ldap-event-handler.ts @@ -6,8 +6,9 @@ import { ClassLogger } from '../../logging/class-logger.js'; import { RollenArt } from '../../../modules/rolle/domain/rolle.enums.js'; import { SchuleDeletedEvent } from '../../../shared/events/schule-deleted.event.js'; import { PersonenkontextUpdatedEvent } from '../../../shared/events/personenkontext-updated.event.js'; -import { PersonenkontextDeletedEvent } from '../../../shared/events/personenkontext-deleted.event.js'; import { PersonenkontextEventKontextData } from '../../../shared/events/personenkontext-event.types.js'; +import { PersonDeletedEvent } from '../../../shared/events/person-deleted.event.js'; +import { PersonID } from '../../../shared/types/aggregate-ids.types.js'; @Injectable() export class LdapEventHandler { @@ -51,14 +52,10 @@ export class LdapEventHandler { } } - @EventHandler(PersonenkontextDeletedEvent) - public async handlePersonenkontextDeletedEvent(event: PersonenkontextDeletedEvent): Promise { - this.logger.info( - `Received PersonenkontextDeletedEvent, personId:${event.personData.id}, orgaId:${event.kontextData.orgaId}, rolleId:${event.kontextData.rolleId}`, - ); - const deletionResult: Result = await this.ldapClientService.deleteLehrer(event.personData, { - kennung: event.kontextData.orgaKennung, - }); + @EventHandler(PersonDeletedEvent) + public async handlePersonDeletedEvent(event: PersonDeletedEvent): Promise { + this.logger.info(`Received PersonenkontextDeletedEvent, personId:${event.personId}`); + const deletionResult: Result = await this.ldapClientService.deleteLehrerByPersonId(event.personId); if (!deletionResult.ok) { this.logger.error(deletionResult.error.message); } @@ -76,9 +73,9 @@ export class LdapEventHandler { .filter((pk: PersonenkontextEventKontextData) => pk.rolle === RollenArt.LEHR) .map(async (pk: PersonenkontextEventKontextData) => { this.logger.info(`Call LdapClientService because rollenArt is LEHR, pkId: ${pk.id}`); - const deletionResult: Result = await this.ldapClientService.deleteLehrerByPersonId( - event.person, - ); + const deletionResult: Result = await this.ldapClientService.deleteLehrer(event.person, { + kennung: pk.orgaKennung, + }); if (!deletionResult.ok) { this.logger.error(deletionResult.error.message); } diff --git a/src/modules/email/domain/email-event-handler.spec.ts b/src/modules/email/domain/email-event-handler.spec.ts index 7ca0dc172..b5edd94e3 100644 --- a/src/modules/email/domain/email-event-handler.spec.ts +++ b/src/modules/email/domain/email-event-handler.spec.ts @@ -12,7 +12,6 @@ import { EmailEventHandler } from './email-event-handler.js'; import { faker } from '@faker-js/faker'; import { EmailModule } from '../email.module.js'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { SimplePersonenkontextDeletedEvent } from '../../../shared/events/simple-personenkontext-deleted.event.js'; import { RolleRepo } from '../../rolle/repo/rolle.repo.js'; import { OrganisationRepository } from '../../organisation/persistence/organisation.repository.js'; import { ServiceProviderRepo } from '../../service-provider/repo/service-provider.repo.js'; @@ -30,7 +29,6 @@ import { PersonRenamedEvent } from '../../../shared/events/person-renamed-event. import { RolleUpdatedEvent } from '../../../shared/events/rolle-updated.event.js'; import { RollenArt } from '../../rolle/domain/rolle.enums.js'; import { DBiamPersonenkontextRepo } from '../../personenkontext/persistence/dbiam-personenkontext.repo.js'; -import { PersonenkontextCreatedEvent } from '../../../shared/events/personenkontext-created.event.js'; import { EmailAddress } from './email-address.js'; import { PersonID, RolleID } from '../../../shared/types/index.js'; import { Personenkontext } from '../../personenkontext/domain/personenkontext.js'; @@ -71,10 +69,6 @@ describe('Email Event Handler', () => { DatabaseTestModule.forRoot({ isDatabaseRequired: false }), ], providers: [ - { - provide: ClassLogger, - useValue: createMock(), - }, { provide: APP_PIPE, useClass: GlobalValidationPipe, @@ -97,12 +91,12 @@ describe('Email Event Handler', () => { .useValue(createMock()) .overrideProvider(DBiamPersonenkontextRepo) .useValue(createMock()) - .overrideProvider(ClassLogger) - .useValue(createMock()) .overrideProvider(EmailEventHandler) .useClass(EmailEventHandler) .overrideProvider(EventService) .useClass(EventService) + .overrideProvider(ClassLogger) + .useValue(createMock()) .compile(); emailEventHandler = module.get(EmailEventHandler); @@ -138,11 +132,11 @@ describe('Email Event Handler', () => { }); } - describe('handlePersonenkontextCreatedEvent', () => { + describe('handlePersonenkontextUpdatedEvent', () => { let fakePersonId: PersonID; let fakeRolleId: RolleID; let fakeEmailAddressString: string; - let event: PersonenkontextCreatedEvent; + let event: PersonenkontextUpdatedEvent; let personenkontexte: Personenkontext[]; let rolle: Rolle; let rolleMap: Map>; @@ -154,7 +148,7 @@ describe('Email Event Handler', () => { fakePersonId = faker.string.uuid(); fakeRolleId = faker.string.uuid(); fakeEmailAddressString = faker.internet.email(); - event = new PersonenkontextCreatedEvent(fakePersonId, faker.string.uuid(), faker.string.uuid()); + event = createMock({ person: { id: fakePersonId } }); personenkontexte = [createMock>()]; rolle = createMock>({ serviceProviderIds: [] }); @@ -185,7 +179,7 @@ describe('Email Event Handler', () => { ); }); - await emailEventHandler.handlePersonenkontextCreatedEvent(event); + await emailEventHandler.handlePersonenkontextUpdatedEvent(event); expect(loggerMock.info).toHaveBeenCalledWith( `Existing email for personId:${fakePersonId} already enabled`, @@ -214,7 +208,7 @@ describe('Email Event Handler', () => { const persistedEmail: EmailAddress = getEmail(); emailRepoMock.save.mockResolvedValueOnce(persistedEmail); - await emailEventHandler.handlePersonenkontextCreatedEvent(event); + await emailEventHandler.handlePersonenkontextUpdatedEvent(event); expect(loggerMock.info).toHaveBeenCalledWith( `Enabled and saved address:${persistedEmail.currentAddress}`, @@ -242,7 +236,7 @@ describe('Email Event Handler', () => { emailRepoMock.save.mockResolvedValueOnce(new EmailAddressNotFoundError(fakeEmailAddressString)); - await emailEventHandler.handlePersonenkontextCreatedEvent(event); + await emailEventHandler.handlePersonenkontextUpdatedEvent(event); expect(loggerMock.error).toHaveBeenCalledWith( `Could not enable email, error is requested EmailAddress with the address:${fakeEmailAddressString} was not found`, @@ -277,7 +271,7 @@ describe('Email Event Handler', () => { }; }); - await emailEventHandler.handlePersonenkontextCreatedEvent(event); + await emailEventHandler.handlePersonenkontextUpdatedEvent(event); expect(loggerMock.info).toHaveBeenCalledWith( `Successfully persisted email with new address:${persistenceResult.currentAddress}`, @@ -301,7 +295,7 @@ describe('Email Event Handler', () => { }; }); - await emailEventHandler.handlePersonenkontextCreatedEvent(event); + await emailEventHandler.handlePersonenkontextUpdatedEvent(event); expect(loggerMock.error).toHaveBeenCalledWith( `Could not create email, error is requested Person with the following ID ${fakePersonId} was not found`, @@ -333,7 +327,7 @@ describe('Email Event Handler', () => { }; }); - await emailEventHandler.handlePersonenkontextCreatedEvent(event); + await emailEventHandler.handlePersonenkontextUpdatedEvent(event); expect(loggerMock.error).toHaveBeenCalledWith( `Could not persist email, error is requested EmailAddress with the address:${fakeEmailAddressString} was not found`, @@ -439,62 +433,6 @@ describe('Email Event Handler', () => { }); }); - describe('handlePersonenkontextDeletedEvent', () => { - describe('when rolle exists and service provider with kategorie email is found', () => { - it('should execute without errors', async () => { - const event: SimplePersonenkontextDeletedEvent = new SimplePersonenkontextDeletedEvent( - faker.string.uuid(), - faker.string.uuid(), - faker.string.uuid(), - faker.string.uuid(), - ); - - const rolle: Rolle = createMock>({ serviceProviderIds: [] }); - const sp: ServiceProvider = createMock>({ - kategorie: ServiceProviderKategorie.EMAIL, - }); - const spMap: Map> = new Map>(); - spMap.set(sp.id, sp); - rolleRepoMock.findById.mockResolvedValueOnce(rolle); - serviceProviderRepoMock.findByIds.mockResolvedValueOnce(spMap); - - const result: void = await emailEventHandler.handlePersonenkontextDeletedEvent(event); - - expect(result).toBeUndefined(); - }); - }); - - describe('when rolle does NOT exists', () => { - it('should execute without errors', async () => { - const event: SimplePersonenkontextDeletedEvent = new SimplePersonenkontextDeletedEvent( - faker.string.uuid(), - faker.string.uuid(), - faker.string.uuid(), - faker.string.uuid(), - ); - - rolleRepoMock.findById.mockResolvedValueOnce(undefined); - const result: void = await emailEventHandler.handlePersonenkontextDeletedEvent(event); - - expect(result).toBeUndefined(); - }); - }); - }); - - describe('handlePersonenkontextUpdatedEvent', () => { - describe('when called', () => { - it('should log info', async () => { - const event: PersonenkontextUpdatedEvent = createMock(); - - await emailEventHandler.handlePersonenkontextUpdatedEvent(event); - - expect(loggerMock.info).toHaveBeenCalledWith( - `Received handlePersonenkontextUpdatedEvent, personId:${event.person.id}`, - ); - }); - }); - }); - describe('handlePersonDeletedEvent', () => { let personId: string; let emailAddress: string; diff --git a/src/modules/email/domain/email-event-handler.ts b/src/modules/email/domain/email-event-handler.ts index 3b5ca2307..45b21b7c6 100644 --- a/src/modules/email/domain/email-event-handler.ts +++ b/src/modules/email/domain/email-event-handler.ts @@ -1,8 +1,6 @@ import { Injectable } from '@nestjs/common'; import { ClassLogger } from '../../../core/logging/class-logger.js'; import { EventHandler } from '../../../core/eventbus/decorators/event-handler.decorator.js'; -import { SimplePersonenkontextDeletedEvent } from '../../../shared/events/simple-personenkontext-deleted.event.js'; -import { PersonenkontextCreatedEvent } from '../../../shared/events/personenkontext-created.event.js'; import { RolleRepo } from '../../rolle/repo/rolle.repo.js'; import { Rolle } from '../../rolle/domain/rolle.js'; import { ServiceProviderRepo } from '../../service-provider/repo/service-provider.repo.js'; @@ -36,14 +34,6 @@ export class EmailEventHandler { private readonly eventService: EventService, ) {} - @EventHandler(PersonenkontextCreatedEvent) - public async handlePersonenkontextCreatedEvent(event: PersonenkontextCreatedEvent): Promise { - this.logger.info( - `Received PersonenkontextCreatedEvent, personId:${event.personId}, orgaId:${event.organisationId}, rolleId:${event.rolleId}`, - ); - await this.handlePerson(event.personId); - } - @EventHandler(PersonRenamedEvent) // eslint-disable-next-line @typescript-eslint/require-await public async handlePersonRenamedEvent(event: PersonRenamedEvent): Promise { @@ -85,20 +75,12 @@ export class EmailEventHandler { }); } - @EventHandler(SimplePersonenkontextDeletedEvent) - // eslint-disable-next-line @typescript-eslint/require-await - public async handlePersonenkontextDeletedEvent(event: SimplePersonenkontextDeletedEvent): Promise { - this.logger.info( - `Received PersonenkontextDeletedEvent, personId:${event.personId}, orgaId:${event.organisationId}, rolleId:${event.rolleId}`, - ); - // currently receiving of this event is not causing a deletion of email and the related addresses for the affected user, this is intentional - } - @EventHandler(PersonenkontextUpdatedEvent) - // eslint-disable-next-line @typescript-eslint/require-await + // currently receiving of this event is not causing a deletion of email and the related addresses for the affected user, this is intentional public async handlePersonenkontextUpdatedEvent(event: PersonenkontextUpdatedEvent): Promise { this.logger.info(`Received handlePersonenkontextUpdatedEvent, personId:${event.person.id}`); - // // TODO implement handlePersonenkontextUpdatedEvent + + await this.handlePerson(event.person.id); } // this method cannot make use of handlePerson(personId) method, because personId is already null when event is received 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 7cdca6b7c..8e7f87495 100644 --- a/src/modules/personenkontext/api/dbiam-personenkontext.controller.integration-spec.ts +++ b/src/modules/personenkontext/api/dbiam-personenkontext.controller.integration-spec.ts @@ -22,10 +22,9 @@ import { PassportUser } from '../../authentication/types/user.js'; import { OrganisationsTyp } from '../../organisation/domain/organisation.enums.js'; import { Rolle } from '../../rolle/domain/rolle.js'; import { RolleRepo } from '../../rolle/repo/rolle.repo.js'; -import { PersonenkontextFactory } from '../domain/personenkontext.factory.js'; import { Personenkontext } from '../domain/personenkontext.js'; import { DBiamPersonenkontextRepo } from '../persistence/dbiam-personenkontext.repo.js'; -import { RollenArt, RollenMerkmal } from '../../rolle/domain/rolle.enums.js'; +import { RollenArt } from '../../rolle/domain/rolle.enums.js'; import { PersonenKontextApiModule } from '../personenkontext-api.module.js'; import { KeycloakConfigModule } from '../../keycloak-administration/keycloak-config.module.js'; import { KeycloakAdministrationModule } from '../../keycloak-administration/keycloak-administration.module.js'; @@ -36,6 +35,9 @@ import { DomainError } from '../../../shared/error/domain.error.js'; import { Organisation } from '../../organisation/domain/organisation.js'; import { PersonFactory } from '../../person/domain/person.factory.js'; import { UsernameGeneratorService } from '../../person/domain/username-generator.service.js'; +import { DbiamPersonenkontextFactory } from '../domain/dbiam-personenkontext.factory.js'; +import { PersonenkontexteUpdate } from '../domain/personenkontexte-update.js'; +import { PersonenkontextCommitError } from '../domain/error/personenkontext-commit.error.js'; describe('dbiam Personenkontext API', () => { let app: INestApplication; @@ -45,7 +47,7 @@ describe('dbiam Personenkontext API', () => { let personRepo: PersonRepository; let organisationRepo: OrganisationRepository; let rolleRepo: RolleRepo; - let personenkontextFactory: PersonenkontextFactory; + let personenkontextFactory: DbiamPersonenkontextFactory; let personpermissionsRepoMock: DeepMocked; let personFactory: PersonFactory; @@ -94,7 +96,7 @@ describe('dbiam Personenkontext API', () => { personRepo = module.get(PersonRepository); organisationRepo = module.get(OrganisationRepository); rolleRepo = module.get(RolleRepo); - personenkontextFactory = module.get(PersonenkontextFactory); + personenkontextFactory = module.get(DbiamPersonenkontextFactory); personpermissionsRepoMock = module.get(PersonPermissionsRepo); personFactory = module.get(PersonFactory); @@ -240,47 +242,37 @@ describe('dbiam Personenkontext API', () => { expect(response.status).toBe(201); }); - it('should return created personenkontext when Klasse specifications are met', async () => { - //create lehrer on Schule - const lehrer: Person = await createPerson(); - if (lehrer instanceof DomainError) { - throw lehrer; - } - const schuleDo: Organisation = DoFactory.createOrganisation(false, { - typ: OrganisationsTyp.SCHULE, - }); - const schule: Organisation = await organisationRepo.save(schuleDo); - const schuelerRolleDummy: Rolle = DoFactory.createRolle(false, { - rollenart: RollenArt.LERN, - administeredBySchulstrukturknoten: schule.id, - merkmale: [], - }); - const schuelerRolle: Rolle = await rolleRepo.save(schuelerRolleDummy); - await personenkontextRepo.save(personenkontextFactory.createNew(lehrer.id, schule.id, schuelerRolle.id)); - - const klasseDo: Organisation = DoFactory.createOrganisation(false, { - typ: OrganisationsTyp.KLASSE, - administriertVon: schule.id, - }); - const klasse: Organisation = await organisationRepo.save(klasseDo); + it('should return error if PersonenkontexteUpdateError occurs', async () => { + const person: Person = await createPerson(); + const organisation: Organisation = await organisationRepo.save( + DoFactory.createOrganisation(false, { typ: OrganisationsTyp.SCHULE }), + ); + const rolle: Rolle = await rolleRepo.save( + DoFactory.createRolle(false, { + administeredBySchulstrukturknoten: organisation.id, + rollenart: RollenArt.LEHR, + }), + ); - const personpermissions: DeepMocked = createMock(); - personpermissionsRepoMock.loadPersonPermissions.mockResolvedValue(personpermissions); - personpermissions.getOrgIdsWithSystemrecht.mockResolvedValueOnce([schule.id, klasse.id]); - personpermissions.hasSystemrechteAtRootOrganisation.mockResolvedValueOnce(true); + const personenkontextUpdateMock: DeepMocked = createMock(); + personenkontextUpdateMock.update.mockResolvedValueOnce(new PersonenkontextCommitError()); + jest.spyOn(personenkontextFactory, 'createNewPersonenkontexteUpdate').mockReturnValueOnce( + personenkontextUpdateMock, + ); const response: Response = await request(app.getHttpServer() as App) .post('/dbiam/personenkontext') .send({ - personId: lehrer.id, - organisationId: klasse.id, - rolleId: schuelerRolle.id, + personId: person.id, + organisationId: organisation.id, + rolleId: rolle.id, }); - expect(response.status).toBe(201); + expect(response.status).toBe(400); + expect(response.text).toBe('{"code":400,"i18nKey":"PERSONENKONTEXTE_UPDATE_ERROR"}'); }); - it('should return error if personenkontext already exists', async () => { + it('should return error, if personenkontext was not added', async () => { const person: Person = await createPerson(); const organisation: Organisation = await organisationRepo.save( DoFactory.createOrganisation(false, { typ: OrganisationsTyp.SCHULE }), @@ -291,178 +283,24 @@ describe('dbiam Personenkontext API', () => { rollenart: RollenArt.LEHR, }), ); - const personenkontext: Personenkontext = await personenkontextRepo.save( - personenkontextFactory.createNew(person.id, organisation.id, rolle.id), + + // Error can only occur when database write fails, therefore it needs to be mocked + const personenkontextUpdateMock: DeepMocked = createMock(); + personenkontextUpdateMock.update.mockResolvedValueOnce([]); + jest.spyOn(personenkontextFactory, 'createNewPersonenkontexteUpdate').mockReturnValueOnce( + personenkontextUpdateMock, ); - const permissions: DeepMocked = createMock(); - personpermissionsRepoMock.loadPersonPermissions.mockResolvedValueOnce(permissions); - permissions.hasSystemrechteAtOrganisation.mockResolvedValueOnce(true); - permissions.canModifyPerson.mockResolvedValueOnce(true); const response: Response = await request(app.getHttpServer() as App) .post('/dbiam/personenkontext') .send({ - personId: personenkontext.personId, - organisationId: personenkontext.organisationId, - rolleId: personenkontext.rolleId, + personId: person.id, + organisationId: organisation.id, + rolleId: rolle.id, }); expect(response.status).toBe(400); - }); - - it('should return error if references do not exist', async () => { - const personenkontext: Personenkontext = DoFactory.createPersonenkontext(false); - - const response: Response = await request(app.getHttpServer() as App) - .post('/dbiam/personenkontext') - .send({ - personId: personenkontext.personId, - organisationId: personenkontext.organisationId, - rolleId: personenkontext.rolleId, - }); - - expect(response.status).toBe(400); // TODO: Fix - }); - - describe('should return error if specifications are not satisfied', () => { - it('when organisation is not found', async () => { - const person: Person = await createPerson(); - const rolle: Rolle = await rolleRepo.save(DoFactory.createRolle(false)); - const response: Response = await request(app.getHttpServer() as App) - .post('/dbiam/personenkontext') - .send({ - personId: person.id, - organisationId: faker.string.uuid(), - rolleId: rolle.id, - }); - - expect(response.status).toBe(400); - }); - - it('when rolle is not found', async () => { - const person: Person = await createPerson(); - const organisation: Organisation = await organisationRepo.save( - DoFactory.createOrganisation(false), - ); - const response: Response = await request(app.getHttpServer() as App) - .post('/dbiam/personenkontext') - .send({ - personId: person.id, - organisationId: organisation.id, - rolleId: faker.string.uuid(), - }); - - expect(response.status).toBe(404); - }); - - it('when rollenart of rolle is not LEHR or LERN', async () => { - const orgaDo: Organisation = DoFactory.createOrganisation(false, { - typ: OrganisationsTyp.KLASSE, - }); - const rolleDummy: Rolle = DoFactory.createRolle(false, { rollenart: RollenArt.SYSADMIN }); - - const person: Person = await createPerson(); - const organisation: Organisation = await organisationRepo.save(orgaDo); - const rolle: Rolle = await rolleRepo.save(rolleDummy); - const response: Response = await request(app.getHttpServer() as App) - .post('/dbiam/personenkontext') - .send({ - personId: person.id, - organisationId: organisation.id, - rolleId: rolle.id, - }); - - expect(response.status).toBe(400); - }); - - it('when rollenart for Schule and Klasse are not equal', async () => { - //create admin on Schule - const admin: Person = await createPerson(); - const schuleDo: Organisation = DoFactory.createOrganisation(false, { - typ: OrganisationsTyp.SCHULE, - }); - const adminRolleDummy: Rolle = DoFactory.createRolle(false, { rollenart: RollenArt.ORGADMIN }); - - const schule: Organisation = await organisationRepo.save(schuleDo); - const adminRolle: Rolle = await rolleRepo.save(adminRolleDummy); - await personenkontextRepo.save(personenkontextFactory.createNew(admin.id, schule.id, adminRolle.id)); - - const klasseDo: Organisation = DoFactory.createOrganisation(false, { - typ: OrganisationsTyp.KLASSE, - administriertVon: schule.id, - }); - const lehrRolleDummy: Rolle = DoFactory.createRolle(false, { rollenart: RollenArt.LEHR }); - const lehrer: Person = admin; - const klasse: Organisation = await organisationRepo.save(klasseDo); - const lehrRolle: Rolle = await rolleRepo.save(lehrRolleDummy); - const response: Response = await request(app.getHttpServer() as App) - .post('/dbiam/personenkontext') - .send({ - personId: lehrer.id, - organisationId: klasse.id, - rolleId: lehrRolle.id, - }); - - expect(response.status).toBe(400); - }); - }); - - describe('when user is not authorized', () => { - it('should return error', async () => { - const person: Person = await createPerson(); - const organisation: Organisation = await organisationRepo.save( - DoFactory.createOrganisation(false, { typ: OrganisationsTyp.SCHULE }), - ); - const rolle: Rolle = await rolleRepo.save( - DoFactory.createRolle(false, { - administeredBySchulstrukturknoten: organisation.id, - rollenart: RollenArt.LEHR, - merkmale: [], - }), - ); - - const personpermissions: DeepMocked = createMock(); - personpermissionsRepoMock.loadPersonPermissions.mockResolvedValue(personpermissions); - personpermissions.hasSystemrechteAtOrganisation.mockResolvedValueOnce(false); - - const response: Response = await request(app.getHttpServer() as App) - .post('/dbiam/personenkontext') - .send({ personId: person.id, organisationId: organisation.id, rolleId: rolle.id }); - - expect(response.status).toBe(404); - expect(response.body).toEqual({ - code: 404, - subcode: '01', - titel: 'Angefragte Entität existiert nicht', - beschreibung: 'Die angeforderte Entität existiert nicht', - }); - }); - }); - - describe('when OrganisationMatchesRollenart is not satisfied', () => { - it('should return error and map to 400', async () => { - const person: Person = await createPerson(); - const organisation: Organisation = await organisationRepo.save( - DoFactory.createOrganisation(false, { typ: OrganisationsTyp.SCHULE }), - ); - const rolle: Rolle = await rolleRepo.save( - DoFactory.createRolle(false, { - administeredBySchulstrukturknoten: organisation.id, - rollenart: RollenArt.SYSADMIN, - merkmale: [RollenMerkmal.KOPERS_PFLICHT], - }), - ); - - const personpermissions: DeepMocked = createMock(); - personpermissionsRepoMock.loadPersonPermissions.mockResolvedValue(personpermissions); - personpermissions.hasSystemrechteAtOrganisation.mockResolvedValueOnce(false); - - const response: Response = await request(app.getHttpServer() as App) - .post('/dbiam/personenkontext') - .send({ personId: person.id, organisationId: organisation.id, rolleId: rolle.id }); - - expect(response.status).toBe(400); - }); + expect(response.text).toBe('{"code":400,"i18nKey":"PERSONENKONTEXTE_UPDATE_ERROR"}'); }); }); }); diff --git a/src/modules/personenkontext/api/dbiam-personenkontext.controller.ts b/src/modules/personenkontext/api/dbiam-personenkontext.controller.ts index 9f2a78026..1983c3b41 100644 --- a/src/modules/personenkontext/api/dbiam-personenkontext.controller.ts +++ b/src/modules/personenkontext/api/dbiam-personenkontext.controller.ts @@ -15,19 +15,19 @@ import { SchulConnexErrorMapper } from '../../../shared/error/schul-connex-error import { SchulConnexValidationErrorFilter } from '../../../shared/error/schulconnex-validation-error.filter.js'; import { Permissions } from '../../authentication/api/permissions.decorator.js'; import { PersonPermissions } from '../../authentication/domain/person-permissions.js'; -import { DBiamPersonenkontextService } from '../domain/dbiam-personenkontext.service.js'; -import { PersonenkontextFactory } from '../domain/personenkontext.factory.js'; import { Personenkontext } from '../domain/personenkontext.js'; import { DBiamPersonenkontextResponse } from './response/dbiam-personenkontext.response.js'; import { DBiamPersonenkontextRepo } from '../persistence/dbiam-personenkontext.repo.js'; -import { PersonenkontextSpecificationError } from '../specification/error/personenkontext-specification.error.js'; import { DbiamPersonenkontextBodyParams } from './param/dbiam-personenkontext.body.params.js'; import { DBiamFindPersonenkontexteByPersonIdParams } from './param/dbiam-find-personenkontext-by-personid.params.js'; import { DbiamPersonenkontextError } from './dbiam-personenkontext.error.js'; import { PersonenkontextExceptionFilter } from './personenkontext-exception-filter.js'; import { PersonenkontexteUpdateExceptionFilter } from './personenkontexte-update-exception-filter.js'; -import { OrganisationMatchesRollenartError } from '../specification/error/organisation-matches-rollenart.error.js'; import { AuthenticationExceptionFilter } from '../../authentication/api/authentication-exception-filter.js'; +import { PersonenkontexteUpdate } from '../domain/personenkontexte-update.js'; +import { DbiamPersonenkontextFactory } from '../domain/dbiam-personenkontext.factory.js'; +import { PersonenkontexteUpdateError } from '../domain/error/personenkontexte-update.error.js'; +import { PersonenkontextCommitError } from '../domain/error/personenkontext-commit.error.js'; @UseFilters( new SchulConnexValidationErrorFilter(), @@ -42,8 +42,7 @@ import { AuthenticationExceptionFilter } from '../../authentication/api/authenti export class DBiamPersonenkontextController { public constructor( private readonly personenkontextRepo: DBiamPersonenkontextRepo, - private readonly dbiamPersonenkontextService: DBiamPersonenkontextService, - private readonly personenkontextFactory: PersonenkontextFactory, + private readonly personenkontextFactory: DbiamPersonenkontextFactory, ) {} @Get(':personId') @@ -87,43 +86,47 @@ export class DBiamPersonenkontextController { @Body() params: DbiamPersonenkontextBodyParams, @Permissions() permissions: PersonPermissions, ): Promise { - // Construct new personenkontext - const newPersonenkontext: Personenkontext = this.personenkontextFactory.createNew( + // Get existing personenkontexte + const existingPKs: DbiamPersonenkontextBodyParams[] = await this.personenkontextRepo.findByPerson( params.personId, - params.organisationId, - params.rolleId, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - params.befristung, ); - //Check specifications - const specificationCheckError: Option = - await this.dbiamPersonenkontextService.checkSpecifications(newPersonenkontext); - if (specificationCheckError) { - throw specificationCheckError; + // Add new PK to list + existingPKs.push({ + personId: params.personId, + organisationId: params.organisationId, + rolleId: params.rolleId, + befristung: params.befristung, + }); + + // Update + const personenkontextUpdate: PersonenkontexteUpdate = + this.personenkontextFactory.createNewPersonenkontexteUpdate( + params.personId, + new Date(), + existingPKs.length - 1, + existingPKs, + permissions, + ); + + const updateResult: Personenkontext[] | PersonenkontexteUpdateError = + await personenkontextUpdate.update(); + + if (updateResult instanceof DomainError) { + throw updateResult; } - // Save personenkontext - const saveResult: Result, DomainError> = await this.personenkontextRepo.saveAuthorized( - newPersonenkontext, - permissions, + const newPersonenkontext: Personenkontext | undefined = updateResult.find( + (pk: Personenkontext) => + pk.personId === params.personId && + pk.organisationId === params.organisationId && + pk.rolleId === params.rolleId, ); - if (!saveResult.ok) { - if (saveResult.error instanceof OrganisationMatchesRollenartError) { - throw saveResult.error; - } - - throw SchulConnexErrorMapper.mapSchulConnexErrorToHttpException( - SchulConnexErrorMapper.mapDomainErrorToSchulConnexError(saveResult.error), - ); + if (!newPersonenkontext) { + throw new PersonenkontextCommitError(); } - return new DBiamPersonenkontextResponse(saveResult.value); + return new DBiamPersonenkontextResponse(newPersonenkontext); } } diff --git a/src/modules/personenkontext/api/personenkontexte-update-exception-filter.ts b/src/modules/personenkontext/api/personenkontexte-update-exception-filter.ts index 0c9bd6662..a07045bbf 100644 --- a/src/modules/personenkontext/api/personenkontexte-update-exception-filter.ts +++ b/src/modules/personenkontext/api/personenkontexte-update-exception-filter.ts @@ -14,6 +14,7 @@ import { PersonenkontexteUpdateError } from '../domain/error/personenkontexte-up import { UpdateInvalidLastModifiedError } from '../domain/error/update-invalid-last-modified.error.js'; import { UpdatePersonNotFoundError } from '../domain/error/update-person-not-found.error.js'; import { UpdateInvalidRollenartForLernError } from '../domain/error/update-invalid-rollenart-for-lern.error.js'; +import { PersonenkontextCommitError } from '../domain/error/personenkontext-commit.error.js'; import { PersonenkontextBefristungRequiredError } from '../domain/error/personenkontext-befristung-required.error.js'; @Catch(PersonenkontexteUpdateError) @@ -68,6 +69,13 @@ export class PersonenkontexteUpdateExceptionFilter implements ExceptionFilter { it('should create an error with the correct message and code', () => { - const message: string = 'An error occurred'; - const error: PersonenkontextCommitError = new PersonenkontextCommitError(message); + const error: PersonenkontextCommitError = new PersonenkontextCommitError(); expect(error).toBeInstanceOf(PersonenkontextCommitError); - expect(error.message).toBe(message); - expect(error.code).toBe('PERSONENKONTEXT_COULD_NOT_BE_COMMITED'); + expect(error.message).toBe('PERSONENKONTEXT_COULD_NOT_BE_COMMITED'); + expect(error.code).toBe('ENTITIES_COULD_NOT_BE_UPDATED'); expect(error.details).toBeUndefined(); }); it('should create an error with details as an array', () => { - const message: string = 'An error occurred'; const details: { key: string; }[] = [{ key: 'value' }]; - const error: PersonenkontextCommitError = new PersonenkontextCommitError(message, details); + const error: PersonenkontextCommitError = new PersonenkontextCommitError(details); expect(error).toBeInstanceOf(PersonenkontextCommitError); - expect(error.message).toBe(message); - expect(error.code).toBe('PERSONENKONTEXT_COULD_NOT_BE_COMMITED'); + expect(error.message).toBe('PERSONENKONTEXT_COULD_NOT_BE_COMMITED'); + expect(error.code).toBe('ENTITIES_COULD_NOT_BE_UPDATED'); expect(error.details).toEqual(details); }); }); diff --git a/src/modules/personenkontext/domain/error/personenkontext-commit.error.ts b/src/modules/personenkontext/domain/error/personenkontext-commit.error.ts index 5268d226c..5212d6aec 100644 --- a/src/modules/personenkontext/domain/error/personenkontext-commit.error.ts +++ b/src/modules/personenkontext/domain/error/personenkontext-commit.error.ts @@ -1,10 +1,7 @@ -import { DomainError } from '../../../../shared/error/index.js'; +import { PersonenkontexteUpdateError } from './personenkontexte-update.error.js'; -export class PersonenkontextCommitError extends DomainError { - public constructor( - public override readonly message: string, - details?: unknown[] | Record, - ) { - super(message, 'PERSONENKONTEXT_COULD_NOT_BE_COMMITED', details); +export class PersonenkontextCommitError extends PersonenkontexteUpdateError { + public constructor(details?: unknown[] | Record) { + super('PERSONENKONTEXT_COULD_NOT_BE_COMMITED', details); } } diff --git a/src/modules/personenkontext/domain/personenkontexte-update.spec.ts b/src/modules/personenkontext/domain/personenkontexte-update.spec.ts index 398abe7d3..1018af595 100644 --- a/src/modules/personenkontext/domain/personenkontexte-update.spec.ts +++ b/src/modules/personenkontext/domain/personenkontexte-update.spec.ts @@ -59,6 +59,7 @@ describe('PersonenkontexteUpdate', () => { let pk2: Personenkontext; let personPermissionsMock: PersonPermissionsMock; let rolleRepoMock: DeepMocked; + let loggerMock: DeepMocked; beforeAll(async () => { module = await Test.createTestingModule({ @@ -122,6 +123,7 @@ describe('PersonenkontexteUpdate', () => { }); personPermissionsMock = new PersonPermissionsMock(); rolleRepoMock = module.get(RolleRepo); + loggerMock = module.get(ClassLogger); }); afterAll(async () => { @@ -177,6 +179,43 @@ describe('PersonenkontexteUpdate', () => { }); }); + describe('when personenkontext could not be saved', () => { + beforeAll(() => { + sut = dbiamPersonenkontextFactory.createNewPersonenkontexteUpdate( + personId, + lastModified, + 0, + [pk1], + personPermissionsMock, + ); + }); + + it('should log error', async () => { + dBiamPersonenkontextRepoMock.find.mockResolvedValue(null); + dBiamPersonenkontextRepoMock.find.mockResolvedValueOnce(null); //mock pk1 is not found => therefore handled as new + dBiamPersonenkontextRepoMock.findByPerson.mockResolvedValueOnce([]); //person has no existing PKs + dBiamPersonenkontextRepoMock.findByPerson.mockResolvedValueOnce([]); //CheckRollenartLernSpecification: person has no existing PKs + rolleRepoMock.findByIds.mockResolvedValueOnce(new Map()); //CheckRollenartLernSpecification + rolleRepoMock.findByIds.mockResolvedValueOnce(new Map()); //CheckRollenartLernSpecification + rolleRepoMock.findByIds.mockResolvedValueOnce(new Map()); + dBiamPersonenkontextRepoMock.findByPerson.mockResolvedValueOnce([pk1, pk2]); // mock while checking the existing PKs + dBiamPersonenkontextRepoMock.findByPerson.mockResolvedValueOnce([pk1, pk2]); + const newPerson: Person = createMock>(); + personRepoMock.findById.mockResolvedValueOnce(newPerson); + + const error: Error = new Error('DB Error'); + dBiamPersonenkontextRepoMock.save.mockRejectedValueOnce(error); + + const updateResult: Personenkontext[] | PersonenkontexteUpdateError = await sut.update(); + + expect(updateResult).toBeInstanceOf(Array); + expect(loggerMock.error).toHaveBeenCalledWith( + expect.stringMatching(/Personenkontext with \(.*\) could not be added!/), + error, + ); + }); + }); + describe('when sent personenkontexte contain personenkontext with mismatching personId', () => { beforeAll(() => { const count: number = 2; diff --git a/src/modules/personenkontext/domain/personenkontexte-update.ts b/src/modules/personenkontext/domain/personenkontexte-update.ts index 4fc87301c..8938a1c57 100644 --- a/src/modules/personenkontext/domain/personenkontexte-update.ts +++ b/src/modules/personenkontext/domain/personenkontexte-update.ts @@ -8,7 +8,6 @@ import { UpdatePersonIdMismatchError } from './error/update-person-id-mismatch.e import { PersonenkontexteUpdateError } from './error/personenkontexte-update.error.js'; import { PersonenkontextFactory } from './personenkontext.factory.js'; import { EventService } from '../../../core/eventbus/index.js'; -import { SimplePersonenkontextDeletedEvent } from '../../../shared/events/simple-personenkontext-deleted.event.js'; import { UpdatePersonNotFoundError } from './error/update-person-not-found.error.js'; import { PersonenkontextUpdatedEvent } from '../../../shared/events/personenkontext-updated.event.js'; import { PersonRepository } from '../../person/persistence/person.repository.js'; @@ -23,12 +22,14 @@ import { MissingPermissionsError } from '../../../shared/error/missing-permissio import { UpdateInvalidRollenartForLernError } from './error/update-invalid-rollenart-for-lern.error.js'; import { IPersonPermissions } from '../../../shared/permissions/person-permissions.interface.js'; import { CheckRollenartLernSpecification } from '../specification/nur-rolle-lern.js'; +import { ClassLogger } from '../../../core/logging/class-logger.js'; import { CheckBefristungSpecification } from '../specification/befristung-required-bei-rolle-befristungspflicht.js'; import { PersonenkontextBefristungRequiredError } from './error/personenkontext-befristung-required.error.js'; export class PersonenkontexteUpdate { private constructor( private readonly eventService: EventService, + private readonly logger: ClassLogger, private readonly dBiamPersonenkontextRepo: DBiamPersonenkontextRepo, private readonly personRepo: PersonRepository, private readonly rolleRepo: RolleRepo, @@ -43,6 +44,7 @@ export class PersonenkontexteUpdate { public static createNew( eventService: EventService, + logger: ClassLogger, dBiamPersonenkontextRepo: DBiamPersonenkontextRepo, personRepo: PersonRepository, rolleRepo: RolleRepo, @@ -56,6 +58,7 @@ export class PersonenkontexteUpdate { ): PersonenkontexteUpdate { return new PersonenkontexteUpdate( eventService, + logger, dBiamPersonenkontextRepo, personRepo, rolleRepo, @@ -210,16 +213,12 @@ export class PersonenkontexteUpdate { pk.rolleId == existingPK.rolleId, ) ) { - await this.dBiamPersonenkontextRepo.delete(existingPK); - deletedPKs.push(existingPK); - this.eventService.publish( - new SimplePersonenkontextDeletedEvent( - existingPK.id, - existingPK.personId, - existingPK.organisationId, - existingPK.rolleId, - ), - ); + try { + await this.dBiamPersonenkontextRepo.delete(existingPK).then(() => {}); + deletedPKs.push(existingPK); + } catch (err) { + this.logger.error(`Personenkontext with ID ${existingPK.id} could not be deleted!`, err); + } } } @@ -241,11 +240,15 @@ export class PersonenkontexteUpdate { existingPK.rolleId == sentPK.rolleId, ) ) { - const savedPK: Personenkontext = await this.dBiamPersonenkontextRepo.save(sentPK); - createdPKs.push(savedPK); - /*this.eventService.publish( - new PersonenkontextCreatedEvent(sentPK.personId, sentPK.organisationId, sentPK.rolleId), - );*/ + try { + const savedPK: Personenkontext = await this.dBiamPersonenkontextRepo.save(sentPK); + createdPKs.push(savedPK); + } catch (err) { + this.logger.error( + `Personenkontext with (person: ${sentPK.personId}, organisation: ${sentPK.organisationId}, rolle: ${sentPK.rolleId}) could not be added!`, + err, + ); + } } } @@ -345,6 +348,9 @@ export class PersonenkontexteUpdate { ]); if (!person) { + this.logger.error( + `Could not find person with ID ${this.personId} while building PersonenkontextUpdatedEvent`, + ); return; // Person can not be found } diff --git a/src/modules/personenkontext/persistence/dbiam-personenkontext.repo.ts b/src/modules/personenkontext/persistence/dbiam-personenkontext.repo.ts index d9ece71aa..e8c994d96 100644 --- a/src/modules/personenkontext/persistence/dbiam-personenkontext.repo.ts +++ b/src/modules/personenkontext/persistence/dbiam-personenkontext.repo.ts @@ -16,9 +16,6 @@ import { MissingPermissionsError } from '../../../shared/error/missing-permissio import { EntityAlreadyExistsError } from '../../../shared/error/entity-already-exists.error.js'; import { PersonenkontextFactory } from '../domain/personenkontext.factory.js'; import { MismatchedRevisionError } from '../../../shared/error/mismatched-revision.error.js'; -import { PersonenkontextCreatedEvent } from '../../../shared/events/personenkontext-created.event.js'; -import { EventService } from '../../../core/eventbus/index.js'; -import { SimplePersonenkontextDeletedEvent } from '../../../shared/events/simple-personenkontext-deleted.event.js'; export function mapAggregateToData( personenKontext: Personenkontext, @@ -60,7 +57,6 @@ function mapEntityToAggregate( export class DBiamPersonenkontextRepo { public constructor( private readonly em: EntityManager, - private readonly eventService: EventService, private readonly personenkontextFactory: PersonenkontextFactory, ) {} @@ -206,6 +202,9 @@ export class DBiamPersonenkontextRepo { return !!personenKontext; } + /** + * @deprecated This method does not throw events, please always use the PersonenkontexteUpdate aggregate + */ public async save(personenKontext: Personenkontext): Promise> { if (personenKontext.id) { return this.update(personenKontext); @@ -214,6 +213,9 @@ export class DBiamPersonenkontextRepo { } } + /** + * @deprecated This method does not throw events, please always use the PersonenkontexteUpdate aggregate + */ public async saveAuthorized( personenkontext: Personenkontext, permissions: PersonPermissions, @@ -259,19 +261,16 @@ export class DBiamPersonenkontextRepo { ); await this.em.persistAndFlush(personenKontextEntity); - this.eventService.publish( - new PersonenkontextCreatedEvent( - personenkontext.personId, - personenkontext.organisationId, - personenkontext.rolleId, - ), - ); + return { ok: true, value: mapEntityToAggregate(personenKontextEntity, this.personenkontextFactory), }; } + /** + * @deprecated This method does not throw events, please always use the PersonenkontexteUpdate aggregate + */ public async deleteAuthorized( id: PersonenkontextID, revision: string, @@ -298,22 +297,22 @@ export class DBiamPersonenkontextRepo { return; } + /** + * @deprecated This method does not throw events, please always use the PersonenkontexteUpdate aggregate + */ private async create(personenKontext: Personenkontext): Promise> { const personenKontextEntity: PersonenkontextEntity = this.em.create( PersonenkontextEntity, mapAggregateToData(personenKontext), ); await this.em.persistAndFlush(personenKontextEntity); - this.eventService.publish( - new PersonenkontextCreatedEvent( - personenKontext.personId, - personenKontext.organisationId, - personenKontext.rolleId, - ), - ); + return mapEntityToAggregate(personenKontextEntity, this.personenkontextFactory); } + /** + * @deprecated This method does not throw events, please always use the PersonenkontexteUpdate aggregate + */ private async update(personenKontext: Personenkontext): Promise> { const personenKontextEntity: Loaded = await this.em.findOneOrFail( PersonenkontextEntity, @@ -338,6 +337,9 @@ export class DBiamPersonenkontextRepo { return organisationIDs.includes(entity.organisationId); } + /** + * @deprecated This method does not throw events, please always use the PersonenkontexteUpdate aggregate + */ public async delete(personenKontext: Personenkontext): Promise { const personId: PersonID = personenKontext.personId; const organisationId: OrganisationID = personenKontext.organisationId; @@ -348,14 +350,6 @@ export class DBiamPersonenkontextRepo { organisationId: organisationId, rolleId: rolleId, }); - this.eventService.publish( - new SimplePersonenkontextDeletedEvent( - personenKontext.id, - personenKontext.personId, - personenKontext.organisationId, - personenKontext.rolleId, - ), - ); } public async hasSystemrechtAtOrganisation( @@ -392,6 +386,9 @@ export class DBiamPersonenkontextRepo { return result[0].has_systemrecht_at_orga as boolean; } + /** + * @deprecated This method does not throw events, please always use the PersonenkontexteUpdate aggregate + */ public async deleteById(id: string): Promise { const deletedPersons: number = await this.em.nativeDelete(PersonenkontextEntity, { id }); return deletedPersons > 0; diff --git a/src/modules/personenkontext/specification/gleiche-rolle-an-klasse-wie-schule.spec.ts b/src/modules/personenkontext/specification/gleiche-rolle-an-klasse-wie-schule.spec.ts new file mode 100644 index 000000000..2d52eeb70 --- /dev/null +++ b/src/modules/personenkontext/specification/gleiche-rolle-an-klasse-wie-schule.spec.ts @@ -0,0 +1,168 @@ +import { faker } from '@faker-js/faker'; +import { DeepMocked, createMock } from '@golevelup/ts-jest'; + +import { DoFactory } from '../../../../test/utils/index.js'; +import { OrganisationsTyp } from '../../organisation/domain/organisation.enums.js'; +import { OrganisationRepository } from '../../organisation/persistence/organisation.repository.js'; +import { RolleRepo } from '../../rolle/repo/rolle.repo.js'; +import { Personenkontext } from '../domain/personenkontext.js'; +import { DBiamPersonenkontextRepo } from '../persistence/dbiam-personenkontext.repo.js'; +import { GleicheRolleAnKlasseWieSchule } from './gleiche-rolle-an-klasse-wie-schule.js'; + +describe('GleicheRolleAnKlasseWieSchule specification', () => { + const organisationRepoMock: DeepMocked = createMock(); + const personenkontextRepoMock: DeepMocked = createMock(); + const rolleRepoMock: DeepMocked = createMock(); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should return true, if all checks pass', async () => { + const spec: GleicheRolleAnKlasseWieSchule = new GleicheRolleAnKlasseWieSchule( + organisationRepoMock, + personenkontextRepoMock, + rolleRepoMock, + ); + const schuleId: string = faker.string.uuid(); + const rolleId: string = faker.string.uuid(); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE, administriertVon: schuleId }), + ); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.SCHULE, id: schuleId }), + ); + personenkontextRepoMock.findByPerson.mockResolvedValueOnce([ + DoFactory.createPersonenkontext(true, { organisationId: schuleId }), + ]); + rolleRepoMock.findById.mockResolvedValueOnce(DoFactory.createRolle(true, { id: rolleId })); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false, { rolleId }); + + await expect(spec.isSatisfiedBy(pk)).resolves.toBe(true); + }); + + it('should return true, if organisation is not Klasse', async () => { + const spec: GleicheRolleAnKlasseWieSchule = new GleicheRolleAnKlasseWieSchule( + organisationRepoMock, + personenkontextRepoMock, + rolleRepoMock, + ); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.SCHULE }), + ); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(spec.isSatisfiedBy(pk)).resolves.toBe(true); + }); + + it('should return false, if organisation does not exist', async () => { + const spec: GleicheRolleAnKlasseWieSchule = new GleicheRolleAnKlasseWieSchule( + organisationRepoMock, + personenkontextRepoMock, + rolleRepoMock, + ); + organisationRepoMock.findById.mockResolvedValueOnce(undefined); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(spec.isSatisfiedBy(pk)).resolves.toBe(false); + }); + + it('should return false, if organisation does not have administriertVon', async () => { + const spec: GleicheRolleAnKlasseWieSchule = new GleicheRolleAnKlasseWieSchule( + organisationRepoMock, + personenkontextRepoMock, + rolleRepoMock, + ); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE, administriertVon: undefined }), + ); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(spec.isSatisfiedBy(pk)).resolves.toBe(false); + }); + + it('should return false, if parent organisation can not be found', async () => { + const spec: GleicheRolleAnKlasseWieSchule = new GleicheRolleAnKlasseWieSchule( + organisationRepoMock, + personenkontextRepoMock, + rolleRepoMock, + ); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE, administriertVon: faker.string.uuid() }), + ); + organisationRepoMock.findById.mockResolvedValueOnce(undefined); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(spec.isSatisfiedBy(pk)).resolves.toBe(false); + }); + + it('should return false, if no matching personenkontext can be found', async () => { + const spec: GleicheRolleAnKlasseWieSchule = new GleicheRolleAnKlasseWieSchule( + organisationRepoMock, + personenkontextRepoMock, + rolleRepoMock, + ); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE, administriertVon: faker.string.uuid() }), + ); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.SCHULE }), + ); + personenkontextRepoMock.findByPerson.mockResolvedValueOnce([DoFactory.createPersonenkontext(true)]); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(spec.isSatisfiedBy(pk)).resolves.toBe(false); + }); + + it('should return false, if no rolle can be found', async () => { + const spec: GleicheRolleAnKlasseWieSchule = new GleicheRolleAnKlasseWieSchule( + organisationRepoMock, + personenkontextRepoMock, + rolleRepoMock, + ); + const schuleId: string = faker.string.uuid(); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE, administriertVon: schuleId }), + ); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.SCHULE, id: schuleId }), + ); + personenkontextRepoMock.findByPerson.mockResolvedValueOnce([ + DoFactory.createPersonenkontext(true, { organisationId: schuleId }), + ]); + rolleRepoMock.findById.mockResolvedValueOnce(undefined); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(spec.isSatisfiedBy(pk)).resolves.toBe(false); + }); + + it('should return false, if rollen ids do not match', async () => { + const spec: GleicheRolleAnKlasseWieSchule = new GleicheRolleAnKlasseWieSchule( + organisationRepoMock, + personenkontextRepoMock, + rolleRepoMock, + ); + const schuleId: string = faker.string.uuid(); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE, administriertVon: schuleId }), + ); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.SCHULE, id: schuleId }), + ); + personenkontextRepoMock.findByPerson.mockResolvedValueOnce([ + DoFactory.createPersonenkontext(true, { organisationId: schuleId }), + ]); + rolleRepoMock.findById.mockResolvedValueOnce(DoFactory.createRolle(true)); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(spec.isSatisfiedBy(pk)).resolves.toBe(false); + }); +}); diff --git a/src/modules/personenkontext/specification/nur-lehr-und-lern-an-klasse.spec.ts b/src/modules/personenkontext/specification/nur-lehr-und-lern-an-klasse.spec.ts new file mode 100644 index 000000000..fced65db5 --- /dev/null +++ b/src/modules/personenkontext/specification/nur-lehr-und-lern-an-klasse.spec.ts @@ -0,0 +1,73 @@ +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { DoFactory } from '../../../../test/utils/do-factory.js'; +import { OrganisationsTyp } from '../../organisation/domain/organisation.enums.js'; +import { OrganisationRepository } from '../../organisation/persistence/organisation.repository.js'; +import { RollenArt } from '../../rolle/domain/rolle.enums.js'; +import { RolleRepo } from '../../rolle/repo/rolle.repo.js'; +import { Personenkontext } from '../domain/personenkontext.js'; +import { NurLehrUndLernAnKlasse } from './nur-lehr-und-lern-an-klasse.js'; + +describe('NurLehrUndLernAnKlasse specification', () => { + const organisationRepoMock: DeepMocked = createMock(); + const rolleRepoMock: DeepMocked = createMock(); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should return true, if organisation is klasse and rolle is LEHR', async () => { + const specification: NurLehrUndLernAnKlasse = new NurLehrUndLernAnKlasse(organisationRepoMock, rolleRepoMock); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE }), + ); + rolleRepoMock.findById.mockResolvedValueOnce(DoFactory.createRolle(true, { rollenart: RollenArt.LEHR })); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(specification.isSatisfiedBy(pk)).resolves.toBe(true); + }); + + it('should return true, if organisation is klasse and rolle is LERN', async () => { + const specification: NurLehrUndLernAnKlasse = new NurLehrUndLernAnKlasse(organisationRepoMock, rolleRepoMock); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE }), + ); + rolleRepoMock.findById.mockResolvedValueOnce(DoFactory.createRolle(true, { rollenart: RollenArt.LERN })); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(specification.isSatisfiedBy(pk)).resolves.toBe(true); + }); + + it('should return true, if organisation is not KLASSE', async () => { + const specification: NurLehrUndLernAnKlasse = new NurLehrUndLernAnKlasse(organisationRepoMock, rolleRepoMock); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.SCHULE }), + ); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(specification.isSatisfiedBy(pk)).resolves.toBe(true); + }); + + it('should return false, if organisation is not found', async () => { + const specification: NurLehrUndLernAnKlasse = new NurLehrUndLernAnKlasse(organisationRepoMock, rolleRepoMock); + organisationRepoMock.findById.mockResolvedValueOnce(undefined); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(specification.isSatisfiedBy(pk)).resolves.toBe(false); + }); + + it('should return true, if rolle is not found', async () => { + const specification: NurLehrUndLernAnKlasse = new NurLehrUndLernAnKlasse(organisationRepoMock, rolleRepoMock); + organisationRepoMock.findById.mockResolvedValueOnce( + DoFactory.createOrganisation(true, { typ: OrganisationsTyp.KLASSE }), + ); + rolleRepoMock.findById.mockResolvedValueOnce(undefined); + + const pk: Personenkontext = DoFactory.createPersonenkontext(false); + + await expect(specification.isSatisfiedBy(pk)).resolves.toBe(false); + }); +}); diff --git a/src/modules/utility/event-adapter.spec.ts b/src/modules/utility/event-adapter.spec.ts deleted file mode 100644 index 3056671a9..000000000 --- a/src/modules/utility/event-adapter.spec.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { - ConfigTestModule, - DatabaseTestModule, - DEFAULT_TIMEOUT_FOR_TESTCONTAINERS, - MapperTestModule, -} from '../../../test/utils/index.js'; -import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { PersonRepository } from '../person/persistence/person.repository.js'; -import { RolleRepo } from '../rolle/repo/rolle.repo.js'; -import { faker } from '@faker-js/faker'; -import { OrganisationRepository } from '../organisation/persistence/organisation.repository.js'; -import { SimplePersonenkontextDeletedEvent } from '../../shared/events/simple-personenkontext-deleted.event.js'; -import { EventAdapter } from './event-adapter.js'; -import { ClassLogger } from '../../core/logging/class-logger.js'; -import { APP_PIPE } from '@nestjs/core'; -import { GlobalValidationPipe } from '../../shared/validation/global-validation.pipe.js'; -import { EventService } from '../../core/eventbus/services/event.service.js'; -import { Person } from '../person/domain/person.js'; -import { Organisation } from '../organisation/domain/organisation.js'; -import { Rolle } from '../rolle/domain/rolle.js'; -import { RollenArt } from '../rolle/domain/rolle.enums.js'; -import { OrganisationsTyp } from '../organisation/domain/organisation.enums.js'; -import { UtilityModule } from './utility.module.js'; - -describe('Event Adapter', () => { - let app: INestApplication; - - let sut: EventAdapter; - - let eventServiceMock: DeepMocked; - let personRepositoryMock: DeepMocked; - let organisationRepositoryMock: DeepMocked; - let rolleRepoMock: DeepMocked; - let loggerMock: DeepMocked; - - beforeAll(async () => { - const module: TestingModule = await Test.createTestingModule({ - imports: [ - ConfigTestModule, - MapperTestModule, - DatabaseTestModule.forRoot({ isDatabaseRequired: false }), - UtilityModule, - ], - providers: [ - { - provide: APP_PIPE, - useClass: GlobalValidationPipe, - }, - { - provide: ClassLogger, - useValue: createMock(), - }, - ], - }) - .overrideProvider(ClassLogger) - .useValue(createMock()) - .overrideProvider(EventService) - .useValue(createMock()) - .overrideProvider(PersonRepository) - .useValue(createMock()) - .overrideProvider(OrganisationRepository) - .useValue(createMock()) - .overrideProvider(RolleRepo) - .useValue(createMock()) - .compile(); - - eventServiceMock = module.get(EventService); - personRepositoryMock = module.get(PersonRepository); - organisationRepositoryMock = module.get(OrganisationRepository); - rolleRepoMock = module.get(RolleRepo); - loggerMock = module.get(ClassLogger); - - sut = module.get(EventAdapter); - - app = module.createNestApplication(); - await app.init(); - }, DEFAULT_TIMEOUT_FOR_TESTCONTAINERS); - - afterAll(async () => { - await app.close(); - }); - - beforeEach(() => { - jest.resetAllMocks(); - }); - - describe('handlePersonenkontextDeletedEvent', () => { - let fakePKId: string; - let fakePersonId: string; - let fakeOrgaId: string; - let fakeRolleId: string; - - beforeEach(() => { - fakePKId = faker.string.uuid(); - fakePersonId = faker.string.uuid(); - fakeOrgaId = faker.string.uuid(); - fakeRolleId = faker.string.uuid(); - }); - - describe('when every entity is found in DB', () => { - it('should log info and trigger PersonenkontextDeletedEvent', async () => { - const fakePerson: Person = createMock>({ - id: fakePersonId, - vorname: faker.person.firstName(), - familienname: faker.person.lastName(), - referrer: faker.internet.userName(), - }); - const fakeOrga: Organisation = createMock>({ - id: fakeOrgaId, - typ: OrganisationsTyp.SCHULE, - kennung: faker.string.alpha({ length: 6 }), - }); - const fakeRolle: Rolle = createMock>({ - id: fakeRolleId, - rollenart: RollenArt.LEHR, - }); - personRepositoryMock.findById.mockResolvedValueOnce(fakePerson); - organisationRepositoryMock.findById.mockResolvedValueOnce(fakeOrga); - rolleRepoMock.findById.mockResolvedValueOnce(fakeRolle); - - const event: SimplePersonenkontextDeletedEvent = new SimplePersonenkontextDeletedEvent( - fakePKId, - fakePersonId, - fakeOrgaId, - fakeRolleId, - ); - - await sut.handlePersonenkontextDeletedEvent(event); - - expect(loggerMock.info).toHaveBeenCalledWith( - `Received PersonenkontextDeletedEvent, personId:${event.personId}, orgaId:${event.organisationId}, rolleId:${event.rolleId}`, - ); - expect(eventServiceMock.publish).toHaveBeenCalledWith( - expect.objectContaining({ - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - personData: expect.objectContaining({ - id: fakePerson.id, - vorname: fakePerson.vorname, - familienname: fakePerson.familienname, - referrer: fakePerson.referrer, - }), - kontextData: { - id: fakePKId, - rolleId: fakeRolle.id, - rolle: fakeRolle.rollenart, - orgaId: fakeOrga.id, - orgaTyp: fakeOrga.typ, - orgaKennung: fakeOrga.kennung, - }, - }), - ); - }); - }); - - describe('when person cannot be found', () => { - it('should log error', async () => { - personRepositoryMock.findById.mockResolvedValueOnce(undefined); - - const event: SimplePersonenkontextDeletedEvent = new SimplePersonenkontextDeletedEvent( - fakePKId, - fakePersonId, - fakeOrgaId, - fakeRolleId, - ); - - await sut.handlePersonenkontextDeletedEvent(event); - - expect(loggerMock.info).toHaveBeenCalledWith( - `Received PersonenkontextDeletedEvent, personId:${event.personId}, orgaId:${event.organisationId}, rolleId:${event.rolleId}`, - ); - expect(loggerMock.error).toHaveBeenCalledWith(`Could not find person for personId:${event.personId}`); - }); - }); - - describe('when organisation cannot be found', () => { - it('should log error', async () => { - personRepositoryMock.findById.mockResolvedValueOnce(createMock>()); - organisationRepositoryMock.findById.mockResolvedValueOnce(undefined); - - const event: SimplePersonenkontextDeletedEvent = new SimplePersonenkontextDeletedEvent( - fakePKId, - fakePersonId, - fakeOrgaId, - fakeRolleId, - ); - - await sut.handlePersonenkontextDeletedEvent(event); - - expect(loggerMock.info).toHaveBeenCalledWith( - `Received PersonenkontextDeletedEvent, personId:${event.personId}, orgaId:${event.organisationId}, rolleId:${event.rolleId}`, - ); - expect(loggerMock.error).toHaveBeenCalledWith( - `Could not find organisation for orgaId:${event.organisationId}`, - ); - }); - }); - - describe('when rolle cannot be found', () => { - it('should log error', async () => { - personRepositoryMock.findById.mockResolvedValueOnce(createMock>()); - organisationRepositoryMock.findById.mockResolvedValueOnce(createMock>()); - rolleRepoMock.findById.mockResolvedValueOnce(undefined); - - const event: SimplePersonenkontextDeletedEvent = new SimplePersonenkontextDeletedEvent( - fakePKId, - fakePersonId, - fakeOrgaId, - fakeRolleId, - ); - - await sut.handlePersonenkontextDeletedEvent(event); - - expect(loggerMock.info).toHaveBeenCalledWith( - `Received PersonenkontextDeletedEvent, personId:${event.personId}, orgaId:${event.organisationId}, rolleId:${event.rolleId}`, - ); - expect(loggerMock.error).toHaveBeenCalledWith(`Could not find rolle for rolleId:${event.rolleId}`); - }); - }); - }); -}); diff --git a/src/modules/utility/event-adapter.ts b/src/modules/utility/event-adapter.ts deleted file mode 100644 index 83f4797df..000000000 --- a/src/modules/utility/event-adapter.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { EventHandler } from '../../core/eventbus/decorators/event-handler.decorator.js'; -import { ClassLogger } from '../../core/logging/class-logger.js'; -import { Organisation } from '../organisation/domain/organisation.js'; -import { OrganisationRepository } from '../organisation/persistence/organisation.repository.js'; -import { SimplePersonenkontextDeletedEvent } from '../../shared/events/simple-personenkontext-deleted.event.js'; -import { RolleRepo } from '../rolle/repo/rolle.repo.js'; -import { PersonRepository } from '../person/persistence/person.repository.js'; -import { Person } from '../person/domain/person.js'; -import { Rolle } from '../rolle/domain/rolle.js'; -import { EventService } from '../../core/eventbus/services/event.service.js'; -import { PersonenkontextDeletedEvent } from '../../shared/events/personenkontext-deleted.event.js'; -import { - PersonenkontextEventKontextData, - PersonenkontextEventPersonData, -} from '../../shared/events/personenkontext-event.types.js'; - -@Injectable() -export class EventAdapter { - public constructor( - private readonly logger: ClassLogger, - private readonly eventService: EventService, - private readonly personRepository: PersonRepository, - private readonly organisationRepository: OrganisationRepository, - private readonly rolleRepo: RolleRepo, - ) {} - - @EventHandler(SimplePersonenkontextDeletedEvent) - public async handlePersonenkontextDeletedEvent(event: SimplePersonenkontextDeletedEvent): Promise { - this.logger.info( - `Received PersonenkontextDeletedEvent, personId:${event.personId}, orgaId:${event.organisationId}, rolleId:${event.rolleId}`, - ); - - const person: Option> = await this.personRepository.findById(event.personId); - if (!person) { - return this.logger.error(`Could not find person for personId:${event.personId}`); - } - const orga: Option> = await this.organisationRepository.findById(event.organisationId); - if (!orga) { - return this.logger.error(`Could not find organisation for orgaId:${event.organisationId}`); - } - const rolle: Option> = await this.rolleRepo.findById(event.rolleId); - if (!rolle) { - return this.logger.error(`Could not find rolle for rolleId:${event.rolleId}`); - } - const personData: PersonenkontextEventPersonData = { - id: person.id, - vorname: person.vorname, - familienname: person.familienname, - referrer: person.referrer, - email: person.email, - }; - const kontextData: PersonenkontextEventKontextData = { - id: event.personenkontextID, - rolleId: rolle.id, - rolle: rolle.rollenart, - orgaId: orga.id, - orgaTyp: orga.typ, - orgaKennung: orga.kennung, - }; - - const personenkontextDeletedEvent: PersonenkontextDeletedEvent = new PersonenkontextDeletedEvent( - personData, - kontextData, - ); - - this.eventService.publish(personenkontextDeletedEvent); - } -} diff --git a/src/modules/utility/utility.module.ts b/src/modules/utility/utility.module.ts deleted file mode 100644 index 8ef8d4d03..000000000 --- a/src/modules/utility/utility.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Module } from '@nestjs/common'; -import { RolleModule } from '../../modules/rolle/rolle.module.js'; -import { OrganisationModule } from '../../modules/organisation/organisation.module.js'; -import { PersonModule } from '../../modules/person/person.module.js'; -import { PersonenKontextModule } from '../../modules/personenkontext/personenkontext.module.js'; -import { EventAdapter } from './event-adapter.js'; -import { LoggerModule } from '../../core/logging/logger.module.js'; - -@Module({ - imports: [ - LoggerModule.register(UtilityModule.name), - RolleModule, - PersonModule, - OrganisationModule, - PersonenKontextModule, - ], - providers: [EventAdapter], - exports: [EventAdapter], -}) -export class UtilityModule {} diff --git a/src/server/server.module.ts b/src/server/server.module.ts index c0936d51a..9b31406f4 100644 --- a/src/server/server.module.ts +++ b/src/server/server.module.ts @@ -33,7 +33,6 @@ import { EventModule } from '../core/eventbus/index.js'; import { ItsLearningModule } from '../modules/itslearning/itslearning.module.js'; import { LdapModule } from '../core/ldap/ldap.module.js'; import { EmailModule } from '../modules/email/email.module.js'; -import { UtilityModule } from '../modules/utility/utility.module.js'; import { OxModule } from '../modules/ox/ox.module.js'; @Module({ @@ -90,7 +89,6 @@ import { OxModule } from '../modules/ox/ox.module.js'; EmailModule, OxModule, PrivacyIdeaAdministrationModule, - UtilityModule, //necessary to enable event-adapter to transform events ], providers: [ { diff --git a/src/shared/events/personenkontext-created.event.ts b/src/shared/events/personenkontext-created.event.ts deleted file mode 100644 index 155ead722..000000000 --- a/src/shared/events/personenkontext-created.event.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { BaseEvent } from './base-event.js'; -import { OrganisationID, PersonID, RolleID } from '../types/index.js'; - -export class PersonenkontextCreatedEvent extends BaseEvent { - public constructor( - public readonly personId: PersonID, - public readonly organisationId: OrganisationID, - public readonly rolleId: RolleID, - ) { - super(); - } -} diff --git a/src/shared/events/personenkontext-deleted.event.ts b/src/shared/events/personenkontext-deleted.event.ts deleted file mode 100644 index 0224a2fe6..000000000 --- a/src/shared/events/personenkontext-deleted.event.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BaseEvent } from './base-event.js'; -import { PersonenkontextEventKontextData, PersonenkontextEventPersonData } from './personenkontext-event.types.js'; - -export class PersonenkontextDeletedEvent extends BaseEvent { - public constructor( - public readonly personData: PersonenkontextEventPersonData, - public readonly kontextData: PersonenkontextEventKontextData, - ) { - super(); - } -} diff --git a/src/shared/events/simple-personenkontext-deleted.event.ts b/src/shared/events/simple-personenkontext-deleted.event.ts deleted file mode 100644 index dfa8d76ae..000000000 --- a/src/shared/events/simple-personenkontext-deleted.event.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { BaseEvent } from './base-event.js'; -import { OrganisationID, PersonenkontextID, PersonID, RolleID } from '../types/index.js'; - -export class SimplePersonenkontextDeletedEvent extends BaseEvent { - public constructor( - public readonly personenkontextID: PersonenkontextID, - public readonly personId: PersonID, - public readonly organisationId: OrganisationID, - public readonly rolleId: RolleID, - ) { - super(); - } -}