From e4b62879dcfce42ef383c282a353e11af925e7dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= Date: Thu, 18 Apr 2024 16:56:58 +0200 Subject: [PATCH] refatorings --- .../recursive-copy.visitor.ts | 25 +-- .../media-available-line.service.spec.ts | 10 +- .../media-available-line.service.ts | 5 +- ...ser.entity.ts => group-user.embeddable.ts} | 2 +- ...ty.ts => group-valid-period.embeddable.ts} | 2 +- .../src/modules/group/entity/group.entity.ts | 24 +-- apps/server/src/modules/group/entity/index.ts | 4 +- .../modules/group/repo/group-domain.mapper.ts | 22 +-- .../src/modules/group/repo/group.repo.spec.ts | 4 +- .../modules/server/api/dto/config.response.ts | 4 + .../server/api/test/server.api.spec.ts | 1 + .../domain/error/index.ts | 1 + ...tricted-context-mismatch-loggabble.spec.ts | 6 +- .../restricted-context-mismatch-loggabble.ts | 7 +- .../context-external-tool/domain/index.ts | 1 + .../context-external-tool.service.spec.ts | 21 +-- .../service/context-external-tool.service.ts | 10 +- .../context-external-tool/service/index.ts | 1 + .../service/tool-reference.service.spec.ts | 7 +- .../service/tool-reference.service.ts | 5 +- .../external-tool-datasheet.mapper.spec.ts | 137 +------------- .../mapper/external-tool-datasheet.mapper.ts | 20 +- .../external-tool-logo-service.spec.ts | 4 +- .../service/external-tool-logo.service.ts | 7 +- .../uc/external-tool-configuration.uc.spec.ts | 11 +- .../uc/external-tool-configuration.uc.ts | 10 +- .../user-import/config/user-import-config.ts | 4 +- .../api-test/import-user-populate.api.spec.ts | 174 ++++++++++++++++++ .../api-test/import-user.api.spec.ts | 110 +---------- .../src/modules/user-import/service/index.ts | 2 +- ...lconnex-fetch-import-users.service.spec.ts | 2 +- .../schulconnex-fetch-import-users.service.ts | 4 +- .../service/user-import.service.spec.ts | 2 +- .../uc/user-import-fetch.uc.spec.ts | 2 +- .../user-import/uc/user-import-fetch.uc.ts | 2 +- .../user-import/uc/user-import.uc.spec.ts | 14 +- .../modules/user-import/uc/user-import.uc.ts | 12 +- ...ntity.ts => external-source.embeddable.ts} | 2 +- apps/server/src/shared/domain/entity/index.ts | 2 +- .../testing/factory/group-entity.factory.ts | 8 +- config/default.schema.json | 5 + 41 files changed, 305 insertions(+), 391 deletions(-) rename apps/server/src/modules/group/entity/{group-user.entity.ts => group-user.embeddable.ts} (92%) rename apps/server/src/modules/group/entity/{group-valid-period.entity.ts => group-valid-period.embeddable.ts} (88%) create mode 100644 apps/server/src/modules/tool/context-external-tool/domain/error/index.ts rename apps/server/src/modules/tool/context-external-tool/{service => domain/error}/restricted-context-mismatch-loggabble.spec.ts (74%) rename apps/server/src/modules/tool/context-external-tool/{service => domain/error}/restricted-context-mismatch-loggabble.ts (68%) create mode 100644 apps/server/src/modules/user-import/controller/api-test/import-user-populate.api.spec.ts rename apps/server/src/modules/user-import/service/{strategy => }/schulconnex-fetch-import-users.service.spec.ts (99%) rename apps/server/src/modules/user-import/service/{strategy => }/schulconnex-fetch-import-users.service.ts (95%) rename apps/server/src/shared/domain/entity/{external-source.entity.ts => external-source.embeddable.ts} (91%) diff --git a/apps/server/src/modules/board/service/board-do-copy-service/recursive-copy.visitor.ts b/apps/server/src/modules/board/service/board-do-copy-service/recursive-copy.visitor.ts index 5895d2139e3..ddd74e0520e 100644 --- a/apps/server/src/modules/board/service/board-do-copy-service/recursive-copy.visitor.ts +++ b/apps/server/src/modules/board/service/board-do-copy-service/recursive-copy.visitor.ts @@ -247,9 +247,7 @@ export class RecursiveCopyVisitor implements BoardCompositeVisitorAsync { } async visitExternalToolElementAsync(original: ExternalToolElement): Promise { - let status: CopyStatusEnum = CopyStatusEnum.SUCCESS; - - const copy = new ExternalToolElement({ + const boardElementCopy: ExternalToolElement = new ExternalToolElement({ id: new ObjectId().toHexString(), contextExternalToolId: undefined, children: [], @@ -257,29 +255,32 @@ export class RecursiveCopyVisitor implements BoardCompositeVisitorAsync { updatedAt: new Date(), }); + let status: CopyStatusEnum; if (this.toolFeatures.ctlToolsCopyEnabled && original.contextExternalToolId) { - const tool: ContextExternalTool | null = await this.contextExternalToolService.findById( + const linkedTool: ContextExternalTool | null = await this.contextExternalToolService.findById( original.contextExternalToolId ); - if (tool) { - const copiedTool: ContextExternalTool = await this.contextExternalToolService.copyContextExternalTool( - tool, - copy.id - ); + if (linkedTool) { + const contextExternalToolCopy: ContextExternalTool = + await this.contextExternalToolService.copyContextExternalTool(linkedTool, boardElementCopy.id); + + boardElementCopy.contextExternalToolId = contextExternalToolCopy.id; - copy.contextExternalToolId = copiedTool.id; + status = CopyStatusEnum.SUCCESS; } else { status = CopyStatusEnum.FAIL; } + } else { + status = CopyStatusEnum.SUCCESS; } this.resultMap.set(original.id, { - copyEntity: copy, + copyEntity: boardElementCopy, type: CopyElementType.EXTERNAL_TOOL_ELEMENT, status, }); - this.copyMap.set(original.id, copy); + this.copyMap.set(original.id, boardElementCopy); return Promise.resolve(); } diff --git a/apps/server/src/modules/board/service/media-board/media-available-line.service.spec.ts b/apps/server/src/modules/board/service/media-board/media-available-line.service.spec.ts index e2ce8effab5..a1d4d64deef 100644 --- a/apps/server/src/modules/board/service/media-board/media-available-line.service.spec.ts +++ b/apps/server/src/modules/board/service/media-board/media-available-line.service.spec.ts @@ -287,14 +287,8 @@ describe(MediaAvailableLineService.name, () => { service.createMediaAvailableLine(availableExternalTools); - expect(externalToolLogoService.buildLogoUrl).toHaveBeenCalledWith( - '/v3/tools/external-tools/{id}/logo', - externalTool1 - ); - expect(externalToolLogoService.buildLogoUrl).toHaveBeenCalledWith( - '/v3/tools/external-tools/{id}/logo', - externalTool2 - ); + expect(externalToolLogoService.buildLogoUrl).toHaveBeenCalledWith(externalTool1); + expect(externalToolLogoService.buildLogoUrl).toHaveBeenCalledWith(externalTool2); }); it('should create a media available line with correct elements', () => { diff --git a/apps/server/src/modules/board/service/media-board/media-available-line.service.ts b/apps/server/src/modules/board/service/media-board/media-available-line.service.ts index fee2cd26ac3..96f01b43b2c 100644 --- a/apps/server/src/modules/board/service/media-board/media-available-line.service.ts +++ b/apps/server/src/modules/board/service/media-board/media-available-line.service.ts @@ -134,10 +134,7 @@ export class MediaAvailableLineService { externalTool: ExternalTool, schoolExternalTool: SchoolExternalTool ): MediaAvailableLineElement { - const logoUrl: string | undefined = this.externalToolLogoService.buildLogoUrl( - '/v3/tools/external-tools/{id}/logo', - externalTool - ); + const logoUrl: string | undefined = this.externalToolLogoService.buildLogoUrl(externalTool); const element: MediaAvailableLineElement = new MediaAvailableLineElement({ schoolExternalToolId: schoolExternalTool.id ?? '', diff --git a/apps/server/src/modules/group/entity/group-user.entity.ts b/apps/server/src/modules/group/entity/group-user.embeddable.ts similarity index 92% rename from apps/server/src/modules/group/entity/group-user.entity.ts rename to apps/server/src/modules/group/entity/group-user.embeddable.ts index d69ef492b25..943c58ef136 100644 --- a/apps/server/src/modules/group/entity/group-user.entity.ts +++ b/apps/server/src/modules/group/entity/group-user.embeddable.ts @@ -9,7 +9,7 @@ export interface GroupUserEntityProps { } @Embeddable() -export class GroupUserEntity { +export class GroupUserEmbeddable { @ManyToOne(() => User) user: User; diff --git a/apps/server/src/modules/group/entity/group-valid-period.entity.ts b/apps/server/src/modules/group/entity/group-valid-period.embeddable.ts similarity index 88% rename from apps/server/src/modules/group/entity/group-valid-period.entity.ts rename to apps/server/src/modules/group/entity/group-valid-period.embeddable.ts index f3f656241c6..58f926427c4 100644 --- a/apps/server/src/modules/group/entity/group-valid-period.entity.ts +++ b/apps/server/src/modules/group/entity/group-valid-period.embeddable.ts @@ -7,7 +7,7 @@ export interface GroupValidPeriodEntityProps { } @Embeddable() -export class GroupValidPeriodEntity { +export class GroupValidPeriodEmbeddable { @Property() from: Date; diff --git a/apps/server/src/modules/group/entity/group.entity.ts b/apps/server/src/modules/group/entity/group.entity.ts index 29261251463..bae4578cbe3 100644 --- a/apps/server/src/modules/group/entity/group.entity.ts +++ b/apps/server/src/modules/group/entity/group.entity.ts @@ -1,11 +1,11 @@ import { Collection, Embedded, Entity, Enum, ManyToOne, OneToMany, Property } from '@mikro-orm/core'; import { Course as CourseEntity } from '@shared/domain/entity'; import { BaseEntityWithTimestamps } from '@shared/domain/entity/base.entity'; -import { ExternalSourceEntity } from '@shared/domain/entity/external-source.entity'; +import { ExternalSourceEmbeddable } from '@shared/domain/entity/external-source.embeddable'; import { SchoolEntity } from '@shared/domain/entity/school.entity'; import { EntityId } from '@shared/domain/types'; -import { GroupUserEntity } from './group-user.entity'; -import { GroupValidPeriodEntity } from './group-valid-period.entity'; +import { GroupUserEmbeddable } from './group-user.embeddable'; +import { GroupValidPeriodEmbeddable } from './group-valid-period.embeddable'; export enum GroupEntityTypes { CLASS = 'class', @@ -20,11 +20,11 @@ export interface GroupEntityProps { type: GroupEntityTypes; - externalSource?: ExternalSourceEntity; + externalSource?: ExternalSourceEmbeddable; - validPeriod?: GroupValidPeriodEntity; + validPeriod?: GroupValidPeriodEmbeddable; - users: GroupUserEntity[]; + users: GroupUserEmbeddable[]; organization?: SchoolEntity; } @@ -37,14 +37,14 @@ export class GroupEntity extends BaseEntityWithTimestamps { @Enum() type: GroupEntityTypes; - @Embedded(() => ExternalSourceEntity, { nullable: true }) - externalSource?: ExternalSourceEntity; + @Embedded(() => ExternalSourceEmbeddable, { nullable: true }) + externalSource?: ExternalSourceEmbeddable; - @Embedded(() => GroupValidPeriodEntity, { nullable: true }) - validPeriod?: GroupValidPeriodEntity; + @Embedded(() => GroupValidPeriodEmbeddable, { nullable: true }) + validPeriod?: GroupValidPeriodEmbeddable; - @Embedded(() => GroupUserEntity, { array: true }) - users: GroupUserEntity[]; + @Embedded(() => GroupUserEmbeddable, { array: true }) + users: GroupUserEmbeddable[]; @ManyToOne(() => SchoolEntity, { nullable: true }) organization?: SchoolEntity; diff --git a/apps/server/src/modules/group/entity/index.ts b/apps/server/src/modules/group/entity/index.ts index 1aff7d6deeb..72122ce8ed5 100644 --- a/apps/server/src/modules/group/entity/index.ts +++ b/apps/server/src/modules/group/entity/index.ts @@ -1,3 +1,3 @@ export * from './group.entity'; -export * from './group-user.entity'; -export * from './group-valid-period.entity'; +export * from './group-user.embeddable'; +export * from './group-valid-period.embeddable'; diff --git a/apps/server/src/modules/group/repo/group-domain.mapper.ts b/apps/server/src/modules/group/repo/group-domain.mapper.ts index e400dc54f48..60adfbaec95 100644 --- a/apps/server/src/modules/group/repo/group-domain.mapper.ts +++ b/apps/server/src/modules/group/repo/group-domain.mapper.ts @@ -1,9 +1,9 @@ import { EntityData } from '@mikro-orm/core'; import { EntityManager } from '@mikro-orm/mongodb'; import { ExternalSource } from '@shared/domain/domainobject'; -import { ExternalSourceEntity, Role, SchoolEntity, SystemEntity, User } from '@shared/domain/entity'; +import { ExternalSourceEmbeddable, Role, SchoolEntity, SystemEntity, User } from '@shared/domain/entity'; import { Group, GroupProps, GroupTypes, GroupUser } from '../domain'; -import { GroupEntity, GroupEntityTypes, GroupUserEntity, GroupValidPeriodEntity } from '../entity'; +import { GroupEntity, GroupEntityTypes, GroupUserEmbeddable, GroupValidPeriodEmbeddable } from '../entity'; const GroupEntityTypesToGroupTypesMapping: Record = { [GroupEntityTypes.CLASS]: GroupTypes.CLASS, @@ -21,9 +21,9 @@ export class GroupDomainMapper { static mapDoToEntityData(group: Group, em: EntityManager): EntityData { const props: GroupProps = group.getProps(); - let validPeriod: GroupValidPeriodEntity | undefined; + let validPeriod: GroupValidPeriodEmbeddable | undefined; if (props.validFrom && props.validUntil) { - validPeriod = new GroupValidPeriodEntity({ + validPeriod = new GroupValidPeriodEmbeddable({ from: props.validFrom, until: props.validUntil, }); @@ -36,7 +36,7 @@ export class GroupDomainMapper { ? this.mapExternalSourceToExternalSourceEntity(props.externalSource, em) : undefined, users: props.users.map( - (groupUser): GroupUserEntity => GroupDomainMapper.mapGroupUserToGroupUserEntity(groupUser, em) + (groupUser): GroupUserEmbeddable => GroupDomainMapper.mapGroupUserToGroupUserEntity(groupUser, em) ), validPeriod, organization: props.organizationId ? em.getReference(SchoolEntity, props.organizationId) : undefined, @@ -65,8 +65,8 @@ export class GroupDomainMapper { static mapExternalSourceToExternalSourceEntity( externalSource: ExternalSource, em: EntityManager - ): ExternalSourceEntity { - const externalSourceEntity: ExternalSourceEntity = new ExternalSourceEntity({ + ): ExternalSourceEmbeddable { + const externalSourceEntity: ExternalSourceEmbeddable = new ExternalSourceEmbeddable({ externalId: externalSource.externalId, system: em.getReference(SystemEntity, externalSource.systemId), }); @@ -74,7 +74,7 @@ export class GroupDomainMapper { return externalSourceEntity; } - static mapExternalSourceEntityToExternalSource(entity: ExternalSourceEntity): ExternalSource { + static mapExternalSourceEntityToExternalSource(entity: ExternalSourceEmbeddable): ExternalSource { const externalSource: ExternalSource = new ExternalSource({ externalId: entity.externalId, systemId: entity.system.id, @@ -83,8 +83,8 @@ export class GroupDomainMapper { return externalSource; } - static mapGroupUserToGroupUserEntity(groupUser: GroupUser, em: EntityManager): GroupUserEntity { - const groupUserEntity: GroupUserEntity = new GroupUserEntity({ + static mapGroupUserToGroupUserEntity(groupUser: GroupUser, em: EntityManager): GroupUserEmbeddable { + const groupUserEntity: GroupUserEmbeddable = new GroupUserEmbeddable({ user: em.getReference(User, groupUser.userId), role: em.getReference(Role, groupUser.roleId), }); @@ -92,7 +92,7 @@ export class GroupDomainMapper { return groupUserEntity; } - static mapGroupUserEntityToGroupUser(entity: GroupUserEntity): GroupUser { + static mapGroupUserEntityToGroupUser(entity: GroupUserEmbeddable): GroupUser { const groupUser: GroupUser = new GroupUser({ userId: entity.user.id, roleId: entity.role.id, 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 e726c5bdc8d..2dce82334ad 100644 --- a/apps/server/src/modules/group/repo/group.repo.spec.ts +++ b/apps/server/src/modules/group/repo/group.repo.spec.ts @@ -17,7 +17,7 @@ import { userFactory, } from '@shared/testing'; import { Group, GroupProps, GroupTypes, GroupUser } from '../domain'; -import { GroupEntity, GroupEntityTypes, GroupUserEntity } from '../entity'; +import { GroupEntity, GroupEntityTypes, GroupUserEmbeddable } from '../entity'; import { GroupRepo } from './group.repo'; describe('GroupRepo', () => { @@ -216,7 +216,7 @@ describe('GroupRepo', () => { const setup = async () => { const userEntity: User = userFactory.buildWithId(); const user: UserDO = userDoFactory.build({ id: userEntity.id }); - const groupUserEntity: GroupUserEntity = new GroupUserEntity({ + const groupUserEntity: GroupUserEmbeddable = new GroupUserEmbeddable({ user: userEntity, role: roleFactory.buildWithId(), }); diff --git a/apps/server/src/modules/server/api/dto/config.response.ts b/apps/server/src/modules/server/api/dto/config.response.ts index 163dd9bf6e6..5f27948c45b 100644 --- a/apps/server/src/modules/server/api/dto/config.response.ts +++ b/apps/server/src/modules/server/api/dto/config.response.ts @@ -197,6 +197,9 @@ export class ConfigResponse { @ApiProperty() FEATURE_MEDIA_SHELF_ENABLED: boolean; + @ApiProperty() + FEATURE_MIGRATION_WIZARD_WITH_USER_LOGIN_MIGRATION: boolean; + constructor(config: ServerConfig) { this.ACCESSIBILITY_REPORT_EMAIL = config.ACCESSIBILITY_REPORT_EMAIL; this.ADMIN_TABLES_DISPLAY_CONSENT_COLUMN = config.ADMIN_TABLES_DISPLAY_CONSENT_COLUMN; @@ -260,5 +263,6 @@ export class ConfigResponse { this.FEATURE_VIDEOCONFERENCE_ENABLED = config.enabled; this.FEATURE_SCHULCONNEX_COURSE_SYNC_ENABLED = config.FEATURE_SCHULCONNEX_COURSE_SYNC_ENABLED; this.FEATURE_MEDIA_SHELF_ENABLED = config.FEATURE_MEDIA_SHELF_ENABLED; + this.FEATURE_MIGRATION_WIZARD_WITH_USER_LOGIN_MIGRATION = config.useWithUserLoginMigration; } } diff --git a/apps/server/src/modules/server/api/test/server.api.spec.ts b/apps/server/src/modules/server/api/test/server.api.spec.ts index 2a271e7d256..cef89344897 100644 --- a/apps/server/src/modules/server/api/test/server.api.spec.ts +++ b/apps/server/src/modules/server/api/test/server.api.spec.ts @@ -93,6 +93,7 @@ describe('Server Controller (API)', () => { 'TLDRAW__ASSETS_MAX_SIZE', 'FEATURE_SCHULCONNEX_COURSE_SYNC_ENABLED', 'FEATURE_MEDIA_SHELF_ENABLED', + 'FEATURE_MIGRATION_WIZARD_WITH_USER_LOGIN_MIGRATION', ]; expect(response.status).toEqual(HttpStatus.OK); diff --git a/apps/server/src/modules/tool/context-external-tool/domain/error/index.ts b/apps/server/src/modules/tool/context-external-tool/domain/error/index.ts new file mode 100644 index 00000000000..75d3370f475 --- /dev/null +++ b/apps/server/src/modules/tool/context-external-tool/domain/error/index.ts @@ -0,0 +1 @@ +export { RestrictedContextMismatchLoggableException } from './restricted-context-mismatch-loggabble'; diff --git a/apps/server/src/modules/tool/context-external-tool/service/restricted-context-mismatch-loggabble.spec.ts b/apps/server/src/modules/tool/context-external-tool/domain/error/restricted-context-mismatch-loggabble.spec.ts similarity index 74% rename from apps/server/src/modules/tool/context-external-tool/service/restricted-context-mismatch-loggabble.spec.ts rename to apps/server/src/modules/tool/context-external-tool/domain/error/restricted-context-mismatch-loggabble.spec.ts index 7e57cb79cc5..504c69622cf 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/restricted-context-mismatch-loggabble.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/domain/error/restricted-context-mismatch-loggabble.spec.ts @@ -1,12 +1,12 @@ -import { ToolContextType } from '../../common/enum'; -import { RestrictedContextMismatchLoggable } from './restricted-context-mismatch-loggabble'; +import { ToolContextType } from '../../../common/enum'; +import { RestrictedContextMismatchLoggableException } from './restricted-context-mismatch-loggabble'; describe('RestrictedContextMismatchLoggable', () => { describe('getLogMessage', () => { const setup = () => { const externalToolName = 'name'; const context: ToolContextType = ToolContextType.COURSE; - const loggable = new RestrictedContextMismatchLoggable(externalToolName, context); + const loggable = new RestrictedContextMismatchLoggableException(externalToolName, context); return { loggable, externalToolName, context }; }; diff --git a/apps/server/src/modules/tool/context-external-tool/service/restricted-context-mismatch-loggabble.ts b/apps/server/src/modules/tool/context-external-tool/domain/error/restricted-context-mismatch-loggabble.ts similarity index 68% rename from apps/server/src/modules/tool/context-external-tool/service/restricted-context-mismatch-loggabble.ts rename to apps/server/src/modules/tool/context-external-tool/domain/error/restricted-context-mismatch-loggabble.ts index 8e6fc4e643a..e0fb9cc0687 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/restricted-context-mismatch-loggabble.ts +++ b/apps/server/src/modules/tool/context-external-tool/domain/error/restricted-context-mismatch-loggabble.ts @@ -1,9 +1,8 @@ import { UnprocessableEntityException } from '@nestjs/common'; -import { Loggable } from '@src/core/logger/interfaces'; -import { ErrorLogMessage, LogMessage, ValidationErrorLogMessage } from '@src/core/logger'; -import { ToolContextType } from '../../common/enum'; +import { ErrorLogMessage, Loggable, LogMessage, ValidationErrorLogMessage } from '@src/core/logger'; +import { ToolContextType } from '../../../common/enum'; -export class RestrictedContextMismatchLoggable extends UnprocessableEntityException implements Loggable { +export class RestrictedContextMismatchLoggableException extends UnprocessableEntityException implements Loggable { constructor(private readonly externalToolName: string, private readonly context: ToolContextType) { super(); } diff --git a/apps/server/src/modules/tool/context-external-tool/domain/index.ts b/apps/server/src/modules/tool/context-external-tool/domain/index.ts index 557bc04788c..17704725505 100644 --- a/apps/server/src/modules/tool/context-external-tool/domain/index.ts +++ b/apps/server/src/modules/tool/context-external-tool/domain/index.ts @@ -1,3 +1,4 @@ export * from './context-external-tool.do'; export * from './context-ref'; export * from './tool-reference'; +export { RestrictedContextMismatchLoggableException } from './error'; diff --git a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.spec.ts b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.spec.ts index c0fb96c7f2d..0f50917ec54 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.spec.ts @@ -1,6 +1,9 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; import { NotFoundException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; +import { Permission } from '@shared/domain/interface'; import { ContextExternalToolRepo } from '@shared/repo'; import { contextExternalToolFactory, @@ -9,19 +12,15 @@ import { legacySchoolDoFactory, schoolExternalToolFactory, } from '@shared/testing/factory/domainobject'; -import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; -import { ObjectId } from '@mikro-orm/mongodb'; -import { Permission } from '@shared/domain/interface'; +import { CustomParameter } from '../../common/domain'; import { ToolContextType } from '../../common/enum'; -import { SchoolExternalTool, SchoolExternalToolWithId } from '../../school-external-tool/domain'; -import { ContextExternalTool, ContextRef } from '../domain'; -import { ContextExternalToolService } from './context-external-tool.service'; -import { ExternalTool } from '../../external-tool/domain'; +import { CommonToolService } from '../../common/service'; import { ExternalToolService } from '../../external-tool'; +import { ExternalTool } from '../../external-tool/domain'; import { SchoolExternalToolService } from '../../school-external-tool'; -import { RestrictedContextMismatchLoggable } from './restricted-context-mismatch-loggabble'; -import { CommonToolService } from '../../common/service'; -import { CustomParameter } from '../../common/domain'; +import { SchoolExternalTool, SchoolExternalToolWithId } from '../../school-external-tool/domain'; +import { ContextExternalTool, ContextRef, RestrictedContextMismatchLoggableException } from '../domain'; +import { ContextExternalToolService } from './context-external-tool.service'; describe('ContextExternalToolService', () => { let module: TestingModule; @@ -397,7 +396,7 @@ describe('ContextExternalToolService', () => { const { contextExternalTool, externalTool } = setup(); await expect(service.checkContextRestrictions(contextExternalTool)).rejects.toThrow( - new RestrictedContextMismatchLoggable(externalTool.name, contextExternalTool.contextRef.type) + new RestrictedContextMismatchLoggableException(externalTool.name, contextExternalTool.contextRef.type) ); }); }); diff --git a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.ts b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.ts index 5d2db49328f..127958c631e 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool.service.ts @@ -7,9 +7,13 @@ import { ExternalTool } from '../../external-tool/domain'; import { ExternalToolService } from '../../external-tool/service'; import { SchoolExternalTool } from '../../school-external-tool/domain'; import { SchoolExternalToolService } from '../../school-external-tool/service'; -import { ContextExternalTool, ContextExternalToolWithId, ContextRef } from '../domain'; +import { + ContextExternalTool, + ContextExternalToolWithId, + ContextRef, + RestrictedContextMismatchLoggableException, +} from '../domain'; import { ContextExternalToolQuery } from '../uc/dto/context-external-tool.types'; -import { RestrictedContextMismatchLoggable } from './restricted-context-mismatch-loggabble'; @Injectable() export class ContextExternalToolService { @@ -75,7 +79,7 @@ export class ContextExternalToolService { const externalTool: ExternalTool = await this.externalToolService.findById(schoolExternalTool.toolId); if (this.commonToolService.isContextRestricted(externalTool, contextExternalTool.contextRef.type)) { - throw new RestrictedContextMismatchLoggable(externalTool.name, contextExternalTool.contextRef.type); + throw new RestrictedContextMismatchLoggableException(externalTool.name, contextExternalTool.contextRef.type); } } diff --git a/apps/server/src/modules/tool/context-external-tool/service/index.ts b/apps/server/src/modules/tool/context-external-tool/service/index.ts index 30458b4b9c3..ca5dd69b3f3 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/index.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/index.ts @@ -1,3 +1,4 @@ export * from './context-external-tool.service'; export * from './context-external-tool-authorizable.service'; export * from './tool-reference.service'; +export { ToolConfigurationStatusService } from './tool-configuration-status.service'; diff --git a/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.spec.ts b/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.spec.ts index 04fdee9c030..816192c2dc2 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.spec.ts @@ -12,8 +12,8 @@ import { SchoolExternalToolService } from '../../school-external-tool'; import { SchoolExternalToolWithId } from '../../school-external-tool/domain'; import { ToolReference } from '../domain'; import { ContextExternalToolService } from './context-external-tool.service'; -import { ToolReferenceService } from './tool-reference.service'; import { ToolConfigurationStatusService } from './tool-configuration-status.service'; +import { ToolReferenceService } from './tool-reference.service'; describe('ToolReferenceService', () => { let module: TestingModule; @@ -118,10 +118,7 @@ describe('ToolReferenceService', () => { await service.getToolReference(contextExternalToolId); - expect(externalToolLogoService.buildLogoUrl).toHaveBeenCalledWith( - '/v3/tools/external-tools/{id}/logo', - externalTool - ); + expect(externalToolLogoService.buildLogoUrl).toHaveBeenCalledWith(externalTool); }); it('should return the tool reference', async () => { diff --git a/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.ts b/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.ts index 0d9cae1c23c..ae3e8fdb08c 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.ts @@ -40,10 +40,7 @@ export class ToolReferenceService { contextExternalTool, status ); - toolReference.logoUrl = this.externalToolLogoService.buildLogoUrl( - '/v3/tools/external-tools/{id}/logo', - externalTool - ); + toolReference.logoUrl = this.externalToolLogoService.buildLogoUrl(externalTool); return toolReference; } diff --git a/apps/server/src/modules/tool/external-tool/mapper/external-tool-datasheet.mapper.spec.ts b/apps/server/src/modules/tool/external-tool/mapper/external-tool-datasheet.mapper.spec.ts index 6014c236210..a80fc8763ab 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/external-tool-datasheet.mapper.spec.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/external-tool-datasheet.mapper.spec.ts @@ -22,7 +22,7 @@ import { ExternalToolDatasheetMapper } from './external-tool-datasheet.mapper'; describe(ExternalToolDatasheetMapper.name, () => { beforeEach(() => { - Configuration.set('SC_THEME', 'default'); + Configuration.set('SC_TITLE', 'dBildungscloud'); }); afterEach(() => { @@ -146,141 +146,6 @@ describe(ExternalToolDatasheetMapper.name, () => { }); }); - describe('when instance is unknown', () => { - const setup = () => { - Configuration.set('SC_THEME', 'mockInstance'); - const user: UserDO = userDoFactory.build(); - const externalTool = externalToolFactory.build(); - const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build(); - const expectDatasheet: ExternalToolDatasheetTemplateData = externalToolDatasheetTemplateDataFactory.build({ - instance: 'unbekannt', - }); - - return { user, externalTool, schoolExternalTool, expectDatasheet }; - }; - it('should map correct instance', () => { - const { user, externalTool, schoolExternalTool, expectDatasheet } = setup(); - - const mappedData: ExternalToolDatasheetTemplateData = - ExternalToolDatasheetMapper.mapToExternalToolDatasheetTemplateData( - externalTool, - user.firstName, - user.lastName, - schoolExternalTool - ); - - expect(mappedData).toEqual(expectDatasheet); - }); - }); - - describe('when instance is brb', () => { - const setup = () => { - Configuration.set('SC_THEME', 'brb'); - const user: UserDO = userDoFactory.build(); - const externalTool = externalToolFactory.build(); - const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build(); - const expectDatasheet: ExternalToolDatasheetTemplateData = externalToolDatasheetTemplateDataFactory.build({ - instance: 'Schul-Cloud Brandenburg', - }); - - return { user, externalTool, schoolExternalTool, expectDatasheet }; - }; - it('should map correct instance', () => { - const { user, externalTool, schoolExternalTool, expectDatasheet } = setup(); - - const mappedData: ExternalToolDatasheetTemplateData = - ExternalToolDatasheetMapper.mapToExternalToolDatasheetTemplateData( - externalTool, - user.firstName, - user.lastName, - schoolExternalTool - ); - - expect(mappedData).toEqual(expectDatasheet); - }); - }); - - describe('when instance is thr', () => { - const setup = () => { - Configuration.set('SC_THEME', 'thr'); - const user: UserDO = userDoFactory.build(); - const externalTool = externalToolFactory.build(); - const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build(); - const expectDatasheet: ExternalToolDatasheetTemplateData = externalToolDatasheetTemplateDataFactory.build({ - instance: 'Thüringer Schulcloud', - }); - - return { user, externalTool, schoolExternalTool, expectDatasheet }; - }; - it('should map correct instance', () => { - const { user, externalTool, schoolExternalTool, expectDatasheet } = setup(); - - const mappedData: ExternalToolDatasheetTemplateData = - ExternalToolDatasheetMapper.mapToExternalToolDatasheetTemplateData( - externalTool, - user.firstName, - user.lastName, - schoolExternalTool - ); - - expect(mappedData).toEqual(expectDatasheet); - }); - }); - - describe('when instance is dbc', () => { - const setup = () => { - Configuration.set('SC_THEME', 'default'); - const user: UserDO = userDoFactory.build(); - const externalTool = externalToolFactory.build(); - const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build(); - const expectDatasheet: ExternalToolDatasheetTemplateData = externalToolDatasheetTemplateDataFactory.build({ - instance: 'dBildungscloud', - }); - - return { user, externalTool, schoolExternalTool, expectDatasheet }; - }; - it('should map correct instance', () => { - const { user, externalTool, schoolExternalTool, expectDatasheet } = setup(); - - const mappedData: ExternalToolDatasheetTemplateData = - ExternalToolDatasheetMapper.mapToExternalToolDatasheetTemplateData( - externalTool, - user.firstName, - user.lastName, - schoolExternalTool - ); - - expect(mappedData).toEqual(expectDatasheet); - }); - }); - - describe('when instance is nbc', () => { - const setup = () => { - Configuration.set('SC_THEME', 'n21'); - const user: UserDO = userDoFactory.build(); - const externalTool = externalToolFactory.build(); - const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build(); - const expectDatasheet: ExternalToolDatasheetTemplateData = externalToolDatasheetTemplateDataFactory.build({ - instance: 'Niedersächsische Bildungscloud', - }); - - return { user, externalTool, schoolExternalTool, expectDatasheet }; - }; - it('should map correct instance', () => { - const { user, externalTool, schoolExternalTool, expectDatasheet } = setup(); - - const mappedData: ExternalToolDatasheetTemplateData = - ExternalToolDatasheetMapper.mapToExternalToolDatasheetTemplateData( - externalTool, - user.firstName, - user.lastName, - schoolExternalTool - ); - - expect(mappedData).toEqual(expectDatasheet); - }); - }); - describe('when custom parameters have types and scopes', () => { const setup = () => { const user: UserDO = userDoFactory.build(); diff --git a/apps/server/src/modules/tool/external-tool/mapper/external-tool-datasheet.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/external-tool-datasheet.mapper.ts index d908925c761..4a336ff604e 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/external-tool-datasheet.mapper.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/external-tool-datasheet.mapper.ts @@ -20,7 +20,7 @@ export class ExternalToolDatasheetMapper { const externalToolData: ExternalToolDatasheetTemplateData = new ExternalToolDatasheetTemplateData({ createdAt: new Date().toLocaleDateString('de-DE'), creatorName: `${firstName} ${lastname}`, - instance: ExternalToolDatasheetMapper.mapToInstanceName(), + instance: ExternalToolDatasheetMapper.getInstanceName(), schoolName, toolName: externalTool.name, toolUrl: externalTool.config.baseUrl, @@ -47,20 +47,10 @@ export class ExternalToolDatasheetMapper { return externalToolData; } - private static mapToInstanceName(): string { - const instance: string = Configuration.get('SC_THEME') as string; - switch (instance) { - case 'n21': - return 'Niedersächsische Bildungscloud'; - case 'brb': - return 'Schul-Cloud Brandenburg'; - case 'thr': - return 'Thüringer Schulcloud'; - case 'default': - return 'dBildungscloud'; - default: - return 'unbekannt'; - } + private static getInstanceName(): string { + const instanceName: string = Configuration.get('SC_TITLE') as string; + + return instanceName; } private static mapToIsDeactivated( diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-logo-service.spec.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-logo-service.spec.ts index c53154098a5..17ae3330aca 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-logo-service.spec.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-logo-service.spec.ts @@ -82,7 +82,7 @@ describe('ExternalToolLogoService', () => { it('should return undefined', () => { const { externalTool, logoUrlTemplate } = setup(); - const logoUrl = service.buildLogoUrl(logoUrlTemplate, externalTool); + const logoUrl = service.buildLogoUrl(externalTool); expect(logoUrl).toBeUndefined(); }); @@ -107,7 +107,7 @@ describe('ExternalToolLogoService', () => { it('should return an internal logoUrl', () => { const { externalTool, logoUrlTemplate, expected } = setup(); - const logoUrl = service.buildLogoUrl(logoUrlTemplate, externalTool); + const logoUrl = service.buildLogoUrl(externalTool); expect(logoUrl).toEqual(expected); }); diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-logo.service.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-logo.service.ts index d065b6d019e..2a274f1a17d 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-logo.service.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-logo.service.ts @@ -31,13 +31,12 @@ export class ExternalToolLogoService { private readonly externalToolService: ExternalToolService ) {} - buildLogoUrl(template: string, externalTool: ExternalTool): string | undefined { + buildLogoUrl(externalTool: ExternalTool): string | undefined { const { logo, id } = externalTool; const backendUrl = this.toolFeatures.backEndUrl; - if (logo) { - const filledTemplate = template.replace(/\{id\}/g, id || ''); - return `${backendUrl}${filledTemplate}`; + if (logo && id) { + return `${backendUrl}/v3/tools/external-tools/${id}/logo`; } return undefined; diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts index 35c26fce9a8..df9969f8fd9 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts @@ -18,10 +18,10 @@ import { import { School, SchoolService } from '@src/modules/school'; import { CustomParameterScope, ToolContextType } from '../../common/enum'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; -import { ContextExternalTool } from '../../context-external-tool/domain'; import { ContextExternalToolService } from '../../context-external-tool'; -import { SchoolExternalTool, SchoolExternalToolWithId } from '../../school-external-tool/domain'; +import { ContextExternalTool } from '../../context-external-tool/domain'; import { SchoolExternalToolService } from '../../school-external-tool'; +import { SchoolExternalTool, SchoolExternalToolWithId } from '../../school-external-tool/domain'; import { ExternalTool } from '../domain'; import { ExternalToolConfigurationService, ExternalToolLogoService, ExternalToolService } from '../service'; import { ExternalToolConfigurationUc } from './external-tool-configuration.uc'; @@ -169,10 +169,7 @@ describe('ExternalToolConfigurationUc', () => { await uc.getAvailableToolsForSchool('userId', 'schoolId'); - expect(logoService.buildLogoUrl).toHaveBeenCalledWith( - '/v3/tools/external-tools/{id}/logo', - externalToolsPage.data[1] - ); + expect(logoService.buildLogoUrl).toHaveBeenCalledWith(externalToolsPage.data[1]); }); it('should call filterForAvailableTools with ids of used tools', async () => { @@ -324,7 +321,7 @@ describe('ExternalToolConfigurationUc', () => { await uc.getAvailableToolsForContext('userId', 'schoolId', 'contextId', ToolContextType.COURSE); - expect(logoService.buildLogoUrl).toHaveBeenCalledWith('/v3/tools/external-tools/{id}/logo', usedTool); + expect(logoService.buildLogoUrl).toHaveBeenCalledWith(usedTool); }); it('should filter for restricted contexts', async () => { diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts index 4f81df0d75d..5937b9d9544 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts @@ -68,10 +68,7 @@ export class ExternalToolConfigurationUc { }); availableTools.forEach((externalTool) => { - externalTool.logoUrl = this.externalToolLogoService.buildLogoUrl( - '/v3/tools/external-tools/{id}/logo', - externalTool - ); + externalTool.logoUrl = this.externalToolLogoService.buildLogoUrl(externalTool); }); return availableTools; @@ -117,10 +114,7 @@ export class ExternalToolConfigurationUc { }); availableToolsForContext.forEach((toolTemplateInfo) => { - toolTemplateInfo.externalTool.logoUrl = this.externalToolLogoService.buildLogoUrl( - '/v3/tools/external-tools/{id}/logo', - toolTemplateInfo.externalTool - ); + toolTemplateInfo.externalTool.logoUrl = this.externalToolLogoService.buildLogoUrl(toolTemplateInfo.externalTool); }); return availableToolsForContext; diff --git a/apps/server/src/modules/user-import/config/user-import-config.ts b/apps/server/src/modules/user-import/config/user-import-config.ts index 52b46a06560..05cd6265a87 100644 --- a/apps/server/src/modules/user-import/config/user-import-config.ts +++ b/apps/server/src/modules/user-import/config/user-import-config.ts @@ -5,13 +5,13 @@ export const UserImportFeatures = Symbol('UserImportFeatures'); export interface IUserImportFeatures { userMigrationEnabled: boolean; userMigrationSystemId: string; - instance: string; + useWithUserLoginMigration: boolean; } export class UserImportConfiguration { static userImportFeatures: IUserImportFeatures = { userMigrationEnabled: Configuration.get('FEATURE_USER_MIGRATION_ENABLED') as boolean, userMigrationSystemId: Configuration.get('FEATURE_USER_MIGRATION_SYSTEM_ID') as string, - instance: Configuration.get('SC_THEME') as string, + useWithUserLoginMigration: Configuration.get('FEATURE_MIGRATION_WIZARD_WITH_USER_LOGIN_MIGRATION') as boolean, }; } diff --git a/apps/server/src/modules/user-import/controller/api-test/import-user-populate.api.spec.ts b/apps/server/src/modules/user-import/controller/api-test/import-user-populate.api.spec.ts new file mode 100644 index 00000000000..4c58eca296c --- /dev/null +++ b/apps/server/src/modules/user-import/controller/api-test/import-user-populate.api.spec.ts @@ -0,0 +1,174 @@ +import { SanisResponse, schulconnexResponseFactory } from '@infra/schulconnex-client'; +import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { OauthTokenResponse } from '@modules/oauth/service/dto'; +import { ServerTestModule } from '@modules/server/server.module'; +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { Permission, RoleName } from '@shared/domain/interface'; +import { SchoolFeature } from '@shared/domain/types'; +import { + accountFactory, + roleFactory, + schoolEntityFactory, + systemEntityFactory, + TestApiClient, + userFactory, +} from '@shared/testing'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import { IUserImportFeatures, UserImportFeatures } from '../../config'; + +describe('ImportUser Controller Populate (API)', () => { + let app: INestApplication; + let em: EntityManager; + + let testApiClient: TestApiClient; + let userImportFeatures: IUserImportFeatures; + let axiosMock: MockAdapter; + + const authenticatedUser = async ( + permissions: Permission[] = [], + features: SchoolFeature[] = [], + schoolHasExternalId = true + ) => { + const system = systemEntityFactory.buildWithId(); + const school = schoolEntityFactory.build({ + officialSchoolNumber: 'foo', + features, + systems: [system], + externalId: schoolHasExternalId ? system.id : undefined, + }); + const roles = [roleFactory.build({ name: RoleName.ADMINISTRATOR, permissions })]; + await em.persistAndFlush([system, school, ...roles]); + const user = userFactory.buildWithId({ roles, school }); + const account = accountFactory.withUser(user).buildWithId(); + await em.persistAndFlush([user, account]); + em.clear(); + return { user, account, roles, school, system }; + }; + + const setConfig = (systemId?: string) => { + userImportFeatures.userMigrationEnabled = true; + userImportFeatures.userMigrationSystemId = systemId || new ObjectId().toString(); + userImportFeatures.useWithUserLoginMigration = 'dbc'; + }; + + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [ServerTestModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + + await app.init(); + + em = app.get(EntityManager); + testApiClient = new TestApiClient(app, 'user/import'); + userImportFeatures = app.get(UserImportFeatures); + axiosMock = new MockAdapter(axios); + }); + + afterAll(async () => { + await app.close(); + }); + + beforeEach(() => { + setConfig(); + }); + + describe('[POST] user/import/populate-import-users', () => { + describe('when user is not authenticated', () => { + const setup = () => { + const notLoggedInClient = new TestApiClient(app, 'user/import'); + + return { notLoggedInClient }; + }; + + it('should return unauthorized', async () => { + const { notLoggedInClient } = setup(); + + await notLoggedInClient.post('populate-import-users').send().expect(HttpStatus.UNAUTHORIZED); + }); + }); + + describe('when migration is not activated', () => { + const setup = async () => { + const { account } = await authenticatedUser([Permission.SCHOOL_IMPORT_USERS_MIGRATE]); + const loggedInClient = await testApiClient.login(account); + + userImportFeatures.userMigrationEnabled = false; + + return { loggedInClient }; + }; + + it('should return with status forbidden', async () => { + const { loggedInClient } = await setup(); + + const response = await loggedInClient.post('populate-import-users').send(); + + expect(response.body).toEqual({ + type: 'USER_MIGRATION_IS_NOT_ENABLED', + title: 'User Migration Is Not Enabled', + message: 'Feature flag of user migration may be disable or the school is not an LDAP pilot', + code: HttpStatus.FORBIDDEN, + }); + }); + }); + + describe('when users school has no external id', () => { + const setup = async () => { + const { account, school, system } = await authenticatedUser( + [Permission.SCHOOL_IMPORT_USERS_MIGRATE], + [], + false + ); + const loggedInClient = await testApiClient.login(account); + userImportFeatures.userMigrationSystemId = system.id; + + school.externalId = undefined; + + return { loggedInClient }; + }; + + it('should return with status bad request', async () => { + const { loggedInClient } = await setup(); + + const response = await loggedInClient.post('populate-import-users').send(); + + expect(response.body).toEqual({ + type: 'USER_IMPORT_SCHOOL_EXTERNAL_ID_MISSING', + title: 'User Import School External Id Missing', + message: 'Bad Request', + code: HttpStatus.BAD_REQUEST, + }); + }); + }); + + describe('when users were populated successful', () => { + const setup = async () => { + const { account, school, system } = await authenticatedUser([Permission.SCHOOL_IMPORT_USERS_MIGRATE]); + const loggedInClient = await testApiClient.login(account); + + userImportFeatures.userMigrationEnabled = true; + userImportFeatures.userMigrationSystemId = system.id; + + axiosMock.onPost(/(.*)\/token/).reply(HttpStatus.OK, { + id_token: 'idToken', + refresh_token: 'refreshToken', + access_token: 'accessToken', + }); + + const schulconnexResponse: SanisResponse = schulconnexResponseFactory.build(); + axiosMock.onGet(/(.*)\/personen-info/).reply(HttpStatus.OK, [schulconnexResponse]); + + return { loggedInClient, account, school }; + }; + + it('should return with status created', async () => { + const { loggedInClient } = await setup(); + + await loggedInClient.post('populate-import-users').send().expect(HttpStatus.CREATED); + }); + }); + }); +}); diff --git a/apps/server/src/modules/user-import/controller/api-test/import-user.api.spec.ts b/apps/server/src/modules/user-import/controller/api-test/import-user.api.spec.ts index 183a4b282d3..782333e04b7 100644 --- a/apps/server/src/modules/user-import/controller/api-test/import-user.api.spec.ts +++ b/apps/server/src/modules/user-import/controller/api-test/import-user.api.spec.ts @@ -1,6 +1,5 @@ -import { SanisResponse, schulconnexResponseFactory } from '@infra/schulconnex-client'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; -import { OauthTokenResponse } from '@modules/oauth/service/dto'; +import { AccountEntity } from '@modules/account/entity/account.entity'; import { ServerTestModule } from '@modules/server/server.module'; import { FilterImportUserParams, @@ -25,19 +24,16 @@ import { ImportUser, MatchCreator, SchoolEntity, SystemEntity, User } from '@sha import { Permission, RoleName, SortOrder } from '@shared/domain/interface'; import { SchoolFeature } from '@shared/domain/types'; import { - TestApiClient, - UserAndAccountTestFactory, accountFactory, cleanupCollections, importUserFactory, roleFactory, schoolEntityFactory, systemEntityFactory, + TestApiClient, + UserAndAccountTestFactory, userFactory, } from '@shared/testing'; -import { AccountEntity } from '@modules/account/entity/account.entity'; -import axios from 'axios'; -import MockAdapter from 'axios-mock-adapter'; import { IUserImportFeatures, UserImportFeatures } from '../../config'; describe('ImportUser Controller (API)', () => { @@ -46,7 +42,6 @@ describe('ImportUser Controller (API)', () => { let testApiClient: TestApiClient; let userImportFeatures: IUserImportFeatures; - let axiosMock: MockAdapter; const authenticatedUser = async ( permissions: Permission[] = [], @@ -72,7 +67,7 @@ describe('ImportUser Controller (API)', () => { const setConfig = (systemId?: string) => { userImportFeatures.userMigrationEnabled = true; userImportFeatures.userMigrationSystemId = systemId || new ObjectId().toString(); - userImportFeatures.instance = 'dbc'; + userImportFeatures.useWithUserLoginMigration = 'dbc'; }; beforeAll(async () => { @@ -87,7 +82,6 @@ describe('ImportUser Controller (API)', () => { em = app.get(EntityManager); testApiClient = new TestApiClient(app, 'user/import'); userImportFeatures = app.get(UserImportFeatures); - axiosMock = new MockAdapter(axios); }); afterAll(async () => { @@ -1123,101 +1117,5 @@ describe('ImportUser Controller (API)', () => { }); }); }); - - describe('[POST] populateImportUsers', () => { - describe('when user is not authenticated', () => { - const setup = () => { - const notLoggedInClient = new TestApiClient(app, 'user/import'); - - return { notLoggedInClient }; - }; - - it('should return unauthorized', async () => { - const { notLoggedInClient } = setup(); - - await notLoggedInClient.post('populate-import-users').send().expect(HttpStatus.UNAUTHORIZED); - }); - }); - - describe('when migration is not activated', () => { - const setup = async () => { - const { account } = await authenticatedUser([Permission.SCHOOL_IMPORT_USERS_MIGRATE]); - const loggedInClient = await testApiClient.login(account); - - userImportFeatures.userMigrationEnabled = false; - - return { loggedInClient }; - }; - - it('should return with status forbidden', async () => { - const { loggedInClient } = await setup(); - - const response = await loggedInClient.post('populate-import-users').send(); - - expect(response.body).toEqual({ - type: 'USER_MIGRATION_IS_NOT_ENABLED', - title: 'User Migration Is Not Enabled', - message: 'Feature flag of user migration may be disable or the school is not an LDAP pilot', - code: HttpStatus.FORBIDDEN, - }); - }); - }); - - describe('when users school has no external id', () => { - const setup = async () => { - const { account, school, system } = await authenticatedUser( - [Permission.SCHOOL_IMPORT_USERS_MIGRATE], - [], - false - ); - const loggedInClient = await testApiClient.login(account); - userImportFeatures.userMigrationSystemId = system.id; - - school.externalId = undefined; - - return { loggedInClient }; - }; - - it('should return with status bad request', async () => { - const { loggedInClient } = await setup(); - - const response = await loggedInClient.post('populate-import-users').send(); - - expect(response.body).toEqual({ - type: 'USER_IMPORT_SCHOOL_EXTERNAL_ID_MISSING', - title: 'User Import School External Id Missing', - message: 'Bad Request', - code: HttpStatus.BAD_REQUEST, - }); - }); - }); - - describe('when users were populated successful', () => { - const setup = async () => { - const { account, school, system } = await authenticatedUser([Permission.SCHOOL_IMPORT_USERS_MIGRATE]); - const loggedInClient = await testApiClient.login(account); - - userImportFeatures.userMigrationEnabled = true; - userImportFeatures.userMigrationSystemId = system.id; - - axiosMock.onPost(/(.*)\/token/).reply(HttpStatus.OK, { - id_token: 'idToken', - refresh_token: 'refreshToken', - access_token: 'accessToken', - }); - - const schulconnexResponse: SanisResponse = schulconnexResponseFactory.build(); - axiosMock.onGet(/(.*)\/personen-info/).reply(HttpStatus.OK, [schulconnexResponse]); - - return { loggedInClient, account, school }; - }; - - it('should return with status created', async () => { - const { loggedInClient } = await setup(); - - await loggedInClient.post('populate-import-users').send().expect(HttpStatus.CREATED); - }); - }); - }); }); }); diff --git a/apps/server/src/modules/user-import/service/index.ts b/apps/server/src/modules/user-import/service/index.ts index 3e9ef6f5466..823799271f6 100644 --- a/apps/server/src/modules/user-import/service/index.ts +++ b/apps/server/src/modules/user-import/service/index.ts @@ -1,2 +1,2 @@ -export { SchulconnexFetchImportUsersService } from './strategy/schulconnex-fetch-import-users.service'; +export { SchulconnexFetchImportUsersService } from './schulconnex-fetch-import-users.service'; export { UserImportService } from './user-import.service'; diff --git a/apps/server/src/modules/user-import/service/strategy/schulconnex-fetch-import-users.service.spec.ts b/apps/server/src/modules/user-import/service/schulconnex-fetch-import-users.service.spec.ts similarity index 99% rename from apps/server/src/modules/user-import/service/strategy/schulconnex-fetch-import-users.service.spec.ts rename to apps/server/src/modules/user-import/service/schulconnex-fetch-import-users.service.spec.ts index 17746b39d0e..f1d2df711f1 100644 --- a/apps/server/src/modules/user-import/service/strategy/schulconnex-fetch-import-users.service.spec.ts +++ b/apps/server/src/modules/user-import/service/schulconnex-fetch-import-users.service.spec.ts @@ -12,7 +12,7 @@ import { systemEntityFactory, userDoFactory, } from '@shared/testing'; -import { UserImportSchoolExternalIdMissingLoggableException } from '../../loggable'; +import { UserImportSchoolExternalIdMissingLoggableException } from '../loggable'; import { SchulconnexFetchImportUsersService } from './schulconnex-fetch-import-users.service'; describe(SchulconnexFetchImportUsersService.name, () => { diff --git a/apps/server/src/modules/user-import/service/strategy/schulconnex-fetch-import-users.service.ts b/apps/server/src/modules/user-import/service/schulconnex-fetch-import-users.service.ts similarity index 95% rename from apps/server/src/modules/user-import/service/strategy/schulconnex-fetch-import-users.service.ts rename to apps/server/src/modules/user-import/service/schulconnex-fetch-import-users.service.ts index 29992e79e99..1c2b2f2e184 100644 --- a/apps/server/src/modules/user-import/service/strategy/schulconnex-fetch-import-users.service.ts +++ b/apps/server/src/modules/user-import/service/schulconnex-fetch-import-users.service.ts @@ -4,8 +4,8 @@ import { Injectable } from '@nestjs/common'; import { UserDO } from '@shared/domain/domainobject'; import { ImportUser, SchoolEntity, SystemEntity } from '@shared/domain/entity'; import { EntityId } from '@shared/domain/types'; -import { UserImportSchoolExternalIdMissingLoggableException } from '../../loggable'; -import { SchulconnexImportUserMapper } from '../../mapper'; +import { UserImportSchoolExternalIdMissingLoggableException } from '../loggable'; +import { SchulconnexImportUserMapper } from '../mapper'; @Injectable() export class SchulconnexFetchImportUsersService { diff --git a/apps/server/src/modules/user-import/service/user-import.service.spec.ts b/apps/server/src/modules/user-import/service/user-import.service.spec.ts index d5f87f703f9..67efc3aec62 100644 --- a/apps/server/src/modules/user-import/service/user-import.service.spec.ts +++ b/apps/server/src/modules/user-import/service/user-import.service.spec.ts @@ -35,7 +35,7 @@ describe(UserImportService.name, () => { const features: IUserImportFeatures = { userMigrationSystemId: new ObjectId().toHexString(), userMigrationEnabled: true, - instance: 'n21', + useWithUserLoginMigration: 'n21', }; beforeAll(async () => { diff --git a/apps/server/src/modules/user-import/uc/user-import-fetch.uc.spec.ts b/apps/server/src/modules/user-import/uc/user-import-fetch.uc.spec.ts index 057b35d3d77..c80032261e7 100644 --- a/apps/server/src/modules/user-import/uc/user-import-fetch.uc.spec.ts +++ b/apps/server/src/modules/user-import/uc/user-import-fetch.uc.spec.ts @@ -55,7 +55,7 @@ describe(UserImportFetchUc.name, () => { Object.assign(userImportFeatures, { userMigrationEnabled: true, userMigrationSystemId: new ObjectId().toHexString(), - instance: 'n21', + useWithUserLoginMigration: true, }); }); diff --git a/apps/server/src/modules/user-import/uc/user-import-fetch.uc.ts b/apps/server/src/modules/user-import/uc/user-import-fetch.uc.ts index 6a635b93a54..0fe21ba4d35 100644 --- a/apps/server/src/modules/user-import/uc/user-import-fetch.uc.ts +++ b/apps/server/src/modules/user-import/uc/user-import-fetch.uc.ts @@ -4,7 +4,7 @@ import { ImportUser, SystemEntity, User } from '@shared/domain/entity'; import { Permission } from '@shared/domain/interface'; import { EntityId } from '@shared/domain/types'; import { IUserImportFeatures, UserImportFeatures } from '../config'; -import { UserMigrationIsNotEnabledLoggableException } from '../loggable/user-migration-not-enable-loggable-exception'; +import { UserMigrationIsNotEnabledLoggableException } from '../loggable'; import { SchulconnexFetchImportUsersService, UserImportService } from '../service'; @Injectable() diff --git a/apps/server/src/modules/user-import/uc/user-import.uc.spec.ts b/apps/server/src/modules/user-import/uc/user-import.uc.spec.ts index 98bfb923a30..4be2a40dc74 100644 --- a/apps/server/src/modules/user-import/uc/user-import.uc.spec.ts +++ b/apps/server/src/modules/user-import/uc/user-import.uc.spec.ts @@ -1,6 +1,6 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; -import { AccountService, Account } from '@modules/account'; +import { Account, AccountService } from '@modules/account'; import { AuthorizationService } from '@modules/authorization'; import { LegacySchoolService } from '@modules/legacy-school'; import { UserLoginMigrationService, UserMigrationService } from '@modules/user-login-migration'; @@ -120,7 +120,7 @@ describe('[ImportUserModule]', () => { Object.assign(userImportFeatures, { userMigrationEnabled: true, userMigrationSystemId: new ObjectId().toHexString(), - instance: 'dbc', + useWithUserLoginMigration: false, }); }); @@ -638,7 +638,7 @@ describe('[ImportUserModule]', () => { userRepo.findById.mockResolvedValueOnce(user); schoolService.getSchoolById.mockResolvedValueOnce(school); importUserRepo.findImportUsers.mockResolvedValueOnce([[importUser, importUserWithoutUser], 2]); - userImportFeatures.instance = 'n21'; + userImportFeatures.useWithUserLoginMigration = true; return { user, @@ -847,7 +847,7 @@ describe('[ImportUserModule]', () => { targetSystemId, }); - userImportFeatures.instance = 'n21'; + userImportFeatures.useWithUserLoginMigration = true; userRepo.findById.mockResolvedValueOnce(user); schoolService.getSchoolById.mockResolvedValueOnce(school); userLoginMigrationService.findMigrationBySchool.mockResolvedValueOnce(userLoginMigration); @@ -892,7 +892,7 @@ describe('[ImportUserModule]', () => { systems: [targetSystemId], }); - userImportFeatures.instance = 'n21'; + userImportFeatures.useWithUserLoginMigration = true; userRepo.findById.mockResolvedValueOnce(user); schoolService.getSchoolById.mockResolvedValueOnce(school); userLoginMigrationService.findMigrationBySchool.mockResolvedValueOnce(null); @@ -925,7 +925,7 @@ describe('[ImportUserModule]', () => { targetSystemId, }); - userImportFeatures.instance = 'n21'; + userImportFeatures.useWithUserLoginMigration = true; userRepo.findById.mockResolvedValueOnce(user); schoolService.getSchoolById.mockResolvedValueOnce(school); userLoginMigrationService.findMigrationBySchool.mockResolvedValueOnce(userLoginMigration); @@ -1022,7 +1022,7 @@ describe('[ImportUserModule]', () => { userRepo.findById.mockResolvedValueOnce(user); schoolService.getSchoolById.mockResolvedValueOnce(school); - userImportFeatures.instance = 'n21'; + userImportFeatures.useWithUserLoginMigration = true; return { user, diff --git a/apps/server/src/modules/user-import/uc/user-import.uc.ts b/apps/server/src/modules/user-import/uc/user-import.uc.ts index 84c5b10c288..6352a122930 100644 --- a/apps/server/src/modules/user-import/uc/user-import.uc.ts +++ b/apps/server/src/modules/user-import/uc/user-import.uc.ts @@ -1,4 +1,4 @@ -import { AccountService, Account, AccountSave } from '@modules/account'; +import { Account, AccountSave, AccountService } from '@modules/account'; import { AuthorizationService } from '@modules/authorization'; import { LegacySchoolService } from '@modules/legacy-school'; import { UserLoginMigrationService, UserMigrationService } from '@modules/user-login-migration'; @@ -230,7 +230,7 @@ export class UserImportUc { } async startSchoolInUserMigration(currentUserId: EntityId, useCentralLdap = true): Promise { - const useWithUserLoginMigration: boolean = this.isNbc(); + const { useWithUserLoginMigration } = this.userImportFeatures; if (useWithUserLoginMigration) { useCentralLdap = false; @@ -297,7 +297,7 @@ export class UserImportUc { school.inMaintenanceSince = undefined; - const isMigrationRestartable: boolean = this.isNbc(); + const isMigrationRestartable: boolean = this.userImportFeatures.useWithUserLoginMigration; if (isMigrationRestartable) { school.inUserMigration = undefined; } @@ -315,7 +315,7 @@ export class UserImportUc { } private async updateUserAndAccount(importUser: ImportUser, school: LegacySchoolDo): Promise { - const useWithUserLoginMigration: boolean = this.isNbc(); + const { useWithUserLoginMigration } = this.userImportFeatures; if (useWithUserLoginMigration) { await this.updateUserAndAccountWithUserLoginMigration(importUser); @@ -394,8 +394,4 @@ export class UserImportUc { throw new MigrationAlreadyActivatedException(); } } - - private isNbc(): boolean { - return this.userImportFeatures.instance === 'n21'; - } } diff --git a/apps/server/src/shared/domain/entity/external-source.entity.ts b/apps/server/src/shared/domain/entity/external-source.embeddable.ts similarity index 91% rename from apps/server/src/shared/domain/entity/external-source.entity.ts rename to apps/server/src/shared/domain/entity/external-source.embeddable.ts index 916ae4f02b7..4c5dfe2ac20 100644 --- a/apps/server/src/shared/domain/entity/external-source.entity.ts +++ b/apps/server/src/shared/domain/entity/external-source.embeddable.ts @@ -8,7 +8,7 @@ export interface ExternalSourceEntityProps { } @Embeddable() -export class ExternalSourceEntity { +export class ExternalSourceEmbeddable { @Property() externalId: string; diff --git a/apps/server/src/shared/domain/entity/index.ts b/apps/server/src/shared/domain/entity/index.ts index 619f345d671..64ce19e2871 100644 --- a/apps/server/src/shared/domain/entity/index.ts +++ b/apps/server/src/shared/domain/entity/index.ts @@ -23,5 +23,5 @@ export * from './team.entity'; export * from './user-login-migration.entity'; export * from './user.entity'; export * from './video-conference.entity'; -export * from './external-source.entity'; +export * from './external-source.embeddable'; export * from './consent'; diff --git a/apps/server/src/shared/testing/factory/group-entity.factory.ts b/apps/server/src/shared/testing/factory/group-entity.factory.ts index 9019f145784..4ce7883317e 100644 --- a/apps/server/src/shared/testing/factory/group-entity.factory.ts +++ b/apps/server/src/shared/testing/factory/group-entity.factory.ts @@ -1,5 +1,5 @@ -import { GroupEntity, GroupEntityProps, GroupEntityTypes, GroupValidPeriodEntity } from '@modules/group/entity'; -import { ExternalSourceEntity } from '@shared/domain/entity'; +import { GroupEntity, GroupEntityProps, GroupEntityTypes, GroupValidPeriodEmbeddable } from '@modules/group/entity'; +import { ExternalSourceEmbeddable } from '@shared/domain/entity'; import { RoleName } from '@shared/domain/interface'; import { BaseFactory } from './base.factory'; import { roleFactory } from './role.factory'; @@ -21,12 +21,12 @@ export const groupEntityFactory = BaseFactory.define