From 3c985ac65b579e8fe2457e4852dc272b1d48a5f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= Date: Mon, 8 Jan 2024 14:04:37 +0100 Subject: [PATCH 1/7] implementation --- .../src/modules/group/repo/group.repo.spec.ts | 112 +++++++++- .../src/modules/group/repo/group.repo.ts | 23 ++ .../modules/group/repo/group.scope.spec.ts | 26 +++ .../src/modules/group/repo/group.scope.ts | 7 + .../group/service/group.service.spec.ts | 42 ++++ .../modules/group/service/group.service.ts | 14 ++ .../school-system-options.builder.spec.ts | 43 ++-- .../domain/school-system-options.builder.ts | 15 +- .../legacy-school/legacy-school.module.ts | 13 +- .../modules/legacy-school/loggable/index.ts | 1 + ...tegy-no-options.loggable-exception.spec.ts | 21 ++ ...-strategy-no-options.loggable-exception.ts | 20 ++ .../modules/legacy-school/service/index.ts | 1 + ...rovisioning-options-update-service.spec.ts | 209 ++++++++++++++++++ .../provisioning-options-update-service.ts | 42 ++++ .../uc/school-system-options.uc.spec.ts | 86 ++++++- .../uc/school-system-options.uc.ts | 23 +- .../config/provisioning-config.ts | 2 - .../strategy/oidc/oidc.strategy.spec.ts | 2 - .../strategy/oidc/oidc.strategy.ts | 4 +- config/default.schema.json | 5 - src/services/config/publicAppConfigService.js | 1 - 22 files changed, 659 insertions(+), 53 deletions(-) create mode 100644 apps/server/src/modules/legacy-school/loggable/provisioning-strategy-no-options.loggable-exception.spec.ts create mode 100644 apps/server/src/modules/legacy-school/loggable/provisioning-strategy-no-options.loggable-exception.ts create mode 100644 apps/server/src/modules/legacy-school/service/provisioning-options-update-service.spec.ts create mode 100644 apps/server/src/modules/legacy-school/service/provisioning-options-update-service.ts diff --git a/apps/server/src/modules/group/repo/group.repo.spec.ts b/apps/server/src/modules/group/repo/group.repo.spec.ts index a8e39f9d86e..e127155fd25 100644 --- a/apps/server/src/modules/group/repo/group.repo.spec.ts +++ b/apps/server/src/modules/group/repo/group.repo.spec.ts @@ -2,13 +2,14 @@ import { MongoMemoryDatabaseModule } from '@infra/database'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; import { ExternalSource, UserDO } from '@shared/domain/domainobject'; -import { SchoolEntity, User } from '@shared/domain/entity'; +import { SchoolEntity, SystemEntity, User } from '@shared/domain/entity'; import { cleanupCollections, groupEntityFactory, groupFactory, roleFactory, schoolFactory, + systemEntityFactory, userDoFactory, userFactory, } from '@shared/testing'; @@ -268,6 +269,115 @@ describe('GroupRepo', () => { }); }); + describe('findGroupsBySchoolIdAndSystemIdAndGroupType', () => { + describe('when groups for the school exist', () => { + const setup = async () => { + const system: SystemEntity = systemEntityFactory.buildWithId(); + const school: SchoolEntity = schoolFactory.buildWithId({ systems: [system] }); + const groups: GroupEntity[] = groupEntityFactory.buildListWithId(3, { + type: GroupEntityTypes.CLASS, + organization: school, + externalSource: { + system, + }, + }); + groups[1].type = GroupEntityTypes.COURSE; + groups[2].type = GroupEntityTypes.OTHER; + + const otherSchool: SchoolEntity = schoolFactory.buildWithId({ systems: [system] }); + const otherGroups: GroupEntity[] = groupEntityFactory.buildListWithId(2, { + type: GroupEntityTypes.CLASS, + organization: otherSchool, + }); + + await em.persistAndFlush([school, system, ...groups, otherSchool, ...otherGroups]); + em.clear(); + + return { + school, + system, + otherSchool, + groups, + }; + }; + + it('should return the groups', async () => { + const { school, system } = await setup(); + + const result: Group[] = await repo.findGroupsBySchoolIdAndSystemIdAndGroupType( + school.id, + system.id, + GroupTypes.CLASS + ); + + expect(result).toHaveLength(1); + }); + + it('should only return groups from the selected school', async () => { + const { school, system } = await setup(); + + const result: Group[] = await repo.findGroupsBySchoolIdAndSystemIdAndGroupType( + school.id, + system.id, + GroupTypes.CLASS + ); + + expect(result.every((group) => group.organizationId === school.id)).toEqual(true); + }); + + it('should only return groups from the selected system', async () => { + const { school, system } = await setup(); + + const result: Group[] = await repo.findGroupsBySchoolIdAndSystemIdAndGroupType( + school.id, + system.id, + GroupTypes.CLASS + ); + + expect(result.every((group) => group.externalSource?.systemId === system.id)).toEqual(true); + }); + + it('should return only groups of the given group type', async () => { + const { school, system } = await setup(); + + const result: Group[] = await repo.findGroupsBySchoolIdAndSystemIdAndGroupType( + school.id, + system.id, + GroupTypes.CLASS + ); + + expect(result).toEqual([expect.objectContaining>({ type: GroupTypes.CLASS })]); + }); + }); + + describe('when no group exists', () => { + const setup = async () => { + const school: SchoolEntity = schoolFactory.buildWithId(); + const system: SystemEntity = systemEntityFactory.buildWithId(); + + await em.persistAndFlush([school, system]); + em.clear(); + + return { + school, + system, + }; + }; + + it('should return an empty array', async () => { + const { school, system } = await setup(); + + const result: Group[] = await repo.findGroupsBySchoolIdAndSystemIdAndGroupType( + school.id, + system.id, + GroupTypes.CLASS + ); + + expect(result).toHaveLength(0); + }); + }); + }); + describe('save', () => { describe('when a new object is provided', () => { const setup = () => { diff --git a/apps/server/src/modules/group/repo/group.repo.ts b/apps/server/src/modules/group/repo/group.repo.ts index d355ee6b117..768a6f3f733 100644 --- a/apps/server/src/modules/group/repo/group.repo.ts +++ b/apps/server/src/modules/group/repo/group.repo.ts @@ -83,6 +83,29 @@ export class GroupRepo { return domainObjects; } + public async findGroupsBySchoolIdAndSystemIdAndGroupType( + schoolId: EntityId, + systemId: EntityId, + groupType: GroupTypes + ): Promise { + const groupEntityType: GroupEntityTypes = GroupTypesToGroupEntityTypesMapping[groupType]; + + const scope: Scope = new GroupScope() + .byOrganizationId(schoolId) + .bySystemId(systemId) + .byTypes([groupEntityType]); + + const entities: GroupEntity[] = await this.em.find(GroupEntity, scope.query); + + const domainObjects: Group[] = entities.map((entity) => { + const props: GroupProps = GroupDomainMapper.mapEntityToDomainObjectProperties(entity); + + return new Group(props); + }); + + return domainObjects; + } + public async save(domainObject: Group): Promise { const entityProps: GroupEntityProps = GroupDomainMapper.mapDomainObjectToEntityProperties(domainObject, this.em); diff --git a/apps/server/src/modules/group/repo/group.scope.spec.ts b/apps/server/src/modules/group/repo/group.scope.spec.ts index 1088651853b..28a71540e70 100644 --- a/apps/server/src/modules/group/repo/group.scope.spec.ts +++ b/apps/server/src/modules/group/repo/group.scope.spec.ts @@ -54,6 +54,32 @@ describe(GroupScope.name, () => { }); }); + describe('bySystemId', () => { + describe('when id is undefined', () => { + it('should not add query', () => { + scope.bySystemId(undefined); + + expect(scope.query).toEqual({}); + }); + }); + + describe('when id is defined', () => { + const setup = () => { + return { + id: new ObjectId().toHexString(), + }; + }; + + it('should add query', () => { + const { id } = setup(); + + scope.bySystemId(id); + + expect(scope.query).toEqual({ externalSource: { system: id } }); + }); + }); + }); + describe('byUserId', () => { describe('when id is undefined', () => { it('should not add query', () => { diff --git a/apps/server/src/modules/group/repo/group.scope.ts b/apps/server/src/modules/group/repo/group.scope.ts index be0c6938aa5..e7e0a5f7b3d 100644 --- a/apps/server/src/modules/group/repo/group.scope.ts +++ b/apps/server/src/modules/group/repo/group.scope.ts @@ -18,6 +18,13 @@ export class GroupScope extends Scope { return this; } + bySystemId(id: EntityId | undefined): this { + if (id) { + this.addQuery({ externalSource: { system: id } }); + } + return this; + } + byUserId(id: EntityId | undefined): this { if (id) { this.addQuery({ users: { user: new ObjectId(id) } }); diff --git a/apps/server/src/modules/group/service/group.service.spec.ts b/apps/server/src/modules/group/service/group.service.spec.ts index 51986e983d0..3acc7dab5ca 100644 --- a/apps/server/src/modules/group/service/group.service.spec.ts +++ b/apps/server/src/modules/group/service/group.service.spec.ts @@ -217,6 +217,48 @@ describe('GroupService', () => { }); }); + describe('findGroupsBySchoolIdAndSystemIdAndGroupType', () => { + describe('when the school has groups of type class', () => { + const setup = () => { + const schoolId: string = new ObjectId().toHexString(); + const systemId: string = new ObjectId().toHexString(); + const groups: Group[] = groupFactory.buildList(3); + + groupRepo.findGroupsBySchoolIdAndSystemIdAndGroupType.mockResolvedValue(groups); + + return { + schoolId, + systemId, + groups, + }; + }; + + it('should search for the groups', async () => { + const { schoolId, systemId } = setup(); + + await service.findGroupsBySchoolIdAndSystemIdAndGroupType(schoolId, systemId, GroupTypes.CLASS); + + expect(groupRepo.findGroupsBySchoolIdAndSystemIdAndGroupType).toHaveBeenCalledWith( + schoolId, + systemId, + GroupTypes.CLASS + ); + }); + + it('should return the groups', async () => { + const { schoolId, systemId, groups } = setup(); + + const result: Group[] = await service.findGroupsBySchoolIdAndSystemIdAndGroupType( + schoolId, + systemId, + GroupTypes.CLASS + ); + + expect(result).toEqual(groups); + }); + }); + }); + describe('save', () => { describe('when saving a group', () => { const setup = () => { diff --git a/apps/server/src/modules/group/service/group.service.ts b/apps/server/src/modules/group/service/group.service.ts index 5ac511f55b2..5dfd41256bb 100644 --- a/apps/server/src/modules/group/service/group.service.ts +++ b/apps/server/src/modules/group/service/group.service.ts @@ -44,6 +44,20 @@ export class GroupService implements AuthorizationLoaderServiceGeneric { return group; } + public async findGroupsBySchoolIdAndSystemIdAndGroupType( + schoolId: EntityId, + systemId: EntityId, + groupType: GroupTypes + ): Promise { + const group: Group[] = await this.groupRepo.findGroupsBySchoolIdAndSystemIdAndGroupType( + schoolId, + systemId, + groupType + ); + + return group; + } + public async save(group: Group): Promise { const savedGroup: Group = await this.groupRepo.save(group); diff --git a/apps/server/src/modules/legacy-school/domain/school-system-options.builder.spec.ts b/apps/server/src/modules/legacy-school/domain/school-system-options.builder.spec.ts index 6c619059440..5cb51923368 100644 --- a/apps/server/src/modules/legacy-school/domain/school-system-options.builder.spec.ts +++ b/apps/server/src/modules/legacy-school/domain/school-system-options.builder.spec.ts @@ -1,11 +1,36 @@ import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; import { ProvisioningOptionsInterface } from '../interface'; -import { ProvisioningStrategyInvalidOptionsLoggableException } from '../loggable'; +import { + ProvisioningStrategyInvalidOptionsLoggableException, + ProvisioningStrategyNoOptionsLoggableException, +} from '../loggable'; import { SchoolSystemOptionsBuilder } from './school-system-options.builder'; import { AnyProvisioningOptions } from './school-system-options.do'; import { SchulConneXProvisioningOptions } from './schulconnex-provisionin-options.do'; describe(SchoolSystemOptionsBuilder.name, () => { + describe('getDefaultProvisioningOptions', () => { + describe('when the provisioning strategy has options', () => { + it('should have the correct options instance', () => { + const builder: SchoolSystemOptionsBuilder = new SchoolSystemOptionsBuilder(SystemProvisioningStrategy.SANIS); + + const result: AnyProvisioningOptions = builder.getDefaultProvisioningOptions(); + + expect(result).toBeInstanceOf(SchulConneXProvisioningOptions); + }); + }); + + describe('when the provisioning strategy has no options', () => { + it('should throw an error', () => { + const builder: SchoolSystemOptionsBuilder = new SchoolSystemOptionsBuilder( + SystemProvisioningStrategy.UNDEFINED + ); + + expect(() => builder.getDefaultProvisioningOptions()).toThrow(ProvisioningStrategyNoOptionsLoggableException); + }); + }); + }); + describe('buildProvisioningOptions', () => { describe('when the provisioning strategy is "SANIS" and the options are valid', () => { const setup = () => { @@ -52,21 +77,5 @@ describe(SchoolSystemOptionsBuilder.name, () => { ).toThrow(ProvisioningStrategyInvalidOptionsLoggableException); }); }); - - describe('when the provisioning strategy has no options', () => { - it('should throw an error', () => { - const builder: SchoolSystemOptionsBuilder = new SchoolSystemOptionsBuilder( - SystemProvisioningStrategy.UNDEFINED - ); - - expect(() => - builder.buildProvisioningOptions({ - groupProvisioningClassesEnabled: true, - groupProvisioningCoursesEnabled: true, - groupProvisioningOtherEnabled: true, - }) - ).toThrow(ProvisioningStrategyInvalidOptionsLoggableException); - }); - }); }); }); diff --git a/apps/server/src/modules/legacy-school/domain/school-system-options.builder.ts b/apps/server/src/modules/legacy-school/domain/school-system-options.builder.ts index f431b27f5ae..8af03d85e3a 100644 --- a/apps/server/src/modules/legacy-school/domain/school-system-options.builder.ts +++ b/apps/server/src/modules/legacy-school/domain/school-system-options.builder.ts @@ -1,22 +1,31 @@ import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; import { ProvisioningOptionsInterface } from '../interface'; -import { ProvisioningStrategyInvalidOptionsLoggableException } from '../loggable'; +import { + ProvisioningStrategyInvalidOptionsLoggableException, + ProvisioningStrategyNoOptionsLoggableException, +} from '../loggable'; import { provisioningStrategyOptions } from './provisioning-strategy-options'; import { AnyProvisioningOptions } from './school-system-options.do'; export class SchoolSystemOptionsBuilder { constructor(private readonly provisioningStrategy: SystemProvisioningStrategy) {} - public buildProvisioningOptions(provisioningOptions: ProvisioningOptionsInterface): AnyProvisioningOptions { + public getDefaultProvisioningOptions(): AnyProvisioningOptions { const ProvisioningOptionsConstructor: (new () => AnyProvisioningOptions) | undefined = provisioningStrategyOptions.get(this.provisioningStrategy); if (!ProvisioningOptionsConstructor) { - throw new ProvisioningStrategyInvalidOptionsLoggableException(this.provisioningStrategy, provisioningOptions); + throw new ProvisioningStrategyNoOptionsLoggableException(this.provisioningStrategy); } const createdProvisioningOptions: AnyProvisioningOptions = new ProvisioningOptionsConstructor(); + return createdProvisioningOptions; + } + + public buildProvisioningOptions(provisioningOptions: ProvisioningOptionsInterface): AnyProvisioningOptions { + const createdProvisioningOptions: AnyProvisioningOptions = this.getDefaultProvisioningOptions(); + if (!createdProvisioningOptions.isApplicable(provisioningOptions)) { throw new ProvisioningStrategyInvalidOptionsLoggableException(this.provisioningStrategy, provisioningOptions); } diff --git a/apps/server/src/modules/legacy-school/legacy-school.module.ts b/apps/server/src/modules/legacy-school/legacy-school.module.ts index 8fc5893bb42..ae9bf34f1a9 100644 --- a/apps/server/src/modules/legacy-school/legacy-school.module.ts +++ b/apps/server/src/modules/legacy-school/legacy-school.module.ts @@ -1,10 +1,12 @@ import { Module } from '@nestjs/common'; import { FederalStateRepo, LegacySchoolRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; +import { GroupModule } from '../group'; import { SchoolSystemOptionsRepo, SchoolYearRepo } from './repo'; import { FederalStateService, LegacySchoolService, + ProvisioningOptionsUpdateService, SchoolSystemOptionsService, SchoolValidationService, SchoolYearService, @@ -14,7 +16,7 @@ import { * @deprecated because it uses the deprecated LegacySchoolDo. */ @Module({ - imports: [LoggerModule], + imports: [LoggerModule, GroupModule], providers: [ LegacySchoolRepo, LegacySchoolService, @@ -25,7 +27,14 @@ import { SchoolValidationService, SchoolSystemOptionsRepo, SchoolSystemOptionsService, + ProvisioningOptionsUpdateService, + ], + exports: [ + LegacySchoolService, + SchoolYearService, + FederalStateService, + SchoolSystemOptionsService, + ProvisioningOptionsUpdateService, ], - exports: [LegacySchoolService, SchoolYearService, FederalStateService, SchoolSystemOptionsService], }) export class LegacySchoolModule {} diff --git a/apps/server/src/modules/legacy-school/loggable/index.ts b/apps/server/src/modules/legacy-school/loggable/index.ts index 5e5deb07997..8288e746eb4 100644 --- a/apps/server/src/modules/legacy-school/loggable/index.ts +++ b/apps/server/src/modules/legacy-school/loggable/index.ts @@ -2,3 +2,4 @@ export { SchoolNumberDuplicateLoggableException } from './school-number-duplicat export { ProvisioningStrategyInvalidOptionsLoggableException } from './provisioning-strategy-invalid-options.loggable-exception'; export { ProvisioningStrategyMissingLoggableException } from './provisioning-strategy-missing.loggable-exception'; export { ProvisioningOptionsInvalidTypeLoggableException } from './provisioning-options-invalid-type.loggable-exception'; +export { ProvisioningStrategyNoOptionsLoggableException } from './provisioning-strategy-no-options.loggable-exception'; diff --git a/apps/server/src/modules/legacy-school/loggable/provisioning-strategy-no-options.loggable-exception.spec.ts b/apps/server/src/modules/legacy-school/loggable/provisioning-strategy-no-options.loggable-exception.spec.ts new file mode 100644 index 00000000000..8be361e1f0b --- /dev/null +++ b/apps/server/src/modules/legacy-school/loggable/provisioning-strategy-no-options.loggable-exception.spec.ts @@ -0,0 +1,21 @@ +import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; +import { ProvisioningStrategyNoOptionsLoggableException } from './provisioning-strategy-no-options.loggable-exception'; + +describe(ProvisioningStrategyNoOptionsLoggableException.name, () => { + describe('getLogMessage', () => { + it('should log the correct message', () => { + const exception = new ProvisioningStrategyNoOptionsLoggableException(SystemProvisioningStrategy.SANIS); + + const result = exception.getLogMessage(); + + expect(result).toEqual({ + type: 'PROVISIONING_STRATEGY_NO_OPTIONS', + message: expect.any(String), + stack: expect.any(String), + data: { + provisioningStrategy: SystemProvisioningStrategy.SANIS, + }, + }); + }); + }); +}); diff --git a/apps/server/src/modules/legacy-school/loggable/provisioning-strategy-no-options.loggable-exception.ts b/apps/server/src/modules/legacy-school/loggable/provisioning-strategy-no-options.loggable-exception.ts new file mode 100644 index 00000000000..69a1726459c --- /dev/null +++ b/apps/server/src/modules/legacy-school/loggable/provisioning-strategy-no-options.loggable-exception.ts @@ -0,0 +1,20 @@ +import { UnprocessableEntityException } from '@nestjs/common'; +import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; +import { ErrorLogMessage, Loggable, LogMessage, ValidationErrorLogMessage } from '@src/core/logger'; + +export class ProvisioningStrategyNoOptionsLoggableException extends UnprocessableEntityException implements Loggable { + constructor(private readonly provisioningStrategy: SystemProvisioningStrategy) { + super(); + } + + getLogMessage(): LogMessage | ErrorLogMessage | ValidationErrorLogMessage { + return { + type: 'PROVISIONING_STRATEGY_NO_OPTIONS', + message: 'The provisioning strategy does not support options.', + stack: this.stack, + data: { + provisioningStrategy: this.provisioningStrategy, + }, + }; + } +} diff --git a/apps/server/src/modules/legacy-school/service/index.ts b/apps/server/src/modules/legacy-school/service/index.ts index 65307ab17f0..b718a5c81b8 100644 --- a/apps/server/src/modules/legacy-school/service/index.ts +++ b/apps/server/src/modules/legacy-school/service/index.ts @@ -3,3 +3,4 @@ export * from './school-year.service'; export * from './federal-state.service'; export * from './validation'; export { SchoolSystemOptionsService } from './school-system-options.service'; +export { ProvisioningOptionsUpdateService } from './provisioning-options-update-service'; diff --git a/apps/server/src/modules/legacy-school/service/provisioning-options-update-service.spec.ts b/apps/server/src/modules/legacy-school/service/provisioning-options-update-service.spec.ts new file mode 100644 index 00000000000..0cdd95e3aed --- /dev/null +++ b/apps/server/src/modules/legacy-school/service/provisioning-options-update-service.spec.ts @@ -0,0 +1,209 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { Group, GroupService, GroupTypes } from '@modules/group'; +import { Test, TestingModule } from '@nestjs/testing'; +import { groupFactory, schoolSystemOptionsFactory } from '@shared/testing'; +import { SchoolSystemOptions, SchulConneXProvisioningOptions } from '../domain'; +import { ProvisioningOptionsUpdateService } from './provisioning-options-update-service'; + +describe(ProvisioningOptionsUpdateService.name, () => { + let module: TestingModule; + let service: ProvisioningOptionsUpdateService; + + let groupService: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + ProvisioningOptionsUpdateService, + { + provide: GroupService, + useValue: createMock(), + }, + ], + }).compile(); + + service = module.get(ProvisioningOptionsUpdateService); + groupService = module.get(GroupService); + }); + + afterAll(async () => { + await module.close(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('handleActions', () => { + describe('when groupProvisioningClassesEnabled gets turned off', () => { + const setup = () => { + const schoolSystemOptions: SchoolSystemOptions = schoolSystemOptionsFactory.build({ + provisioningOptions: new SchulConneXProvisioningOptions().set({ + groupProvisioningClassesEnabled: true, + groupProvisioningOtherEnabled: true, + groupProvisioningCoursesEnabled: true, + }), + }); + const newProvisioningOptions = new SchulConneXProvisioningOptions().set({ + groupProvisioningClassesEnabled: false, + groupProvisioningOtherEnabled: true, + groupProvisioningCoursesEnabled: true, + }); + const group: Group = groupFactory.build({ type: GroupTypes.CLASS }); + + groupService.findGroupsBySchoolIdAndSystemIdAndGroupType.mockResolvedValueOnce([group]); + + return { + schoolSystemOptions, + newProvisioningOptions, + group, + }; + }; + + it('should search for all classes of the school for the system', async () => { + const { schoolSystemOptions, newProvisioningOptions } = setup(); + + await service.handleActions( + schoolSystemOptions.schoolId, + schoolSystemOptions.systemId, + newProvisioningOptions, + schoolSystemOptions.provisioningOptions + ); + + expect(groupService.findGroupsBySchoolIdAndSystemIdAndGroupType).toHaveBeenCalledWith( + schoolSystemOptions.schoolId, + schoolSystemOptions.systemId, + GroupTypes.CLASS + ); + }); + + it('should delete all classes', async () => { + const { schoolSystemOptions, newProvisioningOptions, group } = setup(); + + await service.handleActions( + schoolSystemOptions.schoolId, + schoolSystemOptions.systemId, + newProvisioningOptions, + schoolSystemOptions.provisioningOptions + ); + + expect(groupService.delete).toHaveBeenCalledTimes(1); + expect(groupService.delete).toHaveBeenCalledWith(group); + }); + }); + + describe('when groupProvisioningCoursesEnabled gets turned off', () => { + const setup = () => { + const schoolSystemOptions: SchoolSystemOptions = schoolSystemOptionsFactory.build({ + provisioningOptions: new SchulConneXProvisioningOptions().set({ + groupProvisioningClassesEnabled: true, + groupProvisioningOtherEnabled: true, + groupProvisioningCoursesEnabled: true, + }), + }); + const newProvisioningOptions = new SchulConneXProvisioningOptions().set({ + groupProvisioningClassesEnabled: true, + groupProvisioningOtherEnabled: true, + groupProvisioningCoursesEnabled: false, + }); + const group: Group = groupFactory.build({ type: GroupTypes.COURSE }); + + groupService.findGroupsBySchoolIdAndSystemIdAndGroupType.mockResolvedValueOnce([group]); + + return { + schoolSystemOptions, + newProvisioningOptions, + group, + }; + }; + + it('should search for all courses of the school for the system', async () => { + const { schoolSystemOptions, newProvisioningOptions } = setup(); + + await service.handleActions( + schoolSystemOptions.schoolId, + schoolSystemOptions.systemId, + newProvisioningOptions, + schoolSystemOptions.provisioningOptions + ); + + expect(groupService.findGroupsBySchoolIdAndSystemIdAndGroupType).toHaveBeenCalledWith( + schoolSystemOptions.schoolId, + schoolSystemOptions.systemId, + GroupTypes.COURSE + ); + }); + + it('should delete all courses', async () => { + const { schoolSystemOptions, newProvisioningOptions, group } = setup(); + + await service.handleActions( + schoolSystemOptions.schoolId, + schoolSystemOptions.systemId, + newProvisioningOptions, + schoolSystemOptions.provisioningOptions + ); + + expect(groupService.delete).toHaveBeenCalledTimes(1); + expect(groupService.delete).toHaveBeenCalledWith(group); + }); + }); + + describe('when groupProvisioningOtherEnabled gets turned off', () => { + const setup = () => { + const schoolSystemOptions: SchoolSystemOptions = schoolSystemOptionsFactory.build({ + provisioningOptions: new SchulConneXProvisioningOptions().set({ + groupProvisioningClassesEnabled: true, + groupProvisioningOtherEnabled: true, + groupProvisioningCoursesEnabled: true, + }), + }); + const newProvisioningOptions = new SchulConneXProvisioningOptions().set({ + groupProvisioningClassesEnabled: true, + groupProvisioningOtherEnabled: false, + groupProvisioningCoursesEnabled: true, + }); + const group: Group = groupFactory.build({ type: GroupTypes.OTHER }); + + groupService.findGroupsBySchoolIdAndSystemIdAndGroupType.mockResolvedValueOnce([group]); + + return { + schoolSystemOptions, + newProvisioningOptions, + group, + }; + }; + + it('should search for all other groups of the school for the system', async () => { + const { schoolSystemOptions, newProvisioningOptions } = setup(); + + await service.handleActions( + schoolSystemOptions.schoolId, + schoolSystemOptions.systemId, + newProvisioningOptions, + schoolSystemOptions.provisioningOptions + ); + + expect(groupService.findGroupsBySchoolIdAndSystemIdAndGroupType).toHaveBeenCalledWith( + schoolSystemOptions.schoolId, + schoolSystemOptions.systemId, + GroupTypes.OTHER + ); + }); + + it('should delete all other groups', async () => { + const { schoolSystemOptions, newProvisioningOptions, group } = setup(); + + await service.handleActions( + schoolSystemOptions.schoolId, + schoolSystemOptions.systemId, + newProvisioningOptions, + schoolSystemOptions.provisioningOptions + ); + + expect(groupService.delete).toHaveBeenCalledTimes(1); + expect(groupService.delete).toHaveBeenCalledWith(group); + }); + }); + }); +}); diff --git a/apps/server/src/modules/legacy-school/service/provisioning-options-update-service.ts b/apps/server/src/modules/legacy-school/service/provisioning-options-update-service.ts new file mode 100644 index 00000000000..6a49ee3fbc5 --- /dev/null +++ b/apps/server/src/modules/legacy-school/service/provisioning-options-update-service.ts @@ -0,0 +1,42 @@ +import { Group, GroupService, GroupTypes } from '@modules/group'; +import { Injectable } from '@nestjs/common'; +import { EntityId } from '@shared/domain/types'; +import { AnyProvisioningOptions } from '../domain'; + +@Injectable() +export class ProvisioningOptionsUpdateService { + constructor(private readonly groupService: GroupService) {} + + public async handleActions( + schoolId: EntityId, + systemId: EntityId, + newOptions: T, + oldOptions: T + ): Promise { + if (oldOptions.groupProvisioningClassesEnabled && !newOptions.groupProvisioningClassesEnabled) { + await this.deleteGroups(schoolId, systemId, GroupTypes.CLASS); + } + + if (oldOptions.groupProvisioningCoursesEnabled && !newOptions.groupProvisioningCoursesEnabled) { + await this.deleteGroups(schoolId, systemId, GroupTypes.COURSE); + } + + if (oldOptions.groupProvisioningOtherEnabled && !newOptions.groupProvisioningOtherEnabled) { + await this.deleteGroups(schoolId, systemId, GroupTypes.OTHER); + } + } + + private async deleteGroups(schoolId: EntityId, systemId: EntityId, groupType: GroupTypes): Promise { + const groups: Group[] = await this.groupService.findGroupsBySchoolIdAndSystemIdAndGroupType( + schoolId, + systemId, + groupType + ); + + await Promise.all( + groups.map(async (group: Group): Promise => { + await this.groupService.delete(group); + }) + ); + } +} diff --git a/apps/server/src/modules/legacy-school/uc/school-system-options.uc.spec.ts b/apps/server/src/modules/legacy-school/uc/school-system-options.uc.spec.ts index 21312bc9988..3ab7505d567 100644 --- a/apps/server/src/modules/legacy-school/uc/school-system-options.uc.spec.ts +++ b/apps/server/src/modules/legacy-school/uc/school-system-options.uc.spec.ts @@ -7,9 +7,9 @@ import { NotFoundLoggableException } from '@shared/common/loggable-exception'; import { Permission } from '@shared/domain/interface'; import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; import { schoolSystemOptionsFactory, setupEntities, systemFactory, userFactory } from '@shared/testing'; -import { AnyProvisioningOptions, SchoolSystemOptions } from '../domain'; +import { AnyProvisioningOptions, SchoolSystemOptions, SchulConneXProvisioningOptions } from '../domain'; import { ProvisioningStrategyMissingLoggableException } from '../loggable'; -import { SchoolSystemOptionsService } from '../service'; +import { ProvisioningOptionsUpdateService, SchoolSystemOptionsService } from '../service'; import { SchoolSystemOptionsUc } from './school-system-options.uc'; describe(SchoolSystemOptionsUc.name, () => { @@ -19,6 +19,7 @@ describe(SchoolSystemOptionsUc.name, () => { let authorizationService: DeepMocked; let systemService: DeepMocked; let schoolSystemOptionsService: DeepMocked; + let provisioningOptionsUpdateService: DeepMocked; beforeAll(async () => { await setupEntities(); @@ -38,6 +39,10 @@ describe(SchoolSystemOptionsUc.name, () => { provide: SchoolSystemOptionsService, useValue: createMock(), }, + { + provide: ProvisioningOptionsUpdateService, + useValue: createMock(), + }, ], }).compile(); @@ -45,6 +50,7 @@ describe(SchoolSystemOptionsUc.name, () => { authorizationService = module.get(AuthorizationService); systemService = module.get(SystemService); schoolSystemOptionsService = module.get(SchoolSystemOptionsService); + provisioningOptionsUpdateService = module.get(ProvisioningOptionsUpdateService); }); afterAll(async () => { @@ -124,6 +130,11 @@ describe(SchoolSystemOptionsUc.name, () => { const system: System = systemFactory.build({ provisioningStrategy: SystemProvisioningStrategy.SANIS }); const schoolSystemOptions: SchoolSystemOptions = schoolSystemOptionsFactory.build({ systemId: system.id, + provisioningOptions: new SchulConneXProvisioningOptions().set({ + groupProvisioningClassesEnabled: true, + groupProvisioningCoursesEnabled: true, + groupProvisioningOtherEnabled: true, + }), }); systemService.findById.mockResolvedValueOnce(system); @@ -154,6 +165,24 @@ describe(SchoolSystemOptionsUc.name, () => { ); }); + it('should execute additional update actions', async () => { + const { user, schoolSystemOptions } = setup(); + + await uc.createOrUpdateProvisioningOptions( + user.id, + schoolSystemOptions.schoolId, + schoolSystemOptions.systemId, + schoolSystemOptions.provisioningOptions + ); + + expect(provisioningOptionsUpdateService.handleActions).toHaveBeenCalledWith( + schoolSystemOptions.schoolId, + schoolSystemOptions.systemId, + schoolSystemOptions.provisioningOptions, + new SchulConneXProvisioningOptions() + ); + }); + it('should save the options', async () => { const { user, schoolSystemOptions } = setup(); @@ -189,60 +218,93 @@ describe(SchoolSystemOptionsUc.name, () => { const system: System = systemFactory.build({ provisioningStrategy: SystemProvisioningStrategy.SANIS }); const schoolSystemOptions: SchoolSystemOptions = schoolSystemOptionsFactory.build({ systemId: system.id, + provisioningOptions: new SchulConneXProvisioningOptions().set({ + groupProvisioningClassesEnabled: false, + groupProvisioningCoursesEnabled: false, + groupProvisioningOtherEnabled: false, + }), + }); + const newOptions: AnyProvisioningOptions = new SchulConneXProvisioningOptions().set({ + groupProvisioningClassesEnabled: true, + groupProvisioningCoursesEnabled: true, + groupProvisioningOtherEnabled: true, }); systemService.findById.mockResolvedValueOnce(system); schoolSystemOptionsService.findBySchoolIdAndSystemId.mockResolvedValueOnce(schoolSystemOptions); authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); - schoolSystemOptionsService.save.mockResolvedValueOnce(schoolSystemOptions); + schoolSystemOptionsService.save.mockResolvedValueOnce( + new SchoolSystemOptions({ ...schoolSystemOptions.getProps(), provisioningOptions: newOptions }) + ); return { user, schoolSystemOptions, + newOptions, }; }; it('should check the permissions', async () => { - const { user, schoolSystemOptions } = setup(); + const { user, schoolSystemOptions, newOptions } = setup(); await uc.createOrUpdateProvisioningOptions( user.id, schoolSystemOptions.schoolId, schoolSystemOptions.systemId, - schoolSystemOptions.provisioningOptions + newOptions ); expect(authorizationService.checkPermission).toHaveBeenCalledWith( user, - schoolSystemOptions, + new SchoolSystemOptions({ ...schoolSystemOptions.getProps(), provisioningOptions: newOptions }), AuthorizationContextBuilder.read([Permission.SCHOOL_SYSTEM_EDIT]) ); }); - it('should save the options', async () => { - const { user, schoolSystemOptions } = setup(); + it('should execute additional update actions', async () => { + const { user, schoolSystemOptions, newOptions } = setup(); await uc.createOrUpdateProvisioningOptions( user.id, schoolSystemOptions.schoolId, schoolSystemOptions.systemId, + newOptions + ); + + expect(provisioningOptionsUpdateService.handleActions).toHaveBeenCalledWith( + schoolSystemOptions.schoolId, + schoolSystemOptions.systemId, + newOptions, schoolSystemOptions.provisioningOptions ); + }); - expect(schoolSystemOptionsService.save).toHaveBeenCalledWith(schoolSystemOptions); + it('should save the options', async () => { + const { user, schoolSystemOptions, newOptions } = setup(); + + await uc.createOrUpdateProvisioningOptions( + user.id, + schoolSystemOptions.schoolId, + schoolSystemOptions.systemId, + newOptions + ); + + expect(schoolSystemOptionsService.save).toHaveBeenCalledWith( + new SchoolSystemOptions({ ...schoolSystemOptions.getProps(), provisioningOptions: newOptions }) + ); }); it('should return the options', async () => { - const { user, schoolSystemOptions } = setup(); + const { user, schoolSystemOptions, newOptions } = setup(); const result: AnyProvisioningOptions = await uc.createOrUpdateProvisioningOptions( user.id, schoolSystemOptions.schoolId, schoolSystemOptions.systemId, - schoolSystemOptions.provisioningOptions + newOptions ); - expect(result).toEqual(schoolSystemOptions.provisioningOptions); + expect(result).toEqual(newOptions); }); }); diff --git a/apps/server/src/modules/legacy-school/uc/school-system-options.uc.ts b/apps/server/src/modules/legacy-school/uc/school-system-options.uc.ts index ad4e0d1da59..62926d43a6c 100644 --- a/apps/server/src/modules/legacy-school/uc/school-system-options.uc.ts +++ b/apps/server/src/modules/legacy-school/uc/school-system-options.uc.ts @@ -8,14 +8,15 @@ import { EntityId } from '@shared/domain/types'; import { AnyProvisioningOptions, SchoolSystemOptions, SchoolSystemOptionsBuilder } from '../domain'; import { ProvisioningOptionsInterface } from '../interface'; import { ProvisioningStrategyMissingLoggableException } from '../loggable'; -import { SchoolSystemOptionsService } from '../service'; +import { ProvisioningOptionsUpdateService, SchoolSystemOptionsService } from '../service'; @Injectable() export class SchoolSystemOptionsUc { constructor( private readonly authorizationService: AuthorizationService, private readonly systemService: SystemService, - private readonly schoolSystemOptionsService: SchoolSystemOptionsService + private readonly schoolSystemOptionsService: SchoolSystemOptionsService, + private readonly provisioningOptionsUpdateService: ProvisioningOptionsUpdateService ) {} public async getProvisioningOptions( @@ -56,9 +57,12 @@ export class SchoolSystemOptionsUc { throw new ProvisioningStrategyMissingLoggableException(systemId); } - const provisioningOptions: AnyProvisioningOptions = new SchoolSystemOptionsBuilder( + const schoolSystemOptionsBuilder: SchoolSystemOptionsBuilder = new SchoolSystemOptionsBuilder( system.provisioningStrategy - ).buildProvisioningOptions(requestedProvisioningOptions); + ); + + const newProvisioningOptions: AnyProvisioningOptions = + schoolSystemOptionsBuilder.buildProvisioningOptions(requestedProvisioningOptions); const existingSchoolSystemOptions: SchoolSystemOptions | null = await this.schoolSystemOptionsService.findBySchoolIdAndSystemId(schoolId, systemId); @@ -67,7 +71,7 @@ export class SchoolSystemOptionsUc { id: existingSchoolSystemOptions?.id ?? new ObjectId().toHexString(), systemId, schoolId, - provisioningOptions, + provisioningOptions: newProvisioningOptions, }); const user = await this.authorizationService.getUserWithPermissions(userId); @@ -77,6 +81,15 @@ export class SchoolSystemOptionsUc { AuthorizationContextBuilder.read([Permission.SCHOOL_SYSTEM_EDIT]) ); + const currentProvisioningOptions: AnyProvisioningOptions = + existingSchoolSystemOptions?.provisioningOptions ?? schoolSystemOptionsBuilder.getDefaultProvisioningOptions(); + await this.provisioningOptionsUpdateService.handleActions( + schoolId, + systemId, + newProvisioningOptions, + currentProvisioningOptions + ); + const savedSchoolSystemOptions: SchoolSystemOptions = await this.schoolSystemOptionsService.save( schoolSystemOptions ); diff --git a/apps/server/src/modules/provisioning/config/provisioning-config.ts b/apps/server/src/modules/provisioning/config/provisioning-config.ts index 4f6d83fc19d..b36e52ea050 100644 --- a/apps/server/src/modules/provisioning/config/provisioning-config.ts +++ b/apps/server/src/modules/provisioning/config/provisioning-config.ts @@ -4,12 +4,10 @@ export const ProvisioningFeatures = Symbol('ProvisioningFeatures'); export interface IProvisioningFeatures { schulconnexGroupProvisioningEnabled: boolean; - provisioningOptionsEnabled: boolean; } export class ProvisioningConfiguration { static provisioningFeatures: IProvisioningFeatures = { schulconnexGroupProvisioningEnabled: Configuration.get('FEATURE_SANIS_GROUP_PROVISIONING_ENABLED') as boolean, - provisioningOptionsEnabled: Configuration.get('FEATURE_PROVISIONING_OPTIONS_ENABLED') as boolean, }; } diff --git a/apps/server/src/modules/provisioning/strategy/oidc/oidc.strategy.spec.ts b/apps/server/src/modules/provisioning/strategy/oidc/oidc.strategy.spec.ts index 80c078b52dd..7b6722f735c 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/oidc.strategy.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/oidc.strategy.spec.ts @@ -65,7 +65,6 @@ describe('OidcStrategy', () => { beforeEach(() => { Object.assign>(provisioningFeatures, { schulconnexGroupProvisioningEnabled: false, - provisioningOptionsEnabled: false, }); }); @@ -194,7 +193,6 @@ describe('OidcStrategy', () => { describe('when group data is provided and the feature is enabled', () => { const setup = () => { provisioningFeatures.schulconnexGroupProvisioningEnabled = true; - provisioningFeatures.provisioningOptionsEnabled = true; const externalUserId = 'externalUserId'; const externalGroups: ExternalGroupDto[] = externalGroupDtoFactory.buildList(2); diff --git a/apps/server/src/modules/provisioning/strategy/oidc/oidc.strategy.ts b/apps/server/src/modules/provisioning/strategy/oidc/oidc.strategy.ts index 6fe09cd357c..e1e2f33e2f1 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/oidc.strategy.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/oidc.strategy.ts @@ -36,9 +36,7 @@ export abstract class OidcProvisioningStrategy extends ProvisioningStrategy { if (data.externalGroups) { let groups: ExternalGroupDto[] = data.externalGroups; - if (this.provisioningFeatures.provisioningOptionsEnabled) { - groups = await this.oidcProvisioningService.filterExternalGroups(groups, school?.id, data.system.systemId); - } + groups = await this.oidcProvisioningService.filterExternalGroups(groups, school?.id, data.system.systemId); await Promise.all( groups.map((group: ExternalGroupDto) => diff --git a/config/default.schema.json b/config/default.schema.json index 0104563c2d2..9774124f911 100644 --- a/config/default.schema.json +++ b/config/default.schema.json @@ -1369,11 +1369,6 @@ "default": false, "description": "Enables external tools on the column board" }, - "FEATURE_PROVISIONING_OPTIONS_ENABLED": { - "type": "boolean", - "default": false, - "description": "enables to view the page to set group provisioning options for a school and system pair" - }, "CTL_TOOLS": { "type": "object", "description": "CTL Tools properties", diff --git a/src/services/config/publicAppConfigService.js b/src/services/config/publicAppConfigService.js index 0b429a46048..cdf6a815a25 100644 --- a/src/services/config/publicAppConfigService.js +++ b/src/services/config/publicAppConfigService.js @@ -65,7 +65,6 @@ const exposedVars = [ 'FEATURE_CTL_CONTEXT_CONFIGURATION_ENABLED', 'FEATURE_SHOW_NEW_CLASS_VIEW_ENABLED', 'FEATURE_TLDRAW_ENABLED', - 'FEATURE_PROVISIONING_OPTIONS_ENABLED', ]; /** From 292b2b9e695c7a5eec40a25144975a38e6cfb3ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= Date: Tue, 9 Jan 2024 10:49:27 +0100 Subject: [PATCH 2/7] add strategy pattern for update actions --- .../domain/base-provisioning-options.ts | 5 +- .../src/modules/legacy-school/domain/index.ts | 1 + .../domain/provisioning-options-type.ts | 3 + .../schulconnex-provisionin-options.do.ts | 5 ++ .../modules/legacy-school/service/index.ts | 3 +- .../provisioning-options-update-handler.ts | 6 ++ ...rovisioning-options-update.service.spec.ts | 66 +++++++++++++++++++ .../provisioning-options-update.service.ts | 29 ++++++++ ...ovisioning-options-update.service.spec.ts} | 24 +++---- ...ex-provisioning-options-update.service.ts} | 13 ++-- .../uc/school-system-options.uc.spec.ts | 4 +- .../uc/school-system-options.uc.ts | 2 +- 12 files changed, 139 insertions(+), 22 deletions(-) create mode 100644 apps/server/src/modules/legacy-school/domain/provisioning-options-type.ts create mode 100644 apps/server/src/modules/legacy-school/service/provisioning-options-update-handler.ts create mode 100644 apps/server/src/modules/legacy-school/service/provisioning-options-update.service.spec.ts create mode 100644 apps/server/src/modules/legacy-school/service/provisioning-options-update.service.ts rename apps/server/src/modules/legacy-school/service/{provisioning-options-update-service.spec.ts => schulconnex-provisioning-options-update.service.spec.ts} (91%) rename apps/server/src/modules/legacy-school/service/{provisioning-options-update-service.ts => schulconnex-provisioning-options-update.service.ts} (74%) diff --git a/apps/server/src/modules/legacy-school/domain/base-provisioning-options.ts b/apps/server/src/modules/legacy-school/domain/base-provisioning-options.ts index 3676ce72ee2..80c059c79f8 100644 --- a/apps/server/src/modules/legacy-school/domain/base-provisioning-options.ts +++ b/apps/server/src/modules/legacy-school/domain/base-provisioning-options.ts @@ -1,4 +1,5 @@ import { ProvisioningOptionsInterface } from '../interface'; +import { ProvisioningOptionsType } from './provisioning-options-type'; export abstract class BaseProvisioningOptions { public isApplicable(provisioningOptions: ProvisioningOptionsInterface): provisioningOptions is T { @@ -11,5 +12,7 @@ export abstract class BaseProvisioningOptions @@ -11,6 +12,10 @@ export class SchulConneXProvisioningOptions groupProvisioningOtherEnabled = false; + get getType(): ProvisioningOptionsType { + return ProvisioningOptionsType.SCHULCONNEX; + } + set(props: SchulConneXProvisioningOptionsInterface): this { this.groupProvisioningClassesEnabled = props.groupProvisioningClassesEnabled; this.groupProvisioningCoursesEnabled = props.groupProvisioningCoursesEnabled; diff --git a/apps/server/src/modules/legacy-school/service/index.ts b/apps/server/src/modules/legacy-school/service/index.ts index b718a5c81b8..3df9f66017d 100644 --- a/apps/server/src/modules/legacy-school/service/index.ts +++ b/apps/server/src/modules/legacy-school/service/index.ts @@ -3,4 +3,5 @@ export * from './school-year.service'; export * from './federal-state.service'; export * from './validation'; export { SchoolSystemOptionsService } from './school-system-options.service'; -export { ProvisioningOptionsUpdateService } from './provisioning-options-update-service'; +export { ProvisioningOptionsUpdateService } from './provisioning-options-update.service'; +export { SchulconnexProvisioningOptionsUpdateService } from './schulconnex-provisioning-options-update.service'; diff --git a/apps/server/src/modules/legacy-school/service/provisioning-options-update-handler.ts b/apps/server/src/modules/legacy-school/service/provisioning-options-update-handler.ts new file mode 100644 index 00000000000..1a13b81b7dc --- /dev/null +++ b/apps/server/src/modules/legacy-school/service/provisioning-options-update-handler.ts @@ -0,0 +1,6 @@ +import { EntityId } from '@shared/domain/types'; +import { AnyProvisioningOptions } from '../domain'; + +export interface ProvisioningOptionsUpdateHandler { + handleUpdate(schoolId: EntityId, systemId: EntityId, newOptions: T, oldOptions: T): Promise; +} diff --git a/apps/server/src/modules/legacy-school/service/provisioning-options-update.service.spec.ts b/apps/server/src/modules/legacy-school/service/provisioning-options-update.service.spec.ts new file mode 100644 index 00000000000..45092fbd9c3 --- /dev/null +++ b/apps/server/src/modules/legacy-school/service/provisioning-options-update.service.spec.ts @@ -0,0 +1,66 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { Test, TestingModule } from '@nestjs/testing'; +import { EntityId } from '@shared/domain/types'; +import { SchulConneXProvisioningOptions } from '../domain'; +import { ProvisioningOptionsUpdateService } from './provisioning-options-update.service'; +import { SchulconnexProvisioningOptionsUpdateService } from './schulconnex-provisioning-options-update.service'; + +describe(ProvisioningOptionsUpdateService.name, () => { + let module: TestingModule; + let service: ProvisioningOptionsUpdateService; + + let schulconnexProvisioningOptionsUpdateService: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + ProvisioningOptionsUpdateService, + { + provide: SchulconnexProvisioningOptionsUpdateService, + useValue: createMock(), + }, + ], + }).compile(); + + service = module.get(ProvisioningOptionsUpdateService); + schulconnexProvisioningOptionsUpdateService = module.get(SchulconnexProvisioningOptionsUpdateService); + }); + + afterAll(async () => { + await module.close(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('handleUpdate', () => { + describe('when the options are of type schulconnex', () => { + const setup = () => { + const schoolId: EntityId = new ObjectId().toHexString(); + const systemId: EntityId = new ObjectId().toHexString(); + const provisioningOptions: SchulConneXProvisioningOptions = new SchulConneXProvisioningOptions(); + + return { + schoolId, + systemId, + provisioningOptions, + }; + }; + + it('should execute the schulconnex service', async () => { + const { schoolId, systemId, provisioningOptions } = setup(); + + await service.handleUpdate(schoolId, systemId, provisioningOptions, provisioningOptions); + + expect(schulconnexProvisioningOptionsUpdateService.handleUpdate).toHaveBeenCalledWith( + schoolId, + systemId, + provisioningOptions, + provisioningOptions + ); + }); + }); + }); +}); diff --git a/apps/server/src/modules/legacy-school/service/provisioning-options-update.service.ts b/apps/server/src/modules/legacy-school/service/provisioning-options-update.service.ts new file mode 100644 index 00000000000..7bfa12ac81f --- /dev/null +++ b/apps/server/src/modules/legacy-school/service/provisioning-options-update.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { EntityId } from '@shared/domain/types'; +import { AnyProvisioningOptions, ProvisioningOptionsType } from '../domain'; +import { ProvisioningOptionsUpdateHandler } from './provisioning-options-update-handler'; +import { SchulconnexProvisioningOptionsUpdateService } from './schulconnex-provisioning-options-update.service'; + +@Injectable() +export class ProvisioningOptionsUpdateService { + private readonly updateServices = new Map(); + + constructor( + private readonly schulconnexProvisioningOptionsUpdateService: SchulconnexProvisioningOptionsUpdateService + ) { + this.updateServices.set(ProvisioningOptionsType.SCHULCONNEX, this.schulconnexProvisioningOptionsUpdateService); + } + + public async handleUpdate( + schoolId: EntityId, + systemId: EntityId, + newOptions: AnyProvisioningOptions, + oldOptions: AnyProvisioningOptions + ): Promise { + const updater: ProvisioningOptionsUpdateHandler | undefined = this.updateServices.get(oldOptions.getType); + + if (updater) { + await updater.handleUpdate(schoolId, systemId, newOptions, oldOptions); + } + } +} diff --git a/apps/server/src/modules/legacy-school/service/provisioning-options-update-service.spec.ts b/apps/server/src/modules/legacy-school/service/schulconnex-provisioning-options-update.service.spec.ts similarity index 91% rename from apps/server/src/modules/legacy-school/service/provisioning-options-update-service.spec.ts rename to apps/server/src/modules/legacy-school/service/schulconnex-provisioning-options-update.service.spec.ts index 0cdd95e3aed..57f2c9387a4 100644 --- a/apps/server/src/modules/legacy-school/service/provisioning-options-update-service.spec.ts +++ b/apps/server/src/modules/legacy-school/service/schulconnex-provisioning-options-update.service.spec.ts @@ -3,18 +3,18 @@ import { Group, GroupService, GroupTypes } from '@modules/group'; import { Test, TestingModule } from '@nestjs/testing'; import { groupFactory, schoolSystemOptionsFactory } from '@shared/testing'; import { SchoolSystemOptions, SchulConneXProvisioningOptions } from '../domain'; -import { ProvisioningOptionsUpdateService } from './provisioning-options-update-service'; +import { SchulconnexProvisioningOptionsUpdateService } from './schulconnex-provisioning-options-update.service'; -describe(ProvisioningOptionsUpdateService.name, () => { +describe(SchulconnexProvisioningOptionsUpdateService.name, () => { let module: TestingModule; - let service: ProvisioningOptionsUpdateService; + let service: SchulconnexProvisioningOptionsUpdateService; let groupService: DeepMocked; beforeAll(async () => { module = await Test.createTestingModule({ providers: [ - ProvisioningOptionsUpdateService, + SchulconnexProvisioningOptionsUpdateService, { provide: GroupService, useValue: createMock(), @@ -22,7 +22,7 @@ describe(ProvisioningOptionsUpdateService.name, () => { ], }).compile(); - service = module.get(ProvisioningOptionsUpdateService); + service = module.get(SchulconnexProvisioningOptionsUpdateService); groupService = module.get(GroupService); }); @@ -34,7 +34,7 @@ describe(ProvisioningOptionsUpdateService.name, () => { jest.resetAllMocks(); }); - describe('handleActions', () => { + describe('handleUpdate', () => { describe('when groupProvisioningClassesEnabled gets turned off', () => { const setup = () => { const schoolSystemOptions: SchoolSystemOptions = schoolSystemOptionsFactory.build({ @@ -63,7 +63,7 @@ describe(ProvisioningOptionsUpdateService.name, () => { it('should search for all classes of the school for the system', async () => { const { schoolSystemOptions, newProvisioningOptions } = setup(); - await service.handleActions( + await service.handleUpdate( schoolSystemOptions.schoolId, schoolSystemOptions.systemId, newProvisioningOptions, @@ -80,7 +80,7 @@ describe(ProvisioningOptionsUpdateService.name, () => { it('should delete all classes', async () => { const { schoolSystemOptions, newProvisioningOptions, group } = setup(); - await service.handleActions( + await service.handleUpdate( schoolSystemOptions.schoolId, schoolSystemOptions.systemId, newProvisioningOptions, @@ -120,7 +120,7 @@ describe(ProvisioningOptionsUpdateService.name, () => { it('should search for all courses of the school for the system', async () => { const { schoolSystemOptions, newProvisioningOptions } = setup(); - await service.handleActions( + await service.handleUpdate( schoolSystemOptions.schoolId, schoolSystemOptions.systemId, newProvisioningOptions, @@ -137,7 +137,7 @@ describe(ProvisioningOptionsUpdateService.name, () => { it('should delete all courses', async () => { const { schoolSystemOptions, newProvisioningOptions, group } = setup(); - await service.handleActions( + await service.handleUpdate( schoolSystemOptions.schoolId, schoolSystemOptions.systemId, newProvisioningOptions, @@ -177,7 +177,7 @@ describe(ProvisioningOptionsUpdateService.name, () => { it('should search for all other groups of the school for the system', async () => { const { schoolSystemOptions, newProvisioningOptions } = setup(); - await service.handleActions( + await service.handleUpdate( schoolSystemOptions.schoolId, schoolSystemOptions.systemId, newProvisioningOptions, @@ -194,7 +194,7 @@ describe(ProvisioningOptionsUpdateService.name, () => { it('should delete all other groups', async () => { const { schoolSystemOptions, newProvisioningOptions, group } = setup(); - await service.handleActions( + await service.handleUpdate( schoolSystemOptions.schoolId, schoolSystemOptions.systemId, newProvisioningOptions, diff --git a/apps/server/src/modules/legacy-school/service/provisioning-options-update-service.ts b/apps/server/src/modules/legacy-school/service/schulconnex-provisioning-options-update.service.ts similarity index 74% rename from apps/server/src/modules/legacy-school/service/provisioning-options-update-service.ts rename to apps/server/src/modules/legacy-school/service/schulconnex-provisioning-options-update.service.ts index 6a49ee3fbc5..f6494fdd951 100644 --- a/apps/server/src/modules/legacy-school/service/provisioning-options-update-service.ts +++ b/apps/server/src/modules/legacy-school/service/schulconnex-provisioning-options-update.service.ts @@ -1,17 +1,20 @@ import { Group, GroupService, GroupTypes } from '@modules/group'; import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain/types'; -import { AnyProvisioningOptions } from '../domain'; +import { SchulConneXProvisioningOptions } from '../domain'; +import { ProvisioningOptionsUpdateHandler } from './provisioning-options-update-handler'; @Injectable() -export class ProvisioningOptionsUpdateService { +export class SchulconnexProvisioningOptionsUpdateService + implements ProvisioningOptionsUpdateHandler +{ constructor(private readonly groupService: GroupService) {} - public async handleActions( + public async handleUpdate( schoolId: EntityId, systemId: EntityId, - newOptions: T, - oldOptions: T + newOptions: SchulConneXProvisioningOptions, + oldOptions: SchulConneXProvisioningOptions ): Promise { if (oldOptions.groupProvisioningClassesEnabled && !newOptions.groupProvisioningClassesEnabled) { await this.deleteGroups(schoolId, systemId, GroupTypes.CLASS); diff --git a/apps/server/src/modules/legacy-school/uc/school-system-options.uc.spec.ts b/apps/server/src/modules/legacy-school/uc/school-system-options.uc.spec.ts index 3ab7505d567..c235b96f77b 100644 --- a/apps/server/src/modules/legacy-school/uc/school-system-options.uc.spec.ts +++ b/apps/server/src/modules/legacy-school/uc/school-system-options.uc.spec.ts @@ -175,7 +175,7 @@ describe(SchoolSystemOptionsUc.name, () => { schoolSystemOptions.provisioningOptions ); - expect(provisioningOptionsUpdateService.handleActions).toHaveBeenCalledWith( + expect(provisioningOptionsUpdateService.handleUpdate).toHaveBeenCalledWith( schoolSystemOptions.schoolId, schoolSystemOptions.systemId, schoolSystemOptions.provisioningOptions, @@ -271,7 +271,7 @@ describe(SchoolSystemOptionsUc.name, () => { newOptions ); - expect(provisioningOptionsUpdateService.handleActions).toHaveBeenCalledWith( + expect(provisioningOptionsUpdateService.handleUpdate).toHaveBeenCalledWith( schoolSystemOptions.schoolId, schoolSystemOptions.systemId, newOptions, diff --git a/apps/server/src/modules/legacy-school/uc/school-system-options.uc.ts b/apps/server/src/modules/legacy-school/uc/school-system-options.uc.ts index 62926d43a6c..7f8e6b57dec 100644 --- a/apps/server/src/modules/legacy-school/uc/school-system-options.uc.ts +++ b/apps/server/src/modules/legacy-school/uc/school-system-options.uc.ts @@ -83,7 +83,7 @@ export class SchoolSystemOptionsUc { const currentProvisioningOptions: AnyProvisioningOptions = existingSchoolSystemOptions?.provisioningOptions ?? schoolSystemOptionsBuilder.getDefaultProvisioningOptions(); - await this.provisioningOptionsUpdateService.handleActions( + await this.provisioningOptionsUpdateService.handleUpdate( schoolId, systemId, newProvisioningOptions, From 679f569937ed61f355aab487124af74cc9bc8029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= Date: Tue, 9 Jan 2024 11:25:09 +0100 Subject: [PATCH 3/7] fix module --- apps/server/src/modules/legacy-school/legacy-school.module.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/server/src/modules/legacy-school/legacy-school.module.ts b/apps/server/src/modules/legacy-school/legacy-school.module.ts index ae9bf34f1a9..c48566d6d69 100644 --- a/apps/server/src/modules/legacy-school/legacy-school.module.ts +++ b/apps/server/src/modules/legacy-school/legacy-school.module.ts @@ -10,6 +10,7 @@ import { SchoolSystemOptionsService, SchoolValidationService, SchoolYearService, + SchulconnexProvisioningOptionsUpdateService, } from './service'; /** @@ -28,6 +29,7 @@ import { SchoolSystemOptionsRepo, SchoolSystemOptionsService, ProvisioningOptionsUpdateService, + SchulconnexProvisioningOptionsUpdateService, ], exports: [ LegacySchoolService, From 4e26a2d87fbe3b19560db5bf804f3a822e3c8c17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= Date: Tue, 9 Jan 2024 11:28:15 +0100 Subject: [PATCH 4/7] fix import --- apps/server/src/modules/legacy-school/legacy-school.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/modules/legacy-school/legacy-school.module.ts b/apps/server/src/modules/legacy-school/legacy-school.module.ts index c48566d6d69..92d028044d2 100644 --- a/apps/server/src/modules/legacy-school/legacy-school.module.ts +++ b/apps/server/src/modules/legacy-school/legacy-school.module.ts @@ -1,7 +1,7 @@ +import { GroupModule } from '@modules/group'; import { Module } from '@nestjs/common'; import { FederalStateRepo, LegacySchoolRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; -import { GroupModule } from '../group'; import { SchoolSystemOptionsRepo, SchoolYearRepo } from './repo'; import { FederalStateService, From 94d113fa9288bed62f9687f49c78e4f2373b45e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= Date: Tue, 9 Jan 2024 12:23:26 +0100 Subject: [PATCH 5/7] fix import cycle --- .../strategy/iserv/iserv.strategy.spec.ts | 10 ++--- .../strategy/iserv/iserv.strategy.ts | 15 +++---- .../modules/user/service/user.service.spec.ts | 8 ++-- .../src/modules/user/service/user.service.ts | 4 +- .../user/user-do.repo.integration.spec.ts | 45 +++++++++++++++++++ .../src/shared/repo/user/user-do.repo.ts | 11 +++++ 6 files changed, 73 insertions(+), 20 deletions(-) diff --git a/apps/server/src/modules/provisioning/strategy/iserv/iserv.strategy.spec.ts b/apps/server/src/modules/provisioning/strategy/iserv/iserv.strategy.spec.ts index 22f702f2b1d..47068ab32e2 100644 --- a/apps/server/src/modules/provisioning/strategy/iserv/iserv.strategy.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/iserv/iserv.strategy.spec.ts @@ -4,15 +4,14 @@ import { LegacySchoolService } from '@modules/legacy-school'; import { UserService } from '@modules/user'; import { Test, TestingModule } from '@nestjs/testing'; import { LegacySchoolDo, UserDO } from '@shared/domain/domainobject'; -import { User } from '@shared/domain/entity'; import { RoleName } from '@shared/domain/interface'; import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; -import { legacySchoolDoFactory, schoolFactory, setupEntities, userDoFactory, userFactory } from '@shared/testing'; -import jwt from 'jsonwebtoken'; +import { legacySchoolDoFactory, userDoFactory } from '@shared/testing'; import { IdTokenExtractionFailureLoggableException, IdTokenUserNotFoundLoggableException, } from '@src/modules/oauth/loggable'; +import jwt from 'jsonwebtoken'; import { RoleDto } from '../../../role/service/dto/role.dto'; import { ExternalSchoolDto, @@ -34,7 +33,6 @@ describe('IservProvisioningStrategy', () => { let userService: DeepMocked; beforeAll(async () => { - await setupEntities(); module = await Test.createTestingModule({ providers: [ IservProvisioningStrategy, @@ -141,9 +139,9 @@ describe('IservProvisioningStrategy', () => { it('should throw an error with code sso_user_notfound and additional information', async () => { const { input, userUUID, email } = setup(); const schoolId: string = new ObjectId().toHexString(); - const user: User = userFactory.buildWithId({ + const user: UserDO = userDoFactory.buildWithId({ externalId: userUUID, - school: schoolFactory.buildWithId(undefined, schoolId), + schoolId, }); jest.spyOn(jwt, 'decode').mockImplementation(() => { diff --git a/apps/server/src/modules/provisioning/strategy/iserv/iserv.strategy.ts b/apps/server/src/modules/provisioning/strategy/iserv/iserv.strategy.ts index 9567bcb4664..841daaee78e 100644 --- a/apps/server/src/modules/provisioning/strategy/iserv/iserv.strategy.ts +++ b/apps/server/src/modules/provisioning/strategy/iserv/iserv.strategy.ts @@ -1,15 +1,14 @@ import { LegacySchoolService } from '@modules/legacy-school'; +import { + IdTokenExtractionFailureLoggableException, + IdTokenUserNotFoundLoggableException, +} from '@modules/oauth/loggable'; import { UserService } from '@modules/user'; import { Injectable } from '@nestjs/common'; import { LegacySchoolDo, RoleReference, UserDO } from '@shared/domain/domainobject'; -import { User } from '@shared/domain/entity'; import { RoleName } from '@shared/domain/interface'; import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; import jwt, { JwtPayload } from 'jsonwebtoken'; -import { - IdTokenExtractionFailureLoggableException, - IdTokenUserNotFoundLoggableException, -} from '@modules/oauth/loggable'; import { ExternalSchoolDto, ExternalUserDto, @@ -66,10 +65,10 @@ export class IservProvisioningStrategy extends ProvisioningStrategy { async getAdditionalErrorInfo(email: string | undefined): Promise { if (email) { - const usersWithEmail: User[] = await this.userService.findByEmail(email); + const usersWithEmail: UserDO[] = await this.userService.findByEmail(email); if (usersWithEmail.length > 0) { - const user: User = usersWithEmail[0]; - return ` [schoolId: ${user.school.id}, currentLdapId: ${user.externalId ?? ''}]`; + const user: UserDO = usersWithEmail[0]; + return ` [schoolId: ${user.schoolId}, currentLdapId: ${user.externalId ?? ''}]`; } } return ''; diff --git a/apps/server/src/modules/user/service/user.service.spec.ts b/apps/server/src/modules/user/service/user.service.spec.ts index c7223c45322..310f5ccdfef 100644 --- a/apps/server/src/modules/user/service/user.service.spec.ts +++ b/apps/server/src/modules/user/service/user.service.spec.ts @@ -1,5 +1,6 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { EntityManager } from '@mikro-orm/core'; +import { ObjectId } from '@mikro-orm/mongodb'; import { AccountDto, AccountService } from '@modules/account'; import { OauthCurrentUser } from '@modules/authentication/interface'; import { RoleService } from '@modules/role'; @@ -12,7 +13,6 @@ import { EntityId } from '@shared/domain/types'; import { UserRepo } from '@shared/repo'; import { UserDORepo } from '@shared/repo/user/user-do.repo'; import { roleFactory, setupEntities, userDoFactory, userFactory } from '@shared/testing'; -import { ObjectId } from '@mikro-orm/mongodb'; import { UserDto } from '../uc/dto/user.dto'; import { UserQuery } from './user-query.type'; import { UserService } from './user.service'; @@ -346,11 +346,11 @@ describe('UserService', () => { describe('findByEmail is called', () => { describe('when a user with this email exists', () => { it('should return the user', async () => { - const user: User = userFactory.buildWithId(); + const user: UserDO = userDoFactory.buildWithId(); - userRepo.findByEmail.mockResolvedValue([user]); + userDORepo.findByEmail.mockResolvedValue([user]); - const result: User[] = await service.findByEmail(user.email); + const result: UserDO[] = await service.findByEmail(user.email); expect(result).toEqual([user]); }); diff --git a/apps/server/src/modules/user/service/user.service.ts b/apps/server/src/modules/user/service/user.service.ts index 4e8f2fe2394..ce2fe2316fa 100644 --- a/apps/server/src/modules/user/service/user.service.ts +++ b/apps/server/src/modules/user/service/user.service.ts @@ -90,8 +90,8 @@ export class UserService { return user; } - async findByEmail(email: string): Promise { - const user: Promise = this.userRepo.findByEmail(email); + async findByEmail(email: string): Promise { + const user: Promise = this.userDORepo.findByEmail(email); return user; } diff --git a/apps/server/src/shared/repo/user/user-do.repo.integration.spec.ts b/apps/server/src/shared/repo/user/user-do.repo.integration.spec.ts index 6fdbe820dc7..baa3efbcd89 100644 --- a/apps/server/src/shared/repo/user/user-do.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/user/user-do.repo.integration.spec.ts @@ -181,6 +181,51 @@ describe('UserRepo', () => { }); }); + describe('findByEmail', () => { + it('should find user by email', async () => { + const originalUsername = 'USER@EXAMPLE.COM'; + const user = userFactory.build({ email: originalUsername }); + await em.persistAndFlush([user]); + em.clear(); + + const result = await repo.findByEmail('USER@EXAMPLE.COM'); + expect(result).toHaveLength(1); + expect(result[0]).toEqual(expect.objectContaining({ email: originalUsername })); + }); + + it('should find user by email, ignoring case', async () => { + const originalUsername = 'USER@EXAMPLE.COM'; + const user = userFactory.build({ email: originalUsername }); + await em.persistAndFlush([user]); + em.clear(); + + let result: UserDO[]; + + result = await repo.findByEmail('USER@example.COM'); + expect(result).toHaveLength(1); + expect(result[0]).toEqual(expect.objectContaining({ email: originalUsername })); + + result = await repo.findByEmail('user@example.com'); + expect(result).toHaveLength(1); + expect(result[0]).toEqual(expect.objectContaining({ email: originalUsername })); + }); + + it('should not find by wildcard', async () => { + const originalUsername = 'USER@EXAMPLE.COM'; + const user = userFactory.build({ email: originalUsername }); + await em.persistAndFlush([user]); + em.clear(); + + let result: UserDO[]; + + result = await repo.findByEmail('USER@EXAMPLECCOM'); + expect(result).toHaveLength(0); + + result = await repo.findByEmail('.*'); + expect(result).toHaveLength(0); + }); + }); + describe('mapEntityToDO', () => { it('should return a domain object', () => { const id = new ObjectId(); diff --git a/apps/server/src/shared/repo/user/user-do.repo.ts b/apps/server/src/shared/repo/user/user-do.repo.ts index 1ab196fc3b5..3e363f03120 100644 --- a/apps/server/src/shared/repo/user/user-do.repo.ts +++ b/apps/server/src/shared/repo/user/user-do.repo.ts @@ -90,6 +90,17 @@ export class UserDORepo extends BaseDORepo { return userDo; } + async findByEmail(email: string): Promise { + // find mail case-insensitive by regex + const userEntitys: User[] = await this._em.find(User, { + email: new RegExp(`^${email.replace(/\W/g, '\\$&')}$`, 'i'), + }); + + const userDos: UserDO[] = userEntitys.map((userEntity: User): UserDO => this.mapEntityToDO(userEntity)); + + return userDos; + } + mapEntityToDO(entity: User): UserDO { const user: UserDO = new UserDO({ id: entity.id, From f8533fc49f43ba4adae89e0f386cd65acda9cf4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= Date: Tue, 9 Jan 2024 14:04:57 +0100 Subject: [PATCH 6/7] fix import cycle --- apps/server/src/modules/group/entity/group-user.entity.ts | 3 ++- .../service/user-login-migration-revert.service.spec.ts | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/server/src/modules/group/entity/group-user.entity.ts b/apps/server/src/modules/group/entity/group-user.entity.ts index e202de7a400..d69ef492b25 100644 --- a/apps/server/src/modules/group/entity/group-user.entity.ts +++ b/apps/server/src/modules/group/entity/group-user.entity.ts @@ -1,5 +1,6 @@ import { Embeddable, ManyToOne } from '@mikro-orm/core'; -import { Role, User } from '@shared/domain/entity'; +import { Role } from '@shared/domain/entity/role.entity'; +import { User } from '@shared/domain/entity/user.entity'; export interface GroupUserEntityProps { user: User; diff --git a/apps/server/src/modules/user-login-migration/service/user-login-migration-revert.service.spec.ts b/apps/server/src/modules/user-login-migration/service/user-login-migration-revert.service.spec.ts index 248ba25913f..fd94f69d66e 100644 --- a/apps/server/src/modules/user-login-migration/service/user-login-migration-revert.service.spec.ts +++ b/apps/server/src/modules/user-login-migration/service/user-login-migration-revert.service.spec.ts @@ -2,7 +2,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { LegacySchoolService } from '@modules/legacy-school'; import { Test, TestingModule } from '@nestjs/testing'; import { SchoolFeature } from '@shared/domain/types'; -import { setupEntities, userLoginMigrationDOFactory } from '@shared/testing'; +import { userLoginMigrationDOFactory } from '@shared/testing'; import { UserLoginMigrationRevertService } from './user-login-migration-revert.service'; import { UserLoginMigrationService } from './user-login-migration.service'; @@ -14,8 +14,6 @@ describe('UserLoginMigrationRevertService', () => { let userLoginMigrationService: DeepMocked; beforeAll(async () => { - await setupEntities(); - module = await Test.createTestingModule({ providers: [ UserLoginMigrationRevertService, From e564a2461350733c705959727ba1d981cc017f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= Date: Tue, 9 Jan 2024 14:47:26 +0100 Subject: [PATCH 7/7] fix seed data --- backup/setup/schools.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backup/setup/schools.json b/backup/setup/schools.json index 29ce522fe58..a6382fa5381 100644 --- a/backup/setup/schools.json +++ b/backup/setup/schools.json @@ -193,7 +193,9 @@ "$oid": "5fa318f2b229544f2c697a56" }, "documentBaseDirType": "", - "systems": [], + "systems": [{ + "$oid": "62c7f233f35a554ba3ed0000" + }], "experimental": false, "pilot": false, "features": [