diff --git a/dictation_server/src/common/test/modules.ts b/dictation_server/src/common/test/modules.ts index 876f907..aec0d61 100644 --- a/dictation_server/src/common/test/modules.ts +++ b/dictation_server/src/common/test/modules.ts @@ -34,6 +34,9 @@ import { TemplatesService } from '../../features/templates/templates.service'; import { TemplatesModule } from '../../features/templates/templates.module'; import { WorkflowsService } from '../../features/workflows/workflows.service'; import { WorkflowsModule } from '../../features/workflows/workflows.module'; +import { TermsService } from '../../features/terms/terms.service'; +import { TermsRepositoryModule } from '../../repositories/terms/terms.repository.module'; +import { TermsModule } from '../../features/terms/terms.module'; export const makeTestingModule = async ( datasource: DataSource, @@ -56,6 +59,7 @@ export const makeTestingModule = async ( LicensesModule, TemplatesModule, WorkflowsModule, + TermsModule, AccountsRepositoryModule, UsersRepositoryModule, LicensesRepositoryModule, @@ -71,6 +75,7 @@ export const makeTestingModule = async ( AuthGuardsModule, SortCriteriaRepositoryModule, WorktypesRepositoryModule, + TermsRepositoryModule, ], providers: [ AuthService, @@ -82,6 +87,7 @@ export const makeTestingModule = async ( LicensesService, TemplatesService, WorkflowsService, + TermsService, ], }) .useMocker(async (token) => { diff --git a/dictation_server/src/features/terms/terms.controller.spec.ts b/dictation_server/src/features/terms/terms.controller.spec.ts index 7473f05..b57a498 100644 --- a/dictation_server/src/features/terms/terms.controller.spec.ts +++ b/dictation_server/src/features/terms/terms.controller.spec.ts @@ -6,11 +6,14 @@ describe('TermsController', () => { let controller: TermsController; beforeEach(async () => { + const mockTermsService = {}; const module: TestingModule = await Test.createTestingModule({ controllers: [TermsController], providers: [TermsService], - }).compile(); - + }) + .overrideProvider(TermsService) + .useValue(mockTermsService) + .compile(); controller = module.get(TermsController); }); diff --git a/dictation_server/src/features/terms/terms.controller.ts b/dictation_server/src/features/terms/terms.controller.ts index 5155587..1d855ba 100644 --- a/dictation_server/src/features/terms/terms.controller.ts +++ b/dictation_server/src/features/terms/terms.controller.ts @@ -28,12 +28,7 @@ export class TermsController { async getTermsInfo(): Promise { const context = makeContext(uuidv4()); - // TODO 仮実装。API実装タスクで本実装する。 - // const termInfo = await this.termsService.getTermsInfo(context); - const termsInfo = [ - { documentType: 'EULA', version: '1.0' }, - { documentType: 'DPA', version: '1.1' }, - ] as TermInfo[]; + const termsInfo = await this.termsService.getTermsInfo(context); return { termsInfo }; } diff --git a/dictation_server/src/features/terms/terms.module.ts b/dictation_server/src/features/terms/terms.module.ts index e314518..704e003 100644 --- a/dictation_server/src/features/terms/terms.module.ts +++ b/dictation_server/src/features/terms/terms.module.ts @@ -1,9 +1,11 @@ import { Module } from '@nestjs/common'; import { TermsController } from './terms.controller'; import { TermsService } from './terms.service'; +import { TermsRepositoryModule } from '../../repositories/terms/terms.repository.module'; @Module({ + imports: [TermsRepositoryModule], controllers: [TermsController], - providers: [TermsService] + providers: [TermsService], }) export class TermsModule {} diff --git a/dictation_server/src/features/terms/terms.service.spec.ts b/dictation_server/src/features/terms/terms.service.spec.ts index 6e8839b..33fc59a 100644 --- a/dictation_server/src/features/terms/terms.service.spec.ts +++ b/dictation_server/src/features/terms/terms.service.spec.ts @@ -1,18 +1,83 @@ -import { Test, TestingModule } from '@nestjs/testing'; import { TermsService } from './terms.service'; +import { DataSource } from 'typeorm'; +import { makeTestingModule } from '../../common/test/modules'; +import { createTermInfo } from '../auth/test/utility'; +import { makeContext } from '../../common/log'; +import { v4 as uuidv4 } from 'uuid'; +import { HttpException, HttpStatus } from '@nestjs/common'; +import { makeErrorResponse } from '../../common/error/makeErrorResponse'; -describe('TermsService', () => { - let service: TermsService; - +describe('利用規約取得', () => { + let source: DataSource = null; beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [TermsService], - }).compile(); - - service = module.get(TermsService); + source = new DataSource({ + type: 'sqlite', + database: ':memory:', + logging: false, + entities: [__dirname + '/../../**/*.entity{.ts,.js}'], + synchronize: true, // trueにすると自動的にmigrationが行われるため注意 + }); + return source.initialize(); }); - it('should be defined', () => { - expect(service).toBeDefined(); + afterEach(async () => { + await source.destroy(); + source = null; + }); + + it('最新の利用規約情報が取得できる', async () => { + const module = await makeTestingModule(source); + const service = module.get(TermsService); + + await createTermInfo(source, 'EULA', 'v1.0'); + await createTermInfo(source, 'EULA', 'v1.1'); + await createTermInfo(source, 'DPA', 'v1.0'); + await createTermInfo(source, 'DPA', 'v1.2'); + + const context = makeContext(uuidv4()); + const result = await service.getTermsInfo(context); + + expect(result[0].documentType).toBe('EULA'); + expect(result[0].version).toBe('v1.1'); + expect(result[1].documentType).toBe('DPA'); + expect(result[1].version).toBe('v1.2'); + }); + + it('利用規約情報(EULA、DPA両方)が存在しない場合エラーとなる', async () => { + const module = await makeTestingModule(source); + const service = module.get(TermsService); + const context = makeContext(uuidv4()); + await expect(service.getTermsInfo(context)).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); + }); + + it('利用規約情報(EULAのみ)が存在しない場合エラーとなる', async () => { + const module = await makeTestingModule(source); + const service = module.get(TermsService); + await createTermInfo(source, 'DPA', 'v1.0'); + const context = makeContext(uuidv4()); + await expect(service.getTermsInfo(context)).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); + }); + + it('利用規約情報(DPAのみ)が存在しない場合エラーとなる', async () => { + const module = await makeTestingModule(source); + const service = module.get(TermsService); + await createTermInfo(source, 'EULA', 'v1.0'); + const context = makeContext(uuidv4()); + await expect(service.getTermsInfo(context)).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); }); }); diff --git a/dictation_server/src/features/terms/terms.service.ts b/dictation_server/src/features/terms/terms.service.ts index 51ba395..526bde6 100644 --- a/dictation_server/src/features/terms/terms.service.ts +++ b/dictation_server/src/features/terms/terms.service.ts @@ -1,4 +1,44 @@ -import { Injectable } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'; +import { Context } from '../../common/log'; +import { makeErrorResponse } from '../../common/error/makeErrorResponse'; +import { TermInfo } from './types/types'; +import { TermsRepositoryService } from '../../repositories/terms/terms.repository.service'; +import { TERM_TYPE } from '../../constants'; @Injectable() -export class TermsService {} +export class TermsService { + constructor(private readonly termsRepository: TermsRepositoryService) {} + private readonly logger = new Logger(TermsService.name); + + /** + * 利用規約情報を取得する + * return termsInfo + */ + async getTermsInfo(context: Context): Promise { + this.logger.log(`[IN] [${context.trackingId}] ${this.getTermsInfo.name}`); + try { + const { eulaVersion, dpaVersion } = + await this.termsRepository.getLatestTermsInfo(); + return [ + { + documentType: TERM_TYPE.EULA, + version: eulaVersion, + }, + { + documentType: TERM_TYPE.DPA, + version: dpaVersion, + }, + ]; + } catch (e) { + this.logger.error(`error=${e}`); + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } finally { + this.logger.log( + `[OUT] [${context.trackingId}] ${this.getTermsInfo.name}`, + ); + } + } +} diff --git a/dictation_server/src/features/terms/types/types.ts b/dictation_server/src/features/terms/types/types.ts index afea3f0..6a45eae 100644 --- a/dictation_server/src/features/terms/types/types.ts +++ b/dictation_server/src/features/terms/types/types.ts @@ -10,3 +10,8 @@ export class GetTermsInfoResponse { @ApiProperty({ type: [TermInfo] }) termsInfo: TermInfo[]; } + +export type TermsVersion = { + eulaVersion: string; + dpaVersion: string; +}; diff --git a/dictation_server/src/repositories/terms/terms.repository.module.ts b/dictation_server/src/repositories/terms/terms.repository.module.ts index 9edd181..f88c52c 100644 --- a/dictation_server/src/repositories/terms/terms.repository.module.ts +++ b/dictation_server/src/repositories/terms/terms.repository.module.ts @@ -1,8 +1,11 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Term } from './entity/term.entity'; +import { TermsRepositoryService } from './terms.repository.service'; @Module({ imports: [TypeOrmModule.forFeature([Term])], + providers: [TermsRepositoryService], + exports: [TermsRepositoryService], }) export class TermsRepositoryModule {} diff --git a/dictation_server/src/repositories/terms/terms.repository.service.ts b/dictation_server/src/repositories/terms/terms.repository.service.ts new file mode 100644 index 0000000..7c79f24 --- /dev/null +++ b/dictation_server/src/repositories/terms/terms.repository.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { TermsVersion } from '../../features/terms/types/types'; +import { Term } from './entity/term.entity'; +import { TERM_TYPE } from '../../constants'; +import { TermInfoNotFoundError } from '../users/errors/types'; + +@Injectable() +export class TermsRepositoryService { + constructor(private dataSource: DataSource) {} + + /* + * 利用規約の最新バージョンを取得する + * @returns Term[] + */ + async getLatestTermsInfo(): Promise { + return await this.dataSource.transaction(async (entityManager) => { + const termRepo = entityManager.getRepository(Term); + const latestEulaInfo = await termRepo.findOne({ + where: { + document_type: TERM_TYPE.EULA, + }, + order: { + id: 'DESC', + }, + }); + const latestDpaInfo = await termRepo.findOne({ + where: { + document_type: TERM_TYPE.DPA, + }, + order: { + id: 'DESC', + }, + }); + + if (!latestEulaInfo || !latestDpaInfo) { + throw new TermInfoNotFoundError( + `Terms info is not found. latestEulaInfo: ${latestEulaInfo}, latestDpaInfo: ${latestDpaInfo}`, + ); + } + return { + eulaVersion: latestEulaInfo.version, + dpaVersion: latestDpaInfo.version, + }; + }); + } +}