From 3e9422531f3da0fdce3cf1ce82928c024db0e359 Mon Sep 17 00:00:00 2001 From: Igor Richter Date: Wed, 17 Jul 2024 16:12:00 +0200 Subject: [PATCH 01/20] - data model changes - TODO: add lastSyncAt in unit tests --- apps/server/src/modules/group/entity/group.entity.ts | 8 ++------ apps/server/src/modules/group/repo/group-domain.mapper.ts | 2 ++ .../src/shared/domain/domainobject/external-source.ts | 3 +++ .../shared/domain/entity/external-source.embeddable.ts | 6 ++++++ .../testing/factory/domainobject/groups/group.factory.ts | 3 ++- .../src/shared/testing/factory/group-entity.factory.ts | 1 + 6 files changed, 16 insertions(+), 7 deletions(-) diff --git a/apps/server/src/modules/group/entity/group.entity.ts b/apps/server/src/modules/group/entity/group.entity.ts index bae4578cbe3..01c41e17843 100644 --- a/apps/server/src/modules/group/entity/group.entity.ts +++ b/apps/server/src/modules/group/entity/group.entity.ts @@ -1,5 +1,4 @@ -import { Collection, Embedded, Entity, Enum, ManyToOne, OneToMany, Property } from '@mikro-orm/core'; -import { Course as CourseEntity } from '@shared/domain/entity'; +import { Embedded, Entity, Enum, ManyToOne, Property } from '@mikro-orm/core'; import { BaseEntityWithTimestamps } from '@shared/domain/entity/base.entity'; import { ExternalSourceEmbeddable } from '@shared/domain/entity/external-source.embeddable'; import { SchoolEntity } from '@shared/domain/entity/school.entity'; @@ -37,7 +36,7 @@ export class GroupEntity extends BaseEntityWithTimestamps { @Enum() type: GroupEntityTypes; - @Embedded(() => ExternalSourceEmbeddable, { nullable: true }) + @Embedded(() => ExternalSourceEmbeddable, { nullable: true, object: true }) externalSource?: ExternalSourceEmbeddable; @Embedded(() => GroupValidPeriodEmbeddable, { nullable: true }) @@ -49,9 +48,6 @@ export class GroupEntity extends BaseEntityWithTimestamps { @ManyToOne(() => SchoolEntity, { nullable: true }) organization?: SchoolEntity; - @OneToMany(() => CourseEntity, (course: CourseEntity) => course.syncedWithGroup) - syncedCourses: Collection = new Collection(this); - constructor(props: GroupEntityProps) { super(); if (props.id) { 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 c63c83bbab6..f366ca69f8d 100644 --- a/apps/server/src/modules/group/repo/group-domain.mapper.ts +++ b/apps/server/src/modules/group/repo/group-domain.mapper.ts @@ -70,6 +70,7 @@ export class GroupDomainMapper { const externalSourceEntity: ExternalSourceEmbeddable = new ExternalSourceEmbeddable({ externalId: externalSource.externalId, system: em.getReference(SystemEntity, externalSource.systemId), + lastSyncedAt: externalSource.lastSyncedAt, }); return externalSourceEntity; @@ -79,6 +80,7 @@ export class GroupDomainMapper { const externalSource: ExternalSource = new ExternalSource({ externalId: entity.externalId, systemId: entity.system.id, + lastSyncedAt: entity.lastSyncedAt, }); return externalSource; diff --git a/apps/server/src/shared/domain/domainobject/external-source.ts b/apps/server/src/shared/domain/domainobject/external-source.ts index c29c9f5ae24..5682fee5005 100644 --- a/apps/server/src/shared/domain/domainobject/external-source.ts +++ b/apps/server/src/shared/domain/domainobject/external-source.ts @@ -3,8 +3,11 @@ export class ExternalSource { systemId: string; + lastSyncedAt: Date; + constructor(props: ExternalSource) { this.externalId = props.externalId; this.systemId = props.systemId; + this.lastSyncedAt = props.lastSyncedAt; } } diff --git a/apps/server/src/shared/domain/entity/external-source.embeddable.ts b/apps/server/src/shared/domain/entity/external-source.embeddable.ts index 6326308a867..568b8030770 100644 --- a/apps/server/src/shared/domain/entity/external-source.embeddable.ts +++ b/apps/server/src/shared/domain/entity/external-source.embeddable.ts @@ -5,6 +5,8 @@ export interface ExternalSourceEntityProps { externalId: string; system: SystemEntity; + + lastSyncedAt: Date; } @Embeddable() @@ -15,8 +17,12 @@ export class ExternalSourceEmbeddable { @ManyToOne(() => SystemEntity) system: SystemEntity; + @Property() + lastSyncedAt: Date; + constructor(props: ExternalSourceEntityProps) { this.externalId = props.externalId; this.system = props.system; + this.lastSyncedAt = props.lastSyncedAt; } } diff --git a/apps/server/src/shared/testing/factory/domainobject/groups/group.factory.ts b/apps/server/src/shared/testing/factory/domainobject/groups/group.factory.ts index 8a672648e43..6d0ed928090 100644 --- a/apps/server/src/shared/testing/factory/domainobject/groups/group.factory.ts +++ b/apps/server/src/shared/testing/factory/domainobject/groups/group.factory.ts @@ -1,6 +1,6 @@ +import { ObjectId } from '@mikro-orm/mongodb'; import { Group, GroupProps, GroupTypes } from '@modules/group/domain'; import { ExternalSource } from '@shared/domain/domainobject'; -import { ObjectId } from '@mikro-orm/mongodb'; import { DomainObjectFactory } from '../domain-object.factory'; export const groupFactory = DomainObjectFactory.define(Group, ({ sequence }) => { @@ -20,6 +20,7 @@ export const groupFactory = DomainObjectFactory.define(Group, externalSource: new ExternalSource({ externalId: `externalId-${sequence}`, systemId: new ObjectId().toHexString(), + lastSyncedAt: new Date(), }), }; }); 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 4ce7883317e..a78b68c7263 100644 --- a/apps/server/src/shared/testing/factory/group-entity.factory.ts +++ b/apps/server/src/shared/testing/factory/group-entity.factory.ts @@ -29,6 +29,7 @@ export const groupEntityFactory = BaseFactory.define Date: Wed, 17 Jul 2024 16:51:44 +0200 Subject: [PATCH 02/20] - update lastSyncedAt when provisioning group - fix tests --- .../schulconnex-group-provisioning.service.spec.ts | 8 ++++++++ .../service/schulconnex-group-provisioning.service.ts | 1 + 2 files changed, 9 insertions(+) diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts index 97f2a2afd42..b432ee145b3 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts @@ -444,6 +444,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: { externalId: externalGroupDto.externalId, systemId, + lastSyncedAt: expect.any(Date), }, type: externalGroupDto.type, organizationId: school.id, @@ -479,6 +480,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: { externalId: externalGroupDto.externalId, systemId, + lastSyncedAt: expect.any(Date), }, type: externalGroupDto.type, organizationId: school.id, @@ -547,6 +549,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: { externalId: externalGroupDto.externalId, systemId, + lastSyncedAt: expect.any(Date), }, type: externalGroupDto.type, organizationId: undefined, @@ -616,6 +619,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: { externalId: externalGroupDto.externalId, systemId, + lastSyncedAt: expect.any(Date), }, type: externalGroupDto.type, organizationId: undefined, @@ -704,6 +708,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: new ExternalSource({ externalId: 'externalId-1', systemId, + lastSyncedAt: new Date(), }), }); const secondExistingGroup: Group = groupFactory.build({ @@ -711,6 +716,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: new ExternalSource({ externalId: 'externalId-2', systemId, + lastSyncedAt: new Date(), }), }); const existingGroups = [firstExistingGroup, secondExistingGroup]; @@ -778,6 +784,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: new ExternalSource({ externalId: `externalId-1`, systemId, + lastSyncedAt: new Date(), }), }); @@ -789,6 +796,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: new ExternalSource({ externalId: `externalId-2`, systemId, + lastSyncedAt: new Date(), }), }); diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts index a25c8c4c6c7..60dd5cc838a 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts @@ -103,6 +103,7 @@ export class SchulconnexGroupProvisioningService { externalSource: new ExternalSource({ externalId: externalGroup.externalId, systemId, + lastSyncedAt: new Date(), }), type: externalGroup.type, organizationId, From be7e127762efd7d20a0b22fac6434b66b5e56196 Mon Sep 17 00:00:00 2001 From: Steliyan Dinkov Date: Fri, 19 Jul 2024 10:51:16 +0200 Subject: [PATCH 03/20] update group deletion + sync course logic --- apps/server/src/modules/group/uc/class-group.uc.spec.ts | 2 +- apps/server/src/modules/group/uc/class-group.uc.ts | 2 +- apps/server/src/modules/learnroom/index.ts | 1 + .../oidc/service/schulconnex-course-sync.service.spec.ts | 2 +- .../oidc/service/schulconnex-course-sync.service.ts | 3 +-- .../service/schulconnex-group-provisioning.service.ts | 8 +++++++- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/server/src/modules/group/uc/class-group.uc.spec.ts b/apps/server/src/modules/group/uc/class-group.uc.spec.ts index 1a9bb2c0abf..c4e78af4fca 100644 --- a/apps/server/src/modules/group/uc/class-group.uc.spec.ts +++ b/apps/server/src/modules/group/uc/class-group.uc.spec.ts @@ -5,8 +5,8 @@ import { ClassService } from '@modules/class'; import { Class } from '@modules/class/domain'; import { classFactory } from '@modules/class/domain/testing/factory/class.factory'; import { ClassGroupUc } from '@modules/group/uc/class-group.uc'; +import { CourseDoService } from '@modules/learnroom'; import { Course } from '@modules/learnroom/domain'; -import { CourseDoService } from '@modules/learnroom/service/course-do.service'; import { courseFactory } from '@modules/learnroom/testing'; import { SchoolYearService } from '@modules/legacy-school'; import { ProvisioningConfig } from '@modules/provisioning'; diff --git a/apps/server/src/modules/group/uc/class-group.uc.ts b/apps/server/src/modules/group/uc/class-group.uc.ts index 767591735fe..f763266c416 100644 --- a/apps/server/src/modules/group/uc/class-group.uc.ts +++ b/apps/server/src/modules/group/uc/class-group.uc.ts @@ -1,8 +1,8 @@ import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; import { ClassService } from '@modules/class'; import { Class } from '@modules/class/domain'; +import { CourseDoService } from '@modules/learnroom'; import { Course } from '@modules/learnroom/domain'; -import { CourseDoService } from '@modules/learnroom/service/course-do.service'; import { SchoolYearService } from '@modules/legacy-school'; import { ProvisioningConfig } from '@modules/provisioning'; import { School, SchoolService } from '@modules/school/domain'; diff --git a/apps/server/src/modules/learnroom/index.ts b/apps/server/src/modules/learnroom/index.ts index 0db918062ac..16d9f25c7c7 100644 --- a/apps/server/src/modules/learnroom/index.ts +++ b/apps/server/src/modules/learnroom/index.ts @@ -5,6 +5,7 @@ export { CourseCopyService, CourseGroupService, CourseService, + CourseDoService, DashboardService, RoomsService, } from './service'; diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-course-sync.service.spec.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-course-sync.service.spec.ts index 881822cdea6..6258c798379 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-course-sync.service.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-course-sync.service.spec.ts @@ -1,8 +1,8 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; import { Group, GroupUser } from '@modules/group'; +import { CourseDoService } from '@modules/learnroom'; import { Course } from '@modules/learnroom/domain'; -import { CourseDoService } from '@modules/learnroom/service/course-do.service'; import { courseFactory } from '@modules/learnroom/testing'; import { RoleDto, RoleService } from '@modules/role'; import { Test, TestingModule } from '@nestjs/testing'; diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-course-sync.service.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-course-sync.service.ts index 04049de68aa..a27a518607f 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-course-sync.service.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-course-sync.service.ts @@ -1,5 +1,5 @@ import { Group, GroupUser } from '@modules/group'; -import { CourseDoService } from '@modules/learnroom/service/course-do.service'; +import { CourseDoService } from '@modules/learnroom'; import { RoleDto, RoleService } from '@modules/role'; import { Injectable } from '@nestjs/common'; import { RoleName } from '@shared/domain/interface'; @@ -38,7 +38,6 @@ export class SchulconnexCourseSyncService { } else { // Remove all remaining students and break the link, when the last teacher of the group should be removed course.students = []; - course.syncedWithGroup = undefined; } }); diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts index 60dd5cc838a..dcdfdcc9fa3 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts @@ -1,5 +1,6 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { Group, GroupFilter, GroupService, GroupTypes, GroupUser } from '@modules/group'; +import { CourseDoService } from '@modules/learnroom'; import { LegacySchoolService, SchoolSystemOptionsService, @@ -14,6 +15,7 @@ import { EntityId } from '@shared/domain/types'; import { Logger } from '@src/core/logger'; import { ExternalGroupDto, ExternalGroupUserDto, ExternalSchoolDto } from '../../../dto'; import { SchoolForGroupNotFoundLoggable, UserForGroupNotFoundLoggable } from '../../../loggable'; +import { Course } from '../../../../learnroom/domain'; @Injectable() export class SchulconnexGroupProvisioningService { @@ -22,6 +24,7 @@ export class SchulconnexGroupProvisioningService { private readonly schoolService: LegacySchoolService, private readonly roleService: RoleService, private readonly groupService: GroupService, + private readonly courseService: CourseDoService, private readonly schoolSystemOptionsService: SchoolSystemOptionsService, private readonly logger: Logger ) {} @@ -197,7 +200,10 @@ export class SchulconnexGroupProvisioningService { group.removeUser(user); if (group.isEmpty()) { - await this.groupService.delete(group); + const courses: Course[] = await this.courseService.findBySyncedGroup(group); + if (!courses) { + await this.groupService.delete(group); + } return null; } From f9f9c4032f54f037b37df889a102fcb9549de4fd Mon Sep 17 00:00:00 2001 From: Steliyan Dinkov Date: Fri, 19 Jul 2024 15:44:46 +0200 Subject: [PATCH 04/20] update group provisioning + tests --- ...lconnex-group-provisioning.service.spec.ts | 78 ++++++++++++++++++- .../schulconnex-group-provisioning.service.ts | 2 +- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts index b432ee145b3..db2c3f279d4 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts @@ -1,6 +1,9 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; import { Group, GroupService, GroupTypes } from '@modules/group'; +import { CourseDoService } from '@modules/learnroom'; +import { Course } from '@modules/learnroom/domain'; +import { courseFactory } from '@modules/learnroom/testing'; import { LegacySchoolService, SchoolSystemOptionsService, @@ -35,6 +38,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { let schoolService: DeepMocked; let roleService: DeepMocked; let groupService: DeepMocked; + let courseService: DeepMocked; let schoolSystemOptionsService: DeepMocked; let logger: DeepMocked; @@ -58,6 +62,10 @@ describe(SchulconnexGroupProvisioningService.name, () => { provide: GroupService, useValue: createMock(), }, + { + provide: CourseDoService, + useValue: createMock(), + }, { provide: SchoolSystemOptionsService, useValue: createMock(), @@ -74,6 +82,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { schoolService = module.get(LegacySchoolService); roleService = module.get(RoleService); groupService = module.get(GroupService); + courseService = module.get(CourseDoService); schoolSystemOptionsService = module.get(SchoolSystemOptionsService); logger = module.get(Logger); }); @@ -696,13 +705,12 @@ describe(SchulconnexGroupProvisioningService.name, () => { }); describe('when user is not part of a group anymore', () => { - describe('when group is empty after removal of the User', () => { + describe('when group is empty after removal of the User and is not synced to a course', () => { const setup = () => { const systemId = 'systemId'; const externalUserId = 'externalUserId'; const role: RoleReference = roleFactory.buildWithId(); const user: UserDO = userDoFactory.buildWithId({ roles: [role], externalId: externalUserId }); - const firstExistingGroup: Group = groupFactory.build({ users: [{ userId: user.id as string, roleId: role.id }], externalSource: new ExternalSource({ @@ -729,7 +737,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { userService.findByExternalId.mockResolvedValue(user); groupService.findGroups.mockResolvedValue(new Page(existingGroups, 2)); - + courseService.findBySyncedGroup.mockResolvedValue([]); return { externalGroups, systemId, @@ -766,6 +774,69 @@ describe(SchulconnexGroupProvisioningService.name, () => { expect(result).toHaveLength(0); }); }); + describe('when group is empty after removal of the User but synced with a course', () => { + const setup = () => { + const systemId = 'systemId'; + const externalUserId = 'externalUserId'; + const role: RoleReference = roleFactory.buildWithId(); + const user: UserDO = userDoFactory.buildWithId({ roles: [role], externalId: externalUserId }); + const course: Course = courseFactory.build(); + + const firstExistingGroup: Group = groupFactory.build({ + users: [{ userId: user.id as string, roleId: role.id }], + externalSource: new ExternalSource({ + externalId: 'externalId-1', + systemId, + lastSyncedAt: new Date(), + }), + }); + const secondExistingGroup: Group = groupFactory.build({ + users: [{ userId: user.id as string, roleId: role.id }], + externalSource: new ExternalSource({ + externalId: 'externalId-2', + systemId, + lastSyncedAt: new Date(), + }), + }); + const existingGroups = [firstExistingGroup, secondExistingGroup]; + + const firstExternalGroup: ExternalGroupDto = externalGroupDtoFactory.build({ + externalId: existingGroups[0].externalSource?.externalId, + user: { externalUserId, roleName: role.name }, + }); + const externalGroups: ExternalGroupDto[] = [firstExternalGroup]; + + userService.findByExternalId.mockResolvedValue(user); + groupService.findGroups.mockResolvedValue(new Page(existingGroups, 2)); + courseService.findBySyncedGroup.mockResolvedValue([new Course(course.getProps())]); + return { + externalGroups, + systemId, + externalUserId, + existingGroups, + }; + }; + it('should not delete the group', async () => { + const { externalGroups, systemId, externalUserId } = setup(); + + await service.removeExternalGroupsAndAffiliation(externalUserId, externalGroups, systemId); + + expect(groupService.delete).not.toHaveBeenCalled(); + }); + + it('should save the groups', async () => { + const { externalGroups, systemId, externalUserId } = setup(); + + const result: Group[] = await service.removeExternalGroupsAndAffiliation( + externalUserId, + externalGroups, + systemId + ); + expect(groupService.delete).not.toHaveBeenCalled(); + + expect(result).toHaveLength(0); + }); + }); describe('when group is not empty after removal of the User', () => { const setup = () => { @@ -836,7 +907,6 @@ describe(SchulconnexGroupProvisioningService.name, () => { expect(groupService.delete).not.toHaveBeenCalled(); }); - it('should return the modified groups', async () => { const { externalGroups, systemId, externalUserId, secondExistingGroup } = setup(); diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts index dcdfdcc9fa3..83ba1da755c 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts @@ -201,7 +201,7 @@ export class SchulconnexGroupProvisioningService { if (group.isEmpty()) { const courses: Course[] = await this.courseService.findBySyncedGroup(group); - if (!courses) { + if (!courses || courses.length === 0) { await this.groupService.delete(group); } return null; From a722a70d095e4027c8cc7cd6f6826cac9e19320d Mon Sep 17 00:00:00 2001 From: Steliyan Dinkov Date: Fri, 19 Jul 2024 16:15:35 +0200 Subject: [PATCH 05/20] update course/group-provisioning tests --- .../schulconnex-course-sync.service.spec.ts | 10 +++++++--- ...schulconnex-group-provisioning.service.spec.ts | 15 +-------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-course-sync.service.spec.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-course-sync.service.spec.ts index 6258c798379..28b40ed5b8b 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-course-sync.service.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-course-sync.service.spec.ts @@ -181,7 +181,11 @@ describe(SchulconnexCourseSyncService.name, () => { const setup = () => { const studentUserId = new ObjectId().toHexString(); const teacherUserId = new ObjectId().toHexString(); - const course: Course = courseFactory.build({ teacherIds: [teacherUserId], studentIds: [studentUserId] }); + const course: Course = courseFactory.build({ + teacherIds: [teacherUserId], + studentIds: [studentUserId], + syncedWithGroup: new ObjectId().toHexString(), + }); const studentRoleId: string = new ObjectId().toHexString(); const studentRole: RoleDto = roleDtoFactory.build({ id: studentRoleId }); const teacherRole: RoleDto = roleDtoFactory.build(); @@ -205,7 +209,7 @@ describe(SchulconnexCourseSyncService.name, () => { }; }; - it('should keep the last teacher, remove all students and break the sync with the group', async () => { + it('should keep the last teacher, remove all students', async () => { const { course, newGroup, teacherUserId } = setup(); await service.synchronizeCourseWithGroup(newGroup, newGroup); @@ -218,7 +222,7 @@ describe(SchulconnexCourseSyncService.name, () => { untilDate: newGroup.validUntil, studentIds: [], teacherIds: [teacherUserId], - syncedWithGroup: undefined, + syncedWithGroup: course.syncedWithGroup, }), ]); }); diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts index db2c3f279d4..6b5d02bf2b1 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts @@ -808,7 +808,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { userService.findByExternalId.mockResolvedValue(user); groupService.findGroups.mockResolvedValue(new Page(existingGroups, 2)); - courseService.findBySyncedGroup.mockResolvedValue([new Course(course.getProps())]); + courseService.findBySyncedGroup.mockResolvedValue([course]); return { externalGroups, systemId, @@ -823,19 +823,6 @@ describe(SchulconnexGroupProvisioningService.name, () => { expect(groupService.delete).not.toHaveBeenCalled(); }); - - it('should save the groups', async () => { - const { externalGroups, systemId, externalUserId } = setup(); - - const result: Group[] = await service.removeExternalGroupsAndAffiliation( - externalUserId, - externalGroups, - systemId - ); - expect(groupService.delete).not.toHaveBeenCalled(); - - expect(result).toHaveLength(0); - }); }); describe('when group is not empty after removal of the User', () => { From 35ae019318da8c81c804af8653c7608e9b5464a1 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Tue, 23 Jul 2024 09:51:08 +0200 Subject: [PATCH 06/20] N21-1824 migration script --- .../mikro-orm/Migration20240719115036.ts | 54 ++ backup/setup/groups.json | 468 ++++++++++-------- 2 files changed, 312 insertions(+), 210 deletions(-) create mode 100644 apps/server/src/migrations/mikro-orm/Migration20240719115036.ts diff --git a/apps/server/src/migrations/mikro-orm/Migration20240719115036.ts b/apps/server/src/migrations/mikro-orm/Migration20240719115036.ts new file mode 100644 index 00000000000..c829b84680a --- /dev/null +++ b/apps/server/src/migrations/mikro-orm/Migration20240719115036.ts @@ -0,0 +1,54 @@ +import { Migration } from '@mikro-orm/migrations-mongodb'; + +export class Migration20240719115036 extends Migration { + async up(): Promise { + await this.driver.nativeUpdate( + 'groups', + { + $and: [ + { + externalSource_externalId: { $exists: true }, + }, + { + externalSource_system: { $exists: true }, + }, + ], + }, + { + $unset: { externalSource_externalId: '', externalSource_system: '' }, + $set: { + externalSource: { + externalId: '$externalSource_externalId', + system: '$externalSource_system', + }, + }, + } + ); + + await this.driver.aggregate('groups', [ + { $match: { externalSource: { $exists: true } } }, + { + $set: { + externalSource: { + lastSyncedAt: new Date(), + }, + }, + }, + { $merge: 'groups' }, + ]); + + console.info(`'Added lastSyncedAt to externalSource for all groups`); + } + + async down(): Promise { + await this.driver.nativeUpdate( + 'groups', + { externalSource: { $exists: true } }, + { + $unset: ['externalSource.lastSyncedAt'], + } + ); + + console.info(`Removed lastSyncedAt of externalSource in all groups.'`); + } +} diff --git a/backup/setup/groups.json b/backup/setup/groups.json index 7c4d1a276ce..1f9f11246c9 100644 --- a/backup/setup/groups.json +++ b/backup/setup/groups.json @@ -1,212 +1,260 @@ [ - { - "createdAt": { - "$date": "2023-10-17T12:15:26.458Z" - }, - "updatedAt": { - "$date": "2023-10-17T12:15:26.461Z" - }, - "name": "Cypress-Test-Group", - "type": "class", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, - "users": [ - { - "user": { - "$oid": "5fa2c71bb229544f2c6966d9" - }, - "role": { - "$oid": "0000d186816abba584714c98" - } - }, - { - "user": { - "$oid": "5fa2cccab229544f2c696917" - }, - "role": { - "$oid": "0000d186816abba584714c99" - } - } - ], - "organization": { - "$oid": "5fa2c5ccb229544f2c69666c" - } - }, - { - "createdAt": { - "$date": "2023-10-17T12:15:26.458Z" - }, - "updatedAt": { - "$date": "2023-10-17T12:15:26.461Z" - }, - "name": "Cypress-Test-Group1", - "type": "class", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, - "users": [ - { - "user": { - "$oid": "5fa2c71bb229544f2c6966d9" - }, - "role": { - "$oid": "0000d186816abba584714c98" - } - }, - { - "user": { - "$oid": "5fa2cccab229544f2c696917" - }, - "role": { - "$oid": "0000d186816abba584714c99" - } - } - ], - "organization": { - "$oid": "5fa2c5ccb229544f2c69666c" - } - }, - { - "createdAt": { - "$date": "2023-10-17T12:15:26.458Z" - }, - "updatedAt": { - "$date": "2023-10-17T12:15:26.461Z" - }, - "name": "Cypress-Test-Group2", - "type": "class", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, - "users": [ - { - "user": { - "$oid": "5fa2c71bb229544f2c6966d9" - }, - "role": { - "$oid": "0000d186816abba584714c98" - } - }, - { - "user": { - "$oid": "5fa2cccab229544f2c696917" - }, - "role": { - "$oid": "0000d186816abba584714c99" - } - }, - { - "user": { - "$oid": "5fa30079b229544f2c6969ff" - }, - "role": { - "$oid": "0000d186816abba584714c99" - } - } - ], - "organization": { - "$oid": "5fa2c5ccb229544f2c69666c" - } - }, - { - "createdAt": { - "$date": "2023-10-17T12:15:26.458Z" - }, - "updatedAt": { - "$date": "2023-10-17T12:15:26.461Z" - }, - "name": "Cypress-Test-Group-Course-Sync", - "type": "other", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, - "users": [ - { - "user": { - "$oid": "5fa2c71bb229544f2c6966d9" - }, - "role": { - "$oid": "0000d186816abba584714c98" - } - }, - { - "user": { - "$oid": "5fa2cccab229544f2c696917" - }, - "role": { - "$oid": "0000d186816abba584714c99" - } - } - ], - "organization": { - "$oid": "5fa2c5ccb229544f2c69666c" - } - }, - { - "createdAt": { - "$date": "2023-10-17T12:15:26.458Z" - }, - "updatedAt": { - "$date": "2023-10-17T12:15:26.461Z" - }, - "name": "Cypress-Test-Group-Course-Sync2", - "type": "course", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, - "users": [ - { - "user": { - "$oid": "5fa2c71bb229544f2c6966d9" - }, - "role": { - "$oid": "0000d186816abba584714c98" - } - }, - { - "user": { - "$oid": "5fa2cccab229544f2c696917" - }, - "role": { - "$oid": "0000d186816abba584714c99" - } - } - ], - "organization": { - "$oid": "5fa2c5ccb229544f2c69666c" - } - }, - { - "createdAt": { - "$date": "2023-10-17T12:15:26.458Z" - }, - "updatedAt": { - "$date": "2023-10-17T12:15:26.461Z" - }, - "name": "Cypress-Test-Group-Course-Sync-Without-Teacher", - "type": "class", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, - "users": [ - { - "user": { - "$oid": "5fa2cccab229544f2c696917" - }, - "role": { - "$oid": "0000d186816abba584714c99" - } - } - ], - "organization": { - "$oid": "5fa2c5ccb229544f2c69666c" - } - } + { + "_id": { + "$oid": "669f600182d5ff057ecfe895" + }, + "createdAt": { + "$date": "2023-10-17T12:15:26.458Z" + }, + "updatedAt": { + "$date": "2023-10-17T12:15:26.461Z" + }, + "name": "Cypress-Test-Group", + "type": "class", + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-23T07:48:43.982Z" + } + }, + "users": [ + { + "user": { + "$oid": "5fa2c71bb229544f2c6966d9" + }, + "role": { + "$oid": "0000d186816abba584714c98" + } + }, + { + "user": { + "$oid": "5fa2cccab229544f2c696917" + }, + "role": { + "$oid": "0000d186816abba584714c99" + } + } + ], + "organization": { + "$oid": "5fa2c5ccb229544f2c69666c" + } + }, + { + "_id": { + "$oid": "669f600182d5ff057ecfe896" + }, + "createdAt": { + "$date": "2023-10-17T12:15:26.458Z" + }, + "updatedAt": { + "$date": "2023-10-17T12:15:26.461Z" + }, + "name": "Cypress-Test-Group1", + "type": "class", + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-23T07:48:43.982Z" + } + }, + "users": [ + { + "user": { + "$oid": "5fa2c71bb229544f2c6966d9" + }, + "role": { + "$oid": "0000d186816abba584714c98" + } + }, + { + "user": { + "$oid": "5fa2cccab229544f2c696917" + }, + "role": { + "$oid": "0000d186816abba584714c99" + } + } + ], + "organization": { + "$oid": "5fa2c5ccb229544f2c69666c" + } + }, + { + "_id": { + "$oid": "669f600182d5ff057ecfe897" + }, + "createdAt": { + "$date": "2023-10-17T12:15:26.458Z" + }, + "updatedAt": { + "$date": "2023-10-17T12:15:26.461Z" + }, + "name": "Cypress-Test-Group2", + "type": "class", + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-23T07:48:43.982Z" + } + }, + "users": [ + { + "user": { + "$oid": "5fa2c71bb229544f2c6966d9" + }, + "role": { + "$oid": "0000d186816abba584714c98" + } + }, + { + "user": { + "$oid": "5fa2cccab229544f2c696917" + }, + "role": { + "$oid": "0000d186816abba584714c99" + } + }, + { + "user": { + "$oid": "5fa30079b229544f2c6969ff" + }, + "role": { + "$oid": "0000d186816abba584714c99" + } + } + ], + "organization": { + "$oid": "5fa2c5ccb229544f2c69666c" + } + }, + { + "_id": { + "$oid": "669f600182d5ff057ecfe898" + }, + "createdAt": { + "$date": "2023-10-17T12:15:26.458Z" + }, + "updatedAt": { + "$date": "2023-10-17T12:15:26.461Z" + }, + "name": "Cypress-Test-Group-Course-Sync", + "type": "other", + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-23T07:48:43.982Z" + } + }, + "users": [ + { + "user": { + "$oid": "5fa2c71bb229544f2c6966d9" + }, + "role": { + "$oid": "0000d186816abba584714c98" + } + }, + { + "user": { + "$oid": "5fa2cccab229544f2c696917" + }, + "role": { + "$oid": "0000d186816abba584714c99" + } + } + ], + "organization": { + "$oid": "5fa2c5ccb229544f2c69666c" + } + }, + { + "_id": { + "$oid": "669f600182d5ff057ecfe899" + }, + "createdAt": { + "$date": "2023-10-17T12:15:26.458Z" + }, + "updatedAt": { + "$date": "2023-10-17T12:15:26.461Z" + }, + "name": "Cypress-Test-Group-Course-Sync2", + "type": "course", + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-23T07:48:43.982Z" + } + }, + "users": [ + { + "user": { + "$oid": "5fa2c71bb229544f2c6966d9" + }, + "role": { + "$oid": "0000d186816abba584714c98" + } + }, + { + "user": { + "$oid": "5fa2cccab229544f2c696917" + }, + "role": { + "$oid": "0000d186816abba584714c99" + } + } + ], + "organization": { + "$oid": "5fa2c5ccb229544f2c69666c" + } + }, + { + "_id": { + "$oid": "669f600182d5ff057ecfe89a" + }, + "createdAt": { + "$date": "2023-10-17T12:15:26.458Z" + }, + "updatedAt": { + "$date": "2023-10-17T12:15:26.461Z" + }, + "name": "Cypress-Test-Group-Course-Sync-Without-Teacher", + "type": "class", + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-23T07:48:43.982Z" + } + }, + "users": [ + { + "user": { + "$oid": "5fa2cccab229544f2c696917" + }, + "role": { + "$oid": "0000d186816abba584714c99" + } + } + ], + "organization": { + "$oid": "5fa2c5ccb229544f2c69666c" + } + } ] From 987473445c4be507c7e95af2ebc7ac01bdd5ba18 Mon Sep 17 00:00:00 2001 From: Igor Richter Date: Tue, 23 Jul 2024 14:05:25 +0200 Subject: [PATCH 07/20] - group.repo.spec.ts still failing tests --- apps/server/src/modules/group/repo/group.repo.spec.ts | 2 ++ 1 file changed, 2 insertions(+) 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 5de475a626f..f099fd69ea8 100644 --- a/apps/server/src/modules/group/repo/group.repo.spec.ts +++ b/apps/server/src/modules/group/repo/group.repo.spec.ts @@ -68,6 +68,7 @@ describe('GroupRepo', () => { externalSource: new ExternalSource({ externalId: group.externalSource?.externalId ?? '', systemId: group.externalSource?.system.id ?? '', + lastSyncedAt: group.externalSource?.lastSyncedAt ?? (expect.any(Date) as unknown as Date), }), users: [ new GroupUser({ @@ -676,6 +677,7 @@ describe('GroupRepo', () => { externalSource: new ExternalSource({ externalId: groupEntity.externalSource?.externalId ?? '', systemId: groupEntity.externalSource?.system.id ?? '', + lastSyncedAt: groupEntity.externalSource?.lastSyncedAt ?? (expect.any(Date) as unknown as Date), }), users: [ new GroupUser({ From 9e31385f1ee070b3151df3938f45e14d7017dd16 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Wed, 24 Jul 2024 12:34:25 +0200 Subject: [PATCH 08/20] N21-2095 seed data --- backup/setup/groups.json | 84 +++++++++------------------------------- 1 file changed, 18 insertions(+), 66 deletions(-) diff --git a/backup/setup/groups.json b/backup/setup/groups.json index 1f9f11246c9..a16eba066b1 100644 --- a/backup/setup/groups.json +++ b/backup/setup/groups.json @@ -1,8 +1,5 @@ [ { - "_id": { - "$oid": "669f600182d5ff057ecfe895" - }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -11,14 +8,9 @@ }, "name": "Cypress-Test-Group", "type": "class", - "externalSource": { - "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "system": { - "$oid": "0000d186816abba584714c93" - }, - "lastSyncedAt": { - "$date": "2024-07-23T07:48:43.982Z" - } + "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "externalSource_system": { + "$oid": "0000d186816abba584714c93" }, "users": [ { @@ -43,9 +35,6 @@ } }, { - "_id": { - "$oid": "669f600182d5ff057ecfe896" - }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -54,14 +43,9 @@ }, "name": "Cypress-Test-Group1", "type": "class", - "externalSource": { - "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "system": { - "$oid": "0000d186816abba584714c93" - }, - "lastSyncedAt": { - "$date": "2024-07-23T07:48:43.982Z" - } + "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "externalSource_system": { + "$oid": "0000d186816abba584714c93" }, "users": [ { @@ -86,9 +70,6 @@ } }, { - "_id": { - "$oid": "669f600182d5ff057ecfe897" - }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -97,14 +78,9 @@ }, "name": "Cypress-Test-Group2", "type": "class", - "externalSource": { - "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "system": { - "$oid": "0000d186816abba584714c93" - }, - "lastSyncedAt": { - "$date": "2024-07-23T07:48:43.982Z" - } + "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "externalSource_system": { + "$oid": "0000d186816abba584714c93" }, "users": [ { @@ -137,9 +113,6 @@ } }, { - "_id": { - "$oid": "669f600182d5ff057ecfe898" - }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -148,14 +121,9 @@ }, "name": "Cypress-Test-Group-Course-Sync", "type": "other", - "externalSource": { - "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "system": { - "$oid": "0000d186816abba584714c93" - }, - "lastSyncedAt": { - "$date": "2024-07-23T07:48:43.982Z" - } + "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "externalSource_system": { + "$oid": "0000d186816abba584714c93" }, "users": [ { @@ -180,9 +148,6 @@ } }, { - "_id": { - "$oid": "669f600182d5ff057ecfe899" - }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -191,14 +156,9 @@ }, "name": "Cypress-Test-Group-Course-Sync2", "type": "course", - "externalSource": { - "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "system": { - "$oid": "0000d186816abba584714c93" - }, - "lastSyncedAt": { - "$date": "2024-07-23T07:48:43.982Z" - } + "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "externalSource_system": { + "$oid": "0000d186816abba584714c93" }, "users": [ { @@ -223,9 +183,6 @@ } }, { - "_id": { - "$oid": "669f600182d5ff057ecfe89a" - }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -234,14 +191,9 @@ }, "name": "Cypress-Test-Group-Course-Sync-Without-Teacher", "type": "class", - "externalSource": { - "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "system": { - "$oid": "0000d186816abba584714c93" - }, - "lastSyncedAt": { - "$date": "2024-07-23T07:48:43.982Z" - } + "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "externalSource_system": { + "$oid": "0000d186816abba584714c93" }, "users": [ { From aaf8e9546e84d9d9a56cbb157433c459049ec780 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Wed, 24 Jul 2024 12:38:01 +0200 Subject: [PATCH 09/20] N21-2095 migr script fix --- .../mikro-orm/Migration20240719115036.ts | 34 +++---- backup/setup/groups.json | 96 ++++++++++++++----- 2 files changed, 85 insertions(+), 45 deletions(-) diff --git a/apps/server/src/migrations/mikro-orm/Migration20240719115036.ts b/apps/server/src/migrations/mikro-orm/Migration20240719115036.ts index c829b84680a..864f17b51d3 100644 --- a/apps/server/src/migrations/mikro-orm/Migration20240719115036.ts +++ b/apps/server/src/migrations/mikro-orm/Migration20240719115036.ts @@ -2,26 +2,24 @@ import { Migration } from '@mikro-orm/migrations-mongodb'; export class Migration20240719115036 extends Migration { async up(): Promise { - await this.driver.nativeUpdate( - 'groups', - { - $and: [ - { - externalSource_externalId: { $exists: true }, - }, - { - externalSource_system: { $exists: true }, - }, - ], - }, + await this.driver.aggregate('groups', [ + { $match: { externalSource_externalId: { $exists: true } } }, { - $unset: { externalSource_externalId: '', externalSource_system: '' }, $set: { externalSource: { externalId: '$externalSource_externalId', system: '$externalSource_system', }, }, + }, + { $merge: 'groups' }, + ]); + + await this.driver.nativeUpdate( + 'groups', + {}, + { + $unset: { externalSource_externalId: '', externalSource_system: '' }, } ); @@ -37,17 +35,11 @@ export class Migration20240719115036 extends Migration { { $merge: 'groups' }, ]); - console.info(`'Added lastSyncedAt to externalSource for all groups`); + console.info(`' Removed synced courses for all groups and added lastSyncedAt to externalSource`); } async down(): Promise { - await this.driver.nativeUpdate( - 'groups', - { externalSource: { $exists: true } }, - { - $unset: ['externalSource.lastSyncedAt'], - } - ); + await this.driver.nativeUpdate('groups', {}, { $unset: { 'externalSource.lastSyncedAt': '' } }); console.info(`Removed lastSyncedAt of externalSource in all groups.'`); } diff --git a/backup/setup/groups.json b/backup/setup/groups.json index a16eba066b1..cb40a5df702 100644 --- a/backup/setup/groups.json +++ b/backup/setup/groups.json @@ -1,5 +1,8 @@ [ { + "_id": { + "$oid": "66a0d94082d5ff057ed0cd01" + }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -8,10 +11,6 @@ }, "name": "Cypress-Test-Group", "type": "class", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, "users": [ { "user": { @@ -32,9 +31,21 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" + }, + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-24T10:37:00.935Z" + } } }, { + "_id": { + "$oid": "66a0d94082d5ff057ed0cd02" + }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -43,10 +54,6 @@ }, "name": "Cypress-Test-Group1", "type": "class", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, "users": [ { "user": { @@ -67,9 +74,21 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" + }, + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-24T10:37:00.935Z" + } } }, { + "_id": { + "$oid": "66a0d94082d5ff057ed0cd03" + }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -78,10 +97,6 @@ }, "name": "Cypress-Test-Group2", "type": "class", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, "users": [ { "user": { @@ -110,9 +125,21 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" + }, + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-24T10:37:00.935Z" + } } }, { + "_id": { + "$oid": "66a0d94082d5ff057ed0cd04" + }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -121,10 +148,6 @@ }, "name": "Cypress-Test-Group-Course-Sync", "type": "other", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, "users": [ { "user": { @@ -145,9 +168,21 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" + }, + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-24T10:37:00.935Z" + } } }, { + "_id": { + "$oid": "66a0d94082d5ff057ed0cd05" + }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -156,10 +191,6 @@ }, "name": "Cypress-Test-Group-Course-Sync2", "type": "course", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, "users": [ { "user": { @@ -180,9 +211,21 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" + }, + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-24T10:37:00.935Z" + } } }, { + "_id": { + "$oid": "66a0d94082d5ff057ed0cd06" + }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -191,10 +234,6 @@ }, "name": "Cypress-Test-Group-Course-Sync-Without-Teacher", "type": "class", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, "users": [ { "user": { @@ -207,6 +246,15 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" + }, + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-24T10:37:00.935Z" + } } } ] From 7cdc55945ca28238a04a7a06f0f141015e274b7e Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Wed, 24 Jul 2024 13:30:05 +0200 Subject: [PATCH 10/20] N21-2095 fix seed data --- backup/setup/groups.json | 96 ++++++++++------------------------------ 1 file changed, 24 insertions(+), 72 deletions(-) diff --git a/backup/setup/groups.json b/backup/setup/groups.json index cb40a5df702..a16eba066b1 100644 --- a/backup/setup/groups.json +++ b/backup/setup/groups.json @@ -1,8 +1,5 @@ [ { - "_id": { - "$oid": "66a0d94082d5ff057ed0cd01" - }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -11,6 +8,10 @@ }, "name": "Cypress-Test-Group", "type": "class", + "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "externalSource_system": { + "$oid": "0000d186816abba584714c93" + }, "users": [ { "user": { @@ -31,21 +32,9 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" - }, - "externalSource": { - "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "system": { - "$oid": "0000d186816abba584714c93" - }, - "lastSyncedAt": { - "$date": "2024-07-24T10:37:00.935Z" - } } }, { - "_id": { - "$oid": "66a0d94082d5ff057ed0cd02" - }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -54,6 +43,10 @@ }, "name": "Cypress-Test-Group1", "type": "class", + "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "externalSource_system": { + "$oid": "0000d186816abba584714c93" + }, "users": [ { "user": { @@ -74,21 +67,9 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" - }, - "externalSource": { - "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "system": { - "$oid": "0000d186816abba584714c93" - }, - "lastSyncedAt": { - "$date": "2024-07-24T10:37:00.935Z" - } } }, { - "_id": { - "$oid": "66a0d94082d5ff057ed0cd03" - }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -97,6 +78,10 @@ }, "name": "Cypress-Test-Group2", "type": "class", + "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "externalSource_system": { + "$oid": "0000d186816abba584714c93" + }, "users": [ { "user": { @@ -125,21 +110,9 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" - }, - "externalSource": { - "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "system": { - "$oid": "0000d186816abba584714c93" - }, - "lastSyncedAt": { - "$date": "2024-07-24T10:37:00.935Z" - } } }, { - "_id": { - "$oid": "66a0d94082d5ff057ed0cd04" - }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -148,6 +121,10 @@ }, "name": "Cypress-Test-Group-Course-Sync", "type": "other", + "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "externalSource_system": { + "$oid": "0000d186816abba584714c93" + }, "users": [ { "user": { @@ -168,21 +145,9 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" - }, - "externalSource": { - "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "system": { - "$oid": "0000d186816abba584714c93" - }, - "lastSyncedAt": { - "$date": "2024-07-24T10:37:00.935Z" - } } }, { - "_id": { - "$oid": "66a0d94082d5ff057ed0cd05" - }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -191,6 +156,10 @@ }, "name": "Cypress-Test-Group-Course-Sync2", "type": "course", + "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "externalSource_system": { + "$oid": "0000d186816abba584714c93" + }, "users": [ { "user": { @@ -211,21 +180,9 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" - }, - "externalSource": { - "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "system": { - "$oid": "0000d186816abba584714c93" - }, - "lastSyncedAt": { - "$date": "2024-07-24T10:37:00.935Z" - } } }, { - "_id": { - "$oid": "66a0d94082d5ff057ed0cd06" - }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -234,6 +191,10 @@ }, "name": "Cypress-Test-Group-Course-Sync-Without-Teacher", "type": "class", + "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "externalSource_system": { + "$oid": "0000d186816abba584714c93" + }, "users": [ { "user": { @@ -246,15 +207,6 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" - }, - "externalSource": { - "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "system": { - "$oid": "0000d186816abba584714c93" - }, - "lastSyncedAt": { - "$date": "2024-07-24T10:37:00.935Z" - } } } ] From 1318bbec0f723f06af8ef68f0ff4e4178d1622c1 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Wed, 24 Jul 2024 14:07:31 +0200 Subject: [PATCH 11/20] migration: Migration20240719115036 --- backup/setup/groups.json | 96 ++++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 24 deletions(-) diff --git a/backup/setup/groups.json b/backup/setup/groups.json index a16eba066b1..f57f515cb8c 100644 --- a/backup/setup/groups.json +++ b/backup/setup/groups.json @@ -1,5 +1,8 @@ [ { + "_id": { + "$oid": "66a0e6d782d5ff057ed0da44" + }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -8,10 +11,6 @@ }, "name": "Cypress-Test-Group", "type": "class", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, "users": [ { "user": { @@ -32,9 +31,21 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" + }, + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-24T12:03:08.936Z" + } } }, { + "_id": { + "$oid": "66a0e6d782d5ff057ed0da45" + }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -43,10 +54,6 @@ }, "name": "Cypress-Test-Group1", "type": "class", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, "users": [ { "user": { @@ -67,9 +74,21 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" + }, + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-24T12:03:08.936Z" + } } }, { + "_id": { + "$oid": "66a0e6d782d5ff057ed0da46" + }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -78,10 +97,6 @@ }, "name": "Cypress-Test-Group2", "type": "class", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, "users": [ { "user": { @@ -110,9 +125,21 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" + }, + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-24T12:03:08.936Z" + } } }, { + "_id": { + "$oid": "66a0e6d782d5ff057ed0da47" + }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -121,10 +148,6 @@ }, "name": "Cypress-Test-Group-Course-Sync", "type": "other", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, "users": [ { "user": { @@ -145,9 +168,21 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" + }, + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-24T12:03:08.936Z" + } } }, { + "_id": { + "$oid": "66a0e6d782d5ff057ed0da48" + }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -156,10 +191,6 @@ }, "name": "Cypress-Test-Group-Course-Sync2", "type": "course", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, "users": [ { "user": { @@ -180,9 +211,21 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" + }, + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-24T12:03:08.936Z" + } } }, { + "_id": { + "$oid": "66a0e6d782d5ff057ed0da49" + }, "createdAt": { "$date": "2023-10-17T12:15:26.458Z" }, @@ -191,10 +234,6 @@ }, "name": "Cypress-Test-Group-Course-Sync-Without-Teacher", "type": "class", - "externalSource_externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", - "externalSource_system": { - "$oid": "0000d186816abba584714c93" - }, "users": [ { "user": { @@ -207,6 +246,15 @@ ], "organization": { "$oid": "5fa2c5ccb229544f2c69666c" + }, + "externalSource": { + "externalId": "fd84869b-56e8-41d2-a3dd-6c7239068ed5", + "system": { + "$oid": "0000d186816abba584714c93" + }, + "lastSyncedAt": { + "$date": "2024-07-24T12:03:08.936Z" + } } } ] From f1a3c042a529bdb02de9333099140eacb2e42a50 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Wed, 24 Jul 2024 15:19:44 +0200 Subject: [PATCH 12/20] migration: Migration20240719115036 --- backup/setup/migrations.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backup/setup/migrations.json b/backup/setup/migrations.json index dbba0355eee..8bac8754165 100644 --- a/backup/setup/migrations.json +++ b/backup/setup/migrations.json @@ -178,5 +178,14 @@ "created_at": { "$date": "2024-06-28T07:07:10.278Z" } + }, + { + "_id": { + "$oid": "667e611e207a39b02c306407" + }, + "name": "Migration20240627134214", + "created_at": { + "$date": "2024-07-24T014:50:10.278Z" + } } ] From 32832fecd1c5dd730e494fe4c318d4552996a2c3 Mon Sep 17 00:00:00 2001 From: Mrika Llabani Date: Wed, 24 Jul 2024 15:20:23 +0200 Subject: [PATCH 13/20] migration: Migration20240719115036 --- backup/setup/migrations.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backup/setup/migrations.json b/backup/setup/migrations.json index 8bac8754165..0f72cb55837 100644 --- a/backup/setup/migrations.json +++ b/backup/setup/migrations.json @@ -183,7 +183,7 @@ "_id": { "$oid": "667e611e207a39b02c306407" }, - "name": "Migration20240627134214", + "name": "Migration20240719115036", "created_at": { "$date": "2024-07-24T014:50:10.278Z" } From 1bb7ab9111888faa5df0d8315191e1080a3aba20 Mon Sep 17 00:00:00 2001 From: Igor Richter Date: Thu, 25 Jul 2024 11:48:12 +0200 Subject: [PATCH 14/20] fix group search --- apps/server/src/modules/group/repo/group.repo.ts | 2 +- apps/server/src/modules/group/repo/group.scope.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/server/src/modules/group/repo/group.repo.ts b/apps/server/src/modules/group/repo/group.repo.ts index 8807e6850b4..c66b6bdd096 100644 --- a/apps/server/src/modules/group/repo/group.repo.ts +++ b/apps/server/src/modules/group/repo/group.repo.ts @@ -40,7 +40,7 @@ export class GroupRepo extends BaseDomainObjectRepo { const entity: GroupEntity | null = await this.em.findOne(GroupEntity, { externalSource: { externalId, - system: systemId, + system: new ObjectId(systemId), }, }); diff --git a/apps/server/src/modules/group/repo/group.scope.ts b/apps/server/src/modules/group/repo/group.scope.ts index 54c262cdaba..e227f111408 100644 --- a/apps/server/src/modules/group/repo/group.scope.ts +++ b/apps/server/src/modules/group/repo/group.scope.ts @@ -21,7 +21,7 @@ export class GroupScope extends Scope { bySystemId(id: EntityId | undefined): this { if (id) { - this.addQuery({ externalSource: { system: id } }); + this.addQuery({ externalSource: { system: new ObjectId(id) } }); } return this; } From 3d976348e268cef5f179f52f73ad8f08c2ca0cd9 Mon Sep 17 00:00:00 2001 From: Igor Richter Date: Thu, 25 Jul 2024 13:00:10 +0200 Subject: [PATCH 15/20] fix empty group save --- .../schulconnex-group-provisioning.service.spec.ts | 8 ++++++++ .../service/schulconnex-group-provisioning.service.ts | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts index 6b5d02bf2b1..dbe3b476212 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts @@ -823,6 +823,14 @@ describe(SchulconnexGroupProvisioningService.name, () => { expect(groupService.delete).not.toHaveBeenCalled(); }); + + it('should save the group', async () => { + const { externalGroups, systemId, externalUserId, existingGroups } = setup(); + + await service.removeExternalGroupsAndAffiliation(externalUserId, externalGroups, systemId); + + expect(groupService.save).toHaveBeenCalledWith(existingGroups[1]); + }); }); describe('when group is not empty after removal of the User', () => { diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts index 83ba1da755c..8a7a2e6d81b 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts @@ -13,9 +13,9 @@ import { NotFoundLoggableException } from '@shared/common/loggable-exception'; import { ExternalSource, LegacySchoolDo, Page, UserDO } from '@shared/domain/domainobject'; import { EntityId } from '@shared/domain/types'; import { Logger } from '@src/core/logger'; +import { Course } from '../../../../learnroom/domain'; import { ExternalGroupDto, ExternalGroupUserDto, ExternalSchoolDto } from '../../../dto'; import { SchoolForGroupNotFoundLoggable, UserForGroupNotFoundLoggable } from '../../../loggable'; -import { Course } from '../../../../learnroom/domain'; @Injectable() export class SchulconnexGroupProvisioningService { @@ -203,8 +203,8 @@ export class SchulconnexGroupProvisioningService { const courses: Course[] = await this.courseService.findBySyncedGroup(group); if (!courses || courses.length === 0) { await this.groupService.delete(group); + return null; } - return null; } return this.groupService.save(group); From cfb94270cef76ee223a8ed581177f5516e685bcc Mon Sep 17 00:00:00 2001 From: Steliyan Dinkov Date: Fri, 26 Jul 2024 11:59:25 +0200 Subject: [PATCH 16/20] update migration script --- .../mikro-orm/Migration20240719115036.ts | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/apps/server/src/migrations/mikro-orm/Migration20240719115036.ts b/apps/server/src/migrations/mikro-orm/Migration20240719115036.ts index 864f17b51d3..979c532c10f 100644 --- a/apps/server/src/migrations/mikro-orm/Migration20240719115036.ts +++ b/apps/server/src/migrations/mikro-orm/Migration20240719115036.ts @@ -14,6 +14,9 @@ export class Migration20240719115036 extends Migration { }, { $merge: 'groups' }, ]); + console.info( + `Migrate fields externalSource_externalId and externalSource_system to nested document externalSource` + ); await this.driver.nativeUpdate( 'groups', @@ -22,6 +25,7 @@ export class Migration20240719115036 extends Migration { $unset: { externalSource_externalId: '', externalSource_system: '' }, } ); + console.info(`Removed fields externalSource_externalId and externalSource_system`); await this.driver.aggregate('groups', [ { $match: { externalSource: { $exists: true } } }, @@ -35,12 +39,31 @@ export class Migration20240719115036 extends Migration { { $merge: 'groups' }, ]); - console.info(`' Removed synced courses for all groups and added lastSyncedAt to externalSource`); + console.info(`Updated nested document externalSource. Added nested field lastSyncedAt`); } async down(): Promise { - await this.driver.nativeUpdate('groups', {}, { $unset: { 'externalSource.lastSyncedAt': '' } }); + await this.driver.aggregate('groups', [ + { $match: { externalSource: { $exists: true } } }, + { + $set: { + externalSource_externalId: '$externalSource.externalId', + externalSource_system: '$externalSource.system', + }, + }, + { $merge: 'groups' }, + ]); + console.info( + `Rollback: Migrate fields externalSource_externalId and externalSource_system to nested document externalSource` + ); - console.info(`Removed lastSyncedAt of externalSource in all groups.'`); + await this.driver.nativeUpdate( + 'groups', + { externalSource: { $exists: true } }, + { + $unset: { externalSource: '' }, + } + ); + console.info(`Removed externalSource nested document`); } } From 0f767703aeccefaf5447154dc7d081fe5ce086f3 Mon Sep 17 00:00:00 2001 From: Steliyan Dinkov Date: Fri, 26 Jul 2024 16:26:12 +0200 Subject: [PATCH 17/20] update tests + import --- .../src/modules/group/repo/group.repo.spec.ts | 2 +- ...lconnex-group-provisioning.service.spec.ts | 28 ++++++++++++------- .../schulconnex-group-provisioning.service.ts | 2 +- 3 files changed, 20 insertions(+), 12 deletions(-) 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 f099fd69ea8..7485df2da2e 100644 --- a/apps/server/src/modules/group/repo/group.repo.spec.ts +++ b/apps/server/src/modules/group/repo/group.repo.spec.ts @@ -68,7 +68,7 @@ describe('GroupRepo', () => { externalSource: new ExternalSource({ externalId: group.externalSource?.externalId ?? '', systemId: group.externalSource?.system.id ?? '', - lastSyncedAt: group.externalSource?.lastSyncedAt ?? (expect.any(Date) as unknown as Date), + lastSyncedAt: group.externalSource?.lastSyncedAt ?? new Date(2024, 1, 1), }), users: [ new GroupUser({ diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts index dbe3b476212..0ebee724910 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.spec.ts @@ -41,6 +41,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { let courseService: DeepMocked; let schoolSystemOptionsService: DeepMocked; let logger: DeepMocked; + const mockDate = new Date(2024, 1, 1); beforeAll(async () => { module = await Test.createTestingModule({ @@ -85,6 +86,9 @@ describe(SchulconnexGroupProvisioningService.name, () => { courseService = module.get(CourseDoService); schoolSystemOptionsService = module.get(SchoolSystemOptionsService); logger = module.get(Logger); + + jest.useFakeTimers(); + jest.setSystemTime(mockDate); }); afterAll(async () => { @@ -453,7 +457,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: { externalId: externalGroupDto.externalId, systemId, - lastSyncedAt: expect.any(Date), + lastSyncedAt: mockDate, }, type: externalGroupDto.type, organizationId: school.id, @@ -489,7 +493,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: { externalId: externalGroupDto.externalId, systemId, - lastSyncedAt: expect.any(Date), + lastSyncedAt: mockDate, }, type: externalGroupDto.type, organizationId: school.id, @@ -558,7 +562,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: { externalId: externalGroupDto.externalId, systemId, - lastSyncedAt: expect.any(Date), + lastSyncedAt: mockDate, }, type: externalGroupDto.type, organizationId: undefined, @@ -628,7 +632,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: { externalId: externalGroupDto.externalId, systemId, - lastSyncedAt: expect.any(Date), + lastSyncedAt: mockDate, }, type: externalGroupDto.type, organizationId: undefined, @@ -716,7 +720,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: new ExternalSource({ externalId: 'externalId-1', systemId, - lastSyncedAt: new Date(), + lastSyncedAt: mockDate, }), }); const secondExistingGroup: Group = groupFactory.build({ @@ -724,7 +728,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: new ExternalSource({ externalId: 'externalId-2', systemId, - lastSyncedAt: new Date(), + lastSyncedAt: mockDate, }), }); const existingGroups = [firstExistingGroup, secondExistingGroup]; @@ -774,6 +778,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { expect(result).toHaveLength(0); }); }); + describe('when group is empty after removal of the User but synced with a course', () => { const setup = () => { const systemId = 'systemId'; @@ -787,7 +792,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: new ExternalSource({ externalId: 'externalId-1', systemId, - lastSyncedAt: new Date(), + lastSyncedAt: mockDate, }), }); const secondExistingGroup: Group = groupFactory.build({ @@ -795,7 +800,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: new ExternalSource({ externalId: 'externalId-2', systemId, - lastSyncedAt: new Date(), + lastSyncedAt: mockDate, }), }); const existingGroups = [firstExistingGroup, secondExistingGroup]; @@ -809,6 +814,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { userService.findByExternalId.mockResolvedValue(user); groupService.findGroups.mockResolvedValue(new Page(existingGroups, 2)); courseService.findBySyncedGroup.mockResolvedValue([course]); + return { externalGroups, systemId, @@ -816,6 +822,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { existingGroups, }; }; + it('should not delete the group', async () => { const { externalGroups, systemId, externalUserId } = setup(); @@ -850,7 +857,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: new ExternalSource({ externalId: `externalId-1`, systemId, - lastSyncedAt: new Date(), + lastSyncedAt: mockDate, }), }); @@ -862,7 +869,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { externalSource: new ExternalSource({ externalId: `externalId-2`, systemId, - lastSyncedAt: new Date(), + lastSyncedAt: mockDate, }), }); @@ -902,6 +909,7 @@ describe(SchulconnexGroupProvisioningService.name, () => { expect(groupService.delete).not.toHaveBeenCalled(); }); + it('should return the modified groups', async () => { const { externalGroups, systemId, externalUserId, secondExistingGroup } = setup(); diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts index 8a7a2e6d81b..0b05675b3cc 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-group-provisioning.service.ts @@ -1,6 +1,7 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { Group, GroupFilter, GroupService, GroupTypes, GroupUser } from '@modules/group'; import { CourseDoService } from '@modules/learnroom'; +import { Course } from '@modules/learnroom/domain'; import { LegacySchoolService, SchoolSystemOptionsService, @@ -13,7 +14,6 @@ import { NotFoundLoggableException } from '@shared/common/loggable-exception'; import { ExternalSource, LegacySchoolDo, Page, UserDO } from '@shared/domain/domainobject'; import { EntityId } from '@shared/domain/types'; import { Logger } from '@src/core/logger'; -import { Course } from '../../../../learnroom/domain'; import { ExternalGroupDto, ExternalGroupUserDto, ExternalSchoolDto } from '../../../dto'; import { SchoolForGroupNotFoundLoggable, UserForGroupNotFoundLoggable } from '../../../loggable'; From be9d53a0069eee0d527d0629aa1eed1e67036901 Mon Sep 17 00:00:00 2001 From: Steliyan Dinkov Date: Mon, 29 Jul 2024 09:45:41 +0200 Subject: [PATCH 18/20] update group repo test --- apps/server/src/modules/group/repo/group.repo.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7485df2da2e..57a1b9ca1b4 100644 --- a/apps/server/src/modules/group/repo/group.repo.spec.ts +++ b/apps/server/src/modules/group/repo/group.repo.spec.ts @@ -677,7 +677,7 @@ describe('GroupRepo', () => { externalSource: new ExternalSource({ externalId: groupEntity.externalSource?.externalId ?? '', systemId: groupEntity.externalSource?.system.id ?? '', - lastSyncedAt: groupEntity.externalSource?.lastSyncedAt ?? (expect.any(Date) as unknown as Date), + lastSyncedAt: groupEntity.externalSource?.lastSyncedAt ?? new Date(2024, 1, 1), }), users: [ new GroupUser({ From a8aaa4360ef8cb373479b8a08203274da34093e6 Mon Sep 17 00:00:00 2001 From: Steliyan Dinkov Date: Mon, 29 Jul 2024 13:28:57 +0200 Subject: [PATCH 19/20] remove comment + update import --- apps/server/src/modules/group/uc/class-group.uc.spec.ts | 2 +- .../strategy/oidc/service/schulconnex-course-sync.service.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/server/src/modules/group/uc/class-group.uc.spec.ts b/apps/server/src/modules/group/uc/class-group.uc.spec.ts index 489b9f54353..e3274185de7 100644 --- a/apps/server/src/modules/group/uc/class-group.uc.spec.ts +++ b/apps/server/src/modules/group/uc/class-group.uc.spec.ts @@ -5,8 +5,8 @@ import { ClassService } from '@modules/class'; import { Class } from '@modules/class/domain'; import { classFactory } from '@modules/class/domain/testing/factory/class.factory'; import { ClassGroupUc } from '@modules/group/uc/class-group.uc'; -import { CourseDoService } from '@modules/learnroom'; import { Course } from '@modules/learnroom/domain'; +import { CourseDoService } from '@modules/learnroom/service/course-do.service'; import { courseFactory } from '@modules/learnroom/testing'; import { SchoolYearService } from '@modules/legacy-school'; import { ProvisioningConfig } from '@modules/provisioning'; diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-course-sync.service.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-course-sync.service.ts index a27a518607f..e7be31cf427 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-course-sync.service.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/schulconnex-course-sync.service.ts @@ -36,7 +36,6 @@ export class SchulconnexCourseSyncService { course.students = students.map((user: GroupUser): EntityId => user.userId); course.teachers = teachers.map((user: GroupUser): EntityId => user.userId); } else { - // Remove all remaining students and break the link, when the last teacher of the group should be removed course.students = []; } }); From 5f7522e91b0414f5f459b11e72e896a12c26f6dc Mon Sep 17 00:00:00 2001 From: Steliyan Dinkov Date: Mon, 29 Jul 2024 16:17:17 +0200 Subject: [PATCH 20/20] update group scope test --- apps/server/src/modules/group/repo/group.scope.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 28a71540e70..dfc9f717689 100644 --- a/apps/server/src/modules/group/repo/group.scope.spec.ts +++ b/apps/server/src/modules/group/repo/group.scope.spec.ts @@ -75,7 +75,7 @@ describe(GroupScope.name, () => { scope.bySystemId(id); - expect(scope.query).toEqual({ externalSource: { system: id } }); + expect(scope.query).toEqual({ externalSource: { system: new ObjectId(id) } }); }); }); });