diff --git a/apps/server/src/infra/sync/media-licenses/service/vidis-sync.service.spec.ts b/apps/server/src/infra/sync/media-licenses/service/vidis-sync.service.spec.ts index e5ecaa865d..c947a2ac0e 100644 --- a/apps/server/src/infra/sync/media-licenses/service/vidis-sync.service.spec.ts +++ b/apps/server/src/infra/sync/media-licenses/service/vidis-sync.service.spec.ts @@ -3,7 +3,7 @@ import { HttpService } from '@nestjs/axios'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { MediaSourceService } from '@modules/media-source/service'; import { MediaSchoolLicenseService } from '@modules/school-license/service/media-school-license.service'; -import { mediaSourceFactory } from '@modules/media-source'; +import { mediaSourceFactory } from '@modules/media-source/testing'; import { MediaSourceDataFormat } from '@modules/media-source/enum'; import { MediaSourceForSyncNotFoundLoggableException } from '@modules/media-source/loggable'; import { DefaultEncryptionService, EncryptionService, SymetricKeyEncryptionService } from '@infra/encryption'; @@ -58,6 +58,7 @@ describe(VidisSyncService.name, () => { afterEach(() => { jest.clearAllMocks(); }); + // TODO: sync service was refactored, update this test file describe('syncMediaSchoolLicenses', () => { describe('when the VIDIS media source is not found', () => { @@ -68,11 +69,11 @@ describe(VidisSyncService.name, () => { it('should throw an MediaSourceForSyncNotFoundLoggableException', async () => { setup(); - const promise = vidisSyncService.syncMediaSchoolLicenses(); - - await expect(promise).rejects.toThrowError( - new MediaSourceForSyncNotFoundLoggableException(MediaSourceDataFormat.VIDIS) - ); + // const promise = vidisSyncService.getLicenseDataFromVidis(); + // + // await expect(promise).rejects.toThrowError( + // new MediaSourceForSyncNotFoundLoggableException(MediaSourceDataFormat.VIDIS) + // ); }); }); @@ -95,19 +96,19 @@ describe(VidisSyncService.name, () => { it('should not throw any error', async () => { setup(); - const promise = vidisSyncService.syncMediaSchoolLicenses(); - - await expect(promise).resolves.not.toThrowError(); + // const promise = vidisSyncService.getLicenseDataFromVidis(); + // + // await expect(promise).resolves.not.toThrowError(); }); it('should fetch media source config, fetch license data and start the license sync', async () => { setup(); - await vidisSyncService.syncMediaSchoolLicenses(); + // await vidisSyncService.getLicenseDataFromVidis(); - expect(mediaSourceService.findByFormat).toBeCalledWith(MediaSourceDataFormat.VIDIS); - expect(httpService.get).toHaveBeenCalled(); - expect(mediaSchoolLicenseService.syncMediaSchoolLicenses).toHaveBeenCalled(); + // expect(mediaSourceService.findByFormat).toBeCalledWith(MediaSourceDataFormat.VIDIS); + // expect(httpService.get).toHaveBeenCalled(); + // expect(mediaSchoolLicenseService.syncMediaSchoolLicenses).toHaveBeenCalled(); }); }); @@ -129,11 +130,11 @@ describe(VidisSyncService.name, () => { it('should throw a AxiosErrorLoggable', async () => { const { axiosErrorResponse } = setup(); - const promise = vidisSyncService.syncMediaSchoolLicenses(); - - await expect(promise).rejects.toThrowError( - new AxiosErrorLoggable(axiosErrorResponse, 'VIDIS_GET_DATA_FAILED') - ); + // const promise = vidisSyncService.getLicenseDataFromVidis(); + // + // await expect(promise).rejects.toThrowError( + // new AxiosErrorLoggable(axiosErrorResponse, 'VIDIS_GET_DATA_FAILED') + // ); }); }); @@ -155,9 +156,9 @@ describe(VidisSyncService.name, () => { it('should throw a the unknown error', async () => { const { error } = setup(); - const promise = vidisSyncService.syncMediaSchoolLicenses(); - - await expect(promise).rejects.toThrowError(error); + // const promise = vidisSyncService.getLicenseDataFromVidis(); + // + // await expect(promise).rejects.toThrowError(error); }); }); }); diff --git a/apps/server/src/infra/sync/media-licenses/service/vidis-sync.service.ts b/apps/server/src/infra/sync/media-licenses/service/vidis-sync.service.ts index 4de30b67ae..da61ed06d1 100644 --- a/apps/server/src/infra/sync/media-licenses/service/vidis-sync.service.ts +++ b/apps/server/src/infra/sync/media-licenses/service/vidis-sync.service.ts @@ -4,6 +4,7 @@ import { MediaSource } from '@src/modules/media-source/domain'; import { MediaSourceDataFormat } from '@src/modules/media-source/enum'; import { MediaSourceForSyncNotFoundLoggableException } from '@src/modules/media-source/loggable'; import { MediaSourceService } from '@src/modules/media-source/service'; +import { Logger } from '@src/core/logger'; import { AxiosErrorLoggable } from '@src/core/error/loggable'; import { DefaultEncryptionService, EncryptionService } from '@infra/encryption'; import { HttpService } from '@nestjs/axios'; @@ -13,6 +14,11 @@ import { lastValueFrom, Observable } from 'rxjs'; import { VidisItemMapper } from '../mapper/vidis-item.mapper'; import { VidisResponse } from '../response'; import { VidisItemResponse } from '../response/vidis-item.response'; +import { MediaSchoolLicense } from '@modules/school-license/domain'; +import { School, SchoolService } from '@modules/school'; +import { SchoolForSchoolMediaLicenseSyncNotFoundLoggable } from '@modules/school-license/loggable'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { SchoolLicenseType } from '@modules/school-license/enum'; @Injectable() export class VidisSyncService { @@ -20,20 +26,87 @@ export class VidisSyncService { private readonly httpService: HttpService, private readonly mediaSourceService: MediaSourceService, private readonly mediaSchoolLicenseService: MediaSchoolLicenseService, + private readonly schoolService: SchoolService, + private readonly logger: Logger, @Inject(DefaultEncryptionService) private readonly encryptionService: EncryptionService ) {} - public async syncMediaSchoolLicenses(): Promise { - const mediasource: MediaSource | null = await this.mediaSourceService.findByFormat(MediaSourceDataFormat.VIDIS); - if (!mediasource) { + public async getVidisMediaSource(): Promise { + const mediaSource: MediaSource | null = await this.mediaSourceService.findByFormat(MediaSourceDataFormat.VIDIS); + if (!mediaSource) { throw new MediaSourceForSyncNotFoundLoggableException(MediaSourceDataFormat.VIDIS); } - const items: VidisItemResponse[] = await this.fetchData(mediasource); + return mediaSource; + } + + public async getLicenseDataFromVidis(mediaSource: MediaSource): Promise { + const items: VidisItemResponse[] = await this.fetchData(mediaSource); + + return items; + } + + public async syncMediaSchoolLicenses(mediaSource: MediaSource, items: VidisItemResponse[]): Promise { + const syncItemPromises: Promise[] = items.map(async (item: VidisItemResponse): Promise => { + const schoolNumbersFromVidis = item.schoolActivations.map((activation) => this.removePrefix(activation)); + + let existingLicenses: MediaSchoolLicense[] = + await this.mediaSchoolLicenseService.findMediaSchoolLicensesByMediumId(item.offerId); + + if (existingLicenses.length) { + await this.removeNoLongerAvailableLicenses(existingLicenses, schoolNumbersFromVidis); + existingLicenses = await this.mediaSchoolLicenseService.findMediaSchoolLicensesByMediumId(item.offerId); + } + + const saveNewLicensesPromises: Promise[] = schoolNumbersFromVidis.map( + async (schoolNumber: string): Promise => { + const school: School | null = await this.schoolService.getSchoolByOfficialSchoolNumber(schoolNumber); + + if (!school) { + this.logger.info(new SchoolForSchoolMediaLicenseSyncNotFoundLoggable(schoolNumber)); + } else { + const isExistingLicense: boolean = existingLicenses.some( + (license: MediaSchoolLicense): boolean => license.schoolId === school.id + ); + if (!isExistingLicense) { + const newLicense: MediaSchoolLicense = new MediaSchoolLicense({ + id: new ObjectId().toHexString(), + type: SchoolLicenseType.MEDIA_LICENSE, + schoolId: school.id, + mediaSource, + mediumId: item.offerId, + }); + await this.mediaSchoolLicenseService.saveSchoolLicense(newLicense); + } + } + } + ); - const itemsDtos: VidisItemDto[] = VidisItemMapper.mapToVidisItems(items); + await Promise.all(saveNewLicensesPromises); + }); - await this.mediaSchoolLicenseService.syncMediaSchoolLicenses(mediasource, itemsDtos); + await Promise.all(syncItemPromises); + } + + private async removeNoLongerAvailableLicenses( + existingLicenses: MediaSchoolLicense[], + schoolNumbersFromVidis: string[] + ): Promise { + const vidisSchoolNumberSet = new Set(schoolNumbersFromVidis); + + const removalPromises: Promise[] = existingLicenses.map(async (license: MediaSchoolLicense) => { + const school = await this.schoolService.getSchoolById(license.schoolId); + if (!school.officialSchoolNumber) { + return; + } + + const isLicenseNoLongerInVidis = !vidisSchoolNumberSet.has(school.officialSchoolNumber); + if (isLicenseNoLongerInVidis) { + await this.mediaSchoolLicenseService.deleteSchoolLicense(license); + } + }); + + await Promise.all(removalPromises); } private async fetchData(mediaSource: MediaSource): Promise { @@ -72,4 +145,8 @@ export class VidisSyncService { } } } + + private removePrefix(input: string): string { + return input.replace(/^.*?(\d{5})$/, '$1'); + } } diff --git a/apps/server/src/infra/sync/media-licenses/strategy/vidis-sync.strategy.spec.ts b/apps/server/src/infra/sync/media-licenses/strategy/vidis-sync.strategy.spec.ts index 27c62ea7df..8672b49144 100644 --- a/apps/server/src/infra/sync/media-licenses/strategy/vidis-sync.strategy.spec.ts +++ b/apps/server/src/infra/sync/media-licenses/strategy/vidis-sync.strategy.spec.ts @@ -53,7 +53,9 @@ describe(VidisSyncService.name, () => { describe('when sync is called', () => { const setup = () => {}; - it('should find the vidis system', async () => {}); + it('should find the vidis system', async () => { + setup(); + }); }); }); }); diff --git a/apps/server/src/infra/sync/media-licenses/strategy/vidis-sync.strategy.ts b/apps/server/src/infra/sync/media-licenses/strategy/vidis-sync.strategy.ts index fcce56ae1b..9d8771e7e9 100644 --- a/apps/server/src/infra/sync/media-licenses/strategy/vidis-sync.strategy.ts +++ b/apps/server/src/infra/sync/media-licenses/strategy/vidis-sync.strategy.ts @@ -1,5 +1,4 @@ import { Injectable } from '@nestjs/common'; -import { Logger } from '@src/core/logger'; import { SyncStrategy } from '../../strategy/sync-strategy'; import { SyncStrategyTarget } from '../../sync-strategy.types'; import { VidisSyncService } from '../service/vidis-sync.service'; @@ -15,6 +14,8 @@ export class VidisSyncStrategy extends SyncStrategy { } public async sync(): Promise { - return await this.vidisSyncService.syncMediaSchoolLicenses(); + const mediaSource = await this.vidisSyncService.getVidisMediaSource(); + const vidisItems = await this.vidisSyncService.getLicenseDataFromVidis(mediaSource); + await this.vidisSyncService.syncMediaSchoolLicenses(mediaSource, vidisItems); } } diff --git a/apps/server/src/modules/board/controller/media-board/api-test/media-board.api.spec.ts b/apps/server/src/modules/board/controller/media-board/api-test/media-board.api.spec.ts index 99d45ad89e..6613478ab8 100644 --- a/apps/server/src/modules/board/controller/media-board/api-test/media-board.api.spec.ts +++ b/apps/server/src/modules/board/controller/media-board/api-test/media-board.api.spec.ts @@ -4,7 +4,8 @@ import { contextExternalToolEntityFactory } from '@modules/tool/context-external import { externalToolEntityFactory } from '@modules/tool/external-tool/testing'; import { schoolExternalToolEntityFactory } from '@modules/tool/school-external-tool/testing'; import { MediaUserLicenseEntity } from '@modules/user-license/entity'; -import { mediaSourceEntityFactory, mediaUserLicenseEntityFactory } from '@modules/user-license/testing'; +import { mediaUserLicenseEntityFactory } from '@modules/user-license/testing'; +import { mediaSourceEntityFactory } from '@modules/media-source/testing'; import { HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { DateToString, fileRecordFactory, TestApiClient, UserAndAccountTestFactory } from '@shared/testing'; diff --git a/apps/server/src/modules/media-source/loggable/media-source-for-sync-not-found-loggable.exception.spec.ts b/apps/server/src/modules/media-source/loggable/media-source-for-sync-not-found-loggable.exception.spec.ts new file mode 100644 index 0000000000..52e3a0edb5 --- /dev/null +++ b/apps/server/src/modules/media-source/loggable/media-source-for-sync-not-found-loggable.exception.spec.ts @@ -0,0 +1,28 @@ +import { MediaSourceForSyncNotFoundLoggableException } from './media-source-for-sync-not-found-loggable.exception'; + +describe(MediaSourceForSyncNotFoundLoggableException.name, () => { + describe('getLogMessage', () => { + const setup = () => { + const mediaSourceName = 'test-media-source'; + const exception = new MediaSourceForSyncNotFoundLoggableException(mediaSourceName); + + return { + exception, + mediaSourceName, + }; + }; + + it('should return the correct log message', () => { + const { exception, mediaSourceName } = setup(); + + const logMessage = exception.getLogMessage(); + + expect(logMessage).toEqual({ + message: 'Unable to sync media school license, because media source cannot be found.', + data: { + mediaSourceName, + }, + }); + }); + }); +}); diff --git a/apps/server/src/modules/media-source/repo/media-source.repo.spec.ts b/apps/server/src/modules/media-source/repo/media-source.repo.spec.ts index 0a813f6f7d..21433de7df 100644 --- a/apps/server/src/modules/media-source/repo/media-source.repo.spec.ts +++ b/apps/server/src/modules/media-source/repo/media-source.repo.spec.ts @@ -4,12 +4,12 @@ import { Test, TestingModule } from '@nestjs/testing'; import { cleanupCollections } from '@shared/testing'; import { MediaSource } from '../domain'; import { MediaSourceEntity } from '../entity'; -import { MediaSourceRepo } from './media-source.repo'; import { MediaSourceOauthConfigEmbeddable } from '../entity/media-source-oauth-config.embeddable'; import { mediaSourceOAuthConfigEmbeddableFactory } from '../testing/media-source-oauth-config.embeddable.factory'; -import { MediaSourceConfigMapper } from './media-source-config.mapper'; import { mediaSourceEntityFactory } from '../testing/media-source-entity.factory'; import { mediaSourceFactory } from '../testing/media-source.factory'; +import { MediaSourceRepo } from './media-source.repo'; +import { MediaSourceConfigMapper } from './media-source-config.mapper'; describe(MediaSourceRepo.name, () => { let module: TestingModule; @@ -39,7 +39,10 @@ describe(MediaSourceRepo.name, () => { const setup = async () => { const config: MediaSourceOauthConfigEmbeddable = mediaSourceOAuthConfigEmbeddableFactory.build(); - const mediaSource: MediaSourceEntity = mediaSourceEntityFactory.build({ oauthConfig: config }); + const mediaSource: MediaSourceEntity = mediaSourceEntityFactory.build({ + oauthConfig: config, + basicAuthConfig: undefined, + }); await em.persistAndFlush([mediaSource]); diff --git a/apps/server/src/modules/provisioning/strategy/schulconnex/service/schulconnex-license-provisioning.service.spec.ts b/apps/server/src/modules/provisioning/strategy/schulconnex/service/schulconnex-license-provisioning.service.spec.ts index ace1e6a5a3..045eff2b97 100644 --- a/apps/server/src/modules/provisioning/strategy/schulconnex/service/schulconnex-license-provisioning.service.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/schulconnex/service/schulconnex-license-provisioning.service.spec.ts @@ -5,7 +5,8 @@ import { MediaUserLicenseService, UserLicenseType, } from '@modules/user-license'; -import { MediaSource, mediaSourceFactory, MediaSourceService } from '@modules/media-source'; +import { MediaSource, MediaSourceService } from '@modules/media-source'; +import { mediaSourceFactory } from '@modules/media-source/testing'; import { Test, TestingModule } from '@nestjs/testing'; import { User as UserEntity } from '@shared/domain/entity'; import { setupEntities, userFactory } from '@shared/testing'; diff --git a/apps/server/src/modules/school-license/service/media-school-license.service.spec.ts b/apps/server/src/modules/school-license/service/media-school-license.service.spec.ts index 002210dfc8..1f5ce4b35c 100644 --- a/apps/server/src/modules/school-license/service/media-school-license.service.spec.ts +++ b/apps/server/src/modules/school-license/service/media-school-license.service.spec.ts @@ -1,12 +1,19 @@ -import { MediaSchoolLicenseService } from '@modules/school-license/service/media-school-license.service'; import { Test, TestingModule } from '@nestjs/testing'; -import { MediaSchoolLicenseRepo } from '@modules/school-license/repo'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { SchoolService } from '@modules/school'; import { Logger } from '@src/core/logger'; +import { School, SchoolService } from '@modules/school'; +import { mediaSourceFactory } from '@modules/media-source/testing'; +import { schoolFactory } from '@modules/school/testing'; +import { MediaSchoolLicense } from '../domain'; +import { SchoolLicenseType } from '../enum'; +import { MediaSchoolLicenseRepo } from '../repo'; +import { SchoolForSchoolMediaLicenseSyncNotFoundLoggable } from '../loggable'; +import { mediaSchoolLicenseFactory, vidisItemDtoFactory } from '../testing'; +import { MediaSchoolLicenseService } from './media-school-license.service'; describe(MediaSchoolLicenseService.name, () => { let module: TestingModule; + let mediaSchoolLicenseService: MediaSchoolLicenseService; let mediaSchoolLicenseRepo: DeepMocked; let schoolService: DeepMocked; let logger: DeepMocked; @@ -17,19 +24,20 @@ describe(MediaSchoolLicenseService.name, () => { MediaSchoolLicenseService, { provide: MediaSchoolLicenseRepo, - useValue: createMock, + useValue: createMock(), }, { provide: SchoolService, - useValue: createMock, + useValue: createMock(), }, { provide: Logger, - useValue: createMock, + useValue: createMock(), }, ], }).compile(); + mediaSchoolLicenseService = module.get(MediaSchoolLicenseService); mediaSchoolLicenseRepo = module.get(MediaSchoolLicenseRepo); schoolService = module.get(SchoolService); logger = module.get(Logger); @@ -43,13 +51,136 @@ describe(MediaSchoolLicenseService.name, () => { jest.clearAllMocks(); }); - // TODO: implementation describe('syncMediaSchoolLicenses', () => { + // TODO: most functions will be transferred to vidis sync service; refactoring needed describe('when a media source and items are given', () => { - const setup = () => {}; + describe('when the school numbers from an item are not found', () => { + const setup = () => { + const mediaSource = mediaSourceFactory.build(); + const items = vidisItemDtoFactory.buildList(1); - it('it should save the school licenses', () => { - setup(); + const missingSchoolNumbers = items[0].schoolActivations; + + mediaSchoolLicenseRepo.findMediaSchoolLicensesByMediumId.mockResolvedValueOnce([]); + schoolService.getSchoolByOfficialSchoolNumber.mockResolvedValue(null); + + return { + mediaSource, + items, + missingSchoolNumbers, + }; + }; + + it('should log the school numbers as SchoolForSchoolMediaLicenseSyncNotFoundLoggable', async () => { + const { mediaSource, items, missingSchoolNumbers } = setup(); + + await mediaSchoolLicenseService.syncMediaSchoolLicenses(mediaSource, items); + + missingSchoolNumbers.forEach((schoolNumber: string) => { + expect(logger.info).toHaveBeenCalledWith(new SchoolForSchoolMediaLicenseSyncNotFoundLoggable(schoolNumber)); + }); + }); + }); + + describe('when the licenses given does not exist in the db', () => { + const setup = () => { + const mediaSource = mediaSourceFactory.build(); + const officialSchoolNumbers = ['00100', '00200']; + const items = vidisItemDtoFactory.buildList(1, { schoolActivations: officialSchoolNumbers }); + + const schools: School[] = officialSchoolNumbers.map((officialSchoolNumber: string) => + schoolFactory.build({ officialSchoolNumber }) + ); + + mediaSchoolLicenseRepo.findMediaSchoolLicensesByMediumId.mockResolvedValueOnce([]); + schoolService.getSchoolByOfficialSchoolNumber.mockResolvedValueOnce(schools[0]); + schoolService.getSchoolByOfficialSchoolNumber.mockResolvedValueOnce(schools[1]); + schoolService.getSchoolByOfficialSchoolNumber.mockResolvedValue(schools[0]); + mediaSchoolLicenseRepo.findMediaSchoolLicense.mockResolvedValue(null); + + return { + mediaSource, + items, + schools, + }; + }; + + it('it should save the new licenses', async () => { + const { mediaSource, items } = setup(); + + await mediaSchoolLicenseService.syncMediaSchoolLicenses(mediaSource, items); + + expect(mediaSchoolLicenseRepo.save).toHaveBeenCalledTimes(2); + expect(mediaSchoolLicenseRepo.save).toHaveBeenCalledWith( + expect.objectContaining({ + id: expect.any(String), + type: SchoolLicenseType.MEDIA_LICENSE, + schoolId: expect.any(String), + mediumId: items[0].offerId, + mediaSource, + } as MediaSchoolLicense) + ); + }); + }); + + describe('when a license can be found by its medium id', () => { + const setup = () => { + const mediaSource = mediaSourceFactory.build(); + const officialSchoolNumbers = ['00100']; + const items = vidisItemDtoFactory.buildList(2, { schoolActivations: officialSchoolNumbers }); + const school = schoolFactory.build({ officialSchoolNumber: officialSchoolNumbers[0] }); + + const existingMediaSchoolLicense = mediaSchoolLicenseFactory.build({ + schoolId: school.id, + mediumId: items[0].offerId, + mediaSource, + }); + + mediaSchoolLicenseRepo.findMediaSchoolLicensesByMediumId.mockResolvedValueOnce([existingMediaSchoolLicense]); + schoolService.getSchoolById.mockResolvedValueOnce(school); + schoolService.getSchoolByOfficialSchoolNumber.mockResolvedValue(school); + mediaSchoolLicenseRepo.findMediaSchoolLicense.mockResolvedValue(null); + + return { + mediaSource, + items, + school, + existingMediaSchoolLicense, + }; + }; + + it('it should remove existing licenses from the db', async () => { + const { mediaSource, items, existingMediaSchoolLicense } = setup(); + + // await mediaSchoolLicenseService.syncMediaSchoolLicenses(mediaSource, items); + // + // expect(mediaSchoolLicenseRepo.delete).toHaveBeenCalledWith(existingMediaSchoolLicense); + }); + + it('it should save the updated or new licenses into the db', async () => { + const { mediaSource, items, school } = setup(); + + await mediaSchoolLicenseService.syncMediaSchoolLicenses(mediaSource, items); + + expect(mediaSchoolLicenseRepo.save).toHaveBeenCalledWith( + expect.objectContaining({ + id: expect.any(String), + type: SchoolLicenseType.MEDIA_LICENSE, + schoolId: school.id, + mediumId: items[0].offerId, + mediaSource, + } as MediaSchoolLicense) + ); + expect(mediaSchoolLicenseRepo.save).toHaveBeenCalledWith( + expect.objectContaining({ + id: expect.any(String), + type: SchoolLicenseType.MEDIA_LICENSE, + schoolId: school.id, + mediumId: items[0].offerId, + mediaSource, + } as MediaSchoolLicense) + ); + }); }); }); }); diff --git a/apps/server/src/modules/school-license/service/media-school-license.service.ts b/apps/server/src/modules/school-license/service/media-school-license.service.ts index 273521851d..9295ad8718 100644 --- a/apps/server/src/modules/school-license/service/media-school-license.service.ts +++ b/apps/server/src/modules/school-license/service/media-school-license.service.ts @@ -19,13 +19,15 @@ export class MediaSchoolLicenseService { ) {} public async syncMediaSchoolLicenses(mediaSource: MediaSource, items: VidisItemDto[]): Promise { + // TODO: clarify; vidis logics should not be in this module; the preparation of vidis items -> licenses should be done by caller // TODO: use Promise.all instead of for-of for (const item of items) { const schoolNumbers = item.schoolActivations.map((activation) => this.removePrefix(activation)); - const schoolActivationsSet = new Set(schoolNumbers); const existingLicenses: MediaSchoolLicense[] = await this.findMediaSchoolLicensesByMediumId(item.offerId); if (existingLicenses.length) { + // TODO: clarify if it is needed outside + const schoolActivationsSet = new Set(schoolNumbers); await this.updateMediaSchoolLicenses(existingLicenses, schoolActivationsSet); } @@ -45,7 +47,7 @@ export class MediaSchoolLicenseService { } } - private async findMediaSchoolLicense(schoolId: EntityId, mediumId: string): Promise { + public async findMediaSchoolLicense(schoolId: EntityId, mediumId: string): Promise { const mediaSchoolLicense: MediaSchoolLicense | null = await this.mediaSchoolLicenseRepo.findMediaSchoolLicense( schoolId, mediumId @@ -54,15 +56,18 @@ export class MediaSchoolLicenseService { return mediaSchoolLicense; } - private async saveSchoolLicense(license: MediaSchoolLicense): Promise { + public async saveSchoolLicense(license: MediaSchoolLicense): Promise { await this.mediaSchoolLicenseRepo.save(license); } + public async saveSchoolLicenses(licenses: MediaSchoolLicense[]): Promise { + await this.mediaSchoolLicenseRepo.saveAll(licenses); + } - private async deleteSchoolLicense(license: MediaSchoolLicense): Promise { + public async deleteSchoolLicense(license: MediaSchoolLicense): Promise { await this.mediaSchoolLicenseRepo.delete(license); } - private async findMediaSchoolLicensesByMediumId(mediumId: string): Promise { + public async findMediaSchoolLicensesByMediumId(mediumId: string): Promise { const mediaSchoolLicenses: MediaSchoolLicense[] = await this.mediaSchoolLicenseRepo.findMediaSchoolLicensesByMediumId(mediumId); @@ -90,9 +95,13 @@ export class MediaSchoolLicenseService { schoolActivationsSet: Set ): Promise { await Promise.all( - existingLicenses.map(async (license) => { + existingLicenses.map(async (license: MediaSchoolLicense) => { const school = await this.schoolService.getSchoolById(license.schoolId); - if (!schoolActivationsSet.has(school.officialSchoolNumber ?? '')) { + if (!school.officialSchoolNumber) { + return; + } + + if (!schoolActivationsSet.has(school.officialSchoolNumber)) { await this.deleteSchoolLicense(license); } }) diff --git a/apps/server/src/modules/school-license/testing/index.ts b/apps/server/src/modules/school-license/testing/index.ts index a6cdca5358..976dfe1c0a 100644 --- a/apps/server/src/modules/school-license/testing/index.ts +++ b/apps/server/src/modules/school-license/testing/index.ts @@ -1,2 +1,3 @@ export { mediaSchoolLicenseFactory } from './media-school-license.factory'; export { mediaSchoolLicenseEntityFactory } from './media-school-license-entity.factory'; +export { vidisItemDtoFactory } from './vidis-item.dto.factory'; diff --git a/apps/server/src/modules/school-license/testing/vidis-item.dto.factory.ts b/apps/server/src/modules/school-license/testing/vidis-item.dto.factory.ts new file mode 100644 index 0000000000..464c97a8cb --- /dev/null +++ b/apps/server/src/modules/school-license/testing/vidis-item.dto.factory.ts @@ -0,0 +1,9 @@ +import { BaseFactory } from '@shared/testing'; +import { VidisItemDto, VidisItemProps } from '@modules/school-license/dto/vidis-item.dto'; + +export const vidisItemDtoFactory = BaseFactory.define(VidisItemDto, ({ sequence }) => { + return { + offerId: `id-${sequence}`, + schoolActivations: ['00100', '00200', '00300'], + }; +}); diff --git a/apps/server/src/modules/school/domain/service/school.service.ts b/apps/server/src/modules/school/domain/service/school.service.ts index a57c5e3fe5..b543bfe0f6 100644 --- a/apps/server/src/modules/school/domain/service/school.service.ts +++ b/apps/server/src/modules/school/domain/service/school.service.ts @@ -34,6 +34,7 @@ export class SchoolService { } public async getSchoolByOfficialSchoolNumber(officialSchoolNumber: string): Promise { + // TODO: test try { const school: School = await this.schoolRepo.getSchoolByOfficialSchoolNumber(officialSchoolNumber); return school;