From f8183399e28a5f16ecee32f033f820a37bfa4633 Mon Sep 17 00:00:00 2001 From: Kentaro Fukunaga Date: Tue, 20 Feb 2024 11:23:04 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20762:=20=E3=82=A2=E3=82=AB?= =?UTF-8?q?=E3=82=A6=E3=83=B3=E3=83=88=E5=88=A9=E7=94=A8=E5=88=B6=E9=99=90?= =?UTF-8?q?=E6=9B=B4=E6=96=B0API=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3710: アカウント利用制限更新API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3710) - タイトルの通りです。 - 既存実装でログが不足している箇所あったのでちょろ修正もしました。 ## レビューポイント - これと言ってみて欲しいポイントはないので、何か気になる点あれば ## 動作確認状況 - ローカルで全テストが通ることを確認済み --- .../features/accounts/accounts.controller.ts | 9 +- .../accounts/accounts.service.spec.ts | 126 ++++++++++++++++++ .../src/features/accounts/accounts.service.ts | 48 ++++++- .../accounts/accounts.repository.service.ts | 41 ++++++ 4 files changed, 220 insertions(+), 4 deletions(-) diff --git a/dictation_server/src/features/accounts/accounts.controller.ts b/dictation_server/src/features/accounts/accounts.controller.ts index ef75727..dceff54 100644 --- a/dictation_server/src/features/accounts/accounts.controller.ts +++ b/dictation_server/src/features/accounts/accounts.controller.ts @@ -2250,7 +2250,7 @@ export class AccountsController { }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, - description: 'パラメータ不正/アカウント不在', + description: 'パラメータ不正', type: ErrorResponse, }) @ApiResponse({ @@ -2311,8 +2311,11 @@ export class AccountsController { // service層を呼び出す const { accountId, restricted } = body; - // TODO:service層実装時に削除する - this.logger.log(`accountId: ${accountId}, restricted: ${restricted}`); + await this.accountService.updateRestrictionStatus( + context, + accountId, + restricted, + ); return {}; } diff --git a/dictation_server/src/features/accounts/accounts.service.spec.ts b/dictation_server/src/features/accounts/accounts.service.spec.ts index 0eb2c6f..47a1188 100644 --- a/dictation_server/src/features/accounts/accounts.service.spec.ts +++ b/dictation_server/src/features/accounts/accounts.service.spec.ts @@ -7711,3 +7711,129 @@ describe('updateFileDeleteSetting', () => { } }); }); + +describe('updateRestrictionStatus', () => { + let source: DataSource | null = null; + beforeAll(async () => { + if (source == null) { + source = await (async () => { + const s = new DataSource({ + type: 'mysql', + host: 'test_mysql_db', + port: 3306, + username: 'user', + password: 'password', + database: 'odms', + entities: [__dirname + '/../../**/*.entity{.ts,.js}'], + synchronize: false, // trueにすると自動的にmigrationが行われるため注意 + }); + return await s.initialize(); + })(); + } + }); + + beforeEach(async () => { + if (source) { + await truncateAllTable(source); + } + }); + + afterAll(async () => { + await source?.destroy(); + source = null; + }); + + it('アカウント利用制限のON/OFFが出来る', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + // 第一階層のアカウントを作成する + const { admin } = await makeTestAccount(source, { + tier: 1, + }); + const context = makeContext(admin.external_id, 'requestId'); + + // 操作対象の第五階層のアカウントを作成する + const { account } = await makeTestAccount(source, { + tier: 5, + locked: false, + }); + + const service = module.get(AccountsService); + // 利用制限をかけられるか確認。 + { + const restricted = true; + await service.updateRestrictionStatus(context, account.id, restricted); + + const result = await getAccount(source, account.id); + expect(result?.locked).toBe(restricted); + } + + // 利用制限を解除できるか確認。 + { + const restricted = false; + await service.updateRestrictionStatus(context, account.id, restricted); + + const result = await getAccount(source, account.id); + expect(result?.locked).toBe(restricted); + } + }); + + it('対象アカウントが存在しない場合は500エラーを返す', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + const service = module.get(AccountsService); + + // アカウントを作成せずに実行する + const context = makeContext('dummy', 'requestId'); + try { + await service.updateRestrictionStatus(context, 0, false); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); + expect(e.getResponse()).toEqual(makeErrorResponse('E009999')); + } else { + fail(); + } + } + }); + + it('DBアクセスに失敗した場合、500エラーを返す', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + // 第一階層のアカウントを作成する + const { admin } = await makeTestAccount(source, { + tier: 1, + }); + const context = makeContext(admin.external_id, 'requestId'); + + // 操作対象の第五階層のアカウントを作成する + const { account } = await makeTestAccount(source, { + tier: 5, + locked: false, + }); + + //DBアクセスに失敗するようにする + const accountsRepositoryService = module.get( + AccountsRepositoryService, + ); + accountsRepositoryService.updateRestrictionStatus = jest + .fn() + .mockRejectedValue('DB failed'); + + // テスト実行する + const service = module.get(AccountsService); + try { + await service.updateRestrictionStatus(context, account.id, true); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); + expect(e.getResponse()).toEqual(makeErrorResponse('E009999')); + } else { + fail(); + } + } + }); +}); diff --git a/dictation_server/src/features/accounts/accounts.service.ts b/dictation_server/src/features/accounts/accounts.service.ts index 28ee33d..1cd288f 100644 --- a/dictation_server/src/features/accounts/accounts.service.ts +++ b/dictation_server/src/features/accounts/accounts.service.ts @@ -2584,7 +2584,53 @@ export class AccountsService { HttpStatus.INTERNAL_SERVER_ERROR, ); } finally { - this.logger.log(`[OUT] [${context.getTrackingId()}]`); + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${ + this.updateFileDeleteSetting.name + }`, + ); + } + } + + /** + * 指定したアカウントのシステム利用制限状態を更新する + * @param context + * @param accountId 更新対象アカウントID + * @param restricted 制限するかどうか(true:制限する) + */ + async updateRestrictionStatus( + context: Context, + accountId: number, + restricted: boolean, + ): Promise { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${ + this.updateRestrictionStatus.name + } | params: { ` + + `accountId: ${accountId}, ` + + `restricted: ${restricted},};`, + ); + + try { + await this.accountRepository.updateRestrictionStatus( + context, + accountId, + restricted, + ); + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + // アカウントが存在しない場合のエラーもINTERNAL_SERVER_ERROR扱いのため個別の判定は行わない + + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${ + this.updateRestrictionStatus.name + }`, + ); } } } diff --git a/dictation_server/src/repositories/accounts/accounts.repository.service.ts b/dictation_server/src/repositories/accounts/accounts.repository.service.ts index 4a516a8..11464b1 100644 --- a/dictation_server/src/repositories/accounts/accounts.repository.service.ts +++ b/dictation_server/src/repositories/accounts/accounts.repository.service.ts @@ -1361,4 +1361,45 @@ export class AccountsRepositoryService { ); }); } + + /** + * 指定したアカウントのシステム利用制限状態を更新する + * @param context + * @param accountId 更新対象アカウントID + * @param restricted 制限するかどうか(true:制限する) + */ + async updateRestrictionStatus( + context: Context, + accountId: number, + restricted: boolean, + ): Promise { + return await this.dataSource.transaction(async (entityManager) => { + const accountRepo = entityManager.getRepository(Account); + + // アカウントが存在するかチェック + const account = await accountRepo.findOne({ + where: { id: accountId }, + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, + }); + + // アカウントが存在しない場合はエラー + if (!account) { + throw new AccountNotFoundError( + `Account is not found. id: ${accountId}`, + ); + } + + // アカウント利用制限状態の更新を行う + await updateEntity( + accountRepo, + { id: accountId }, + { + locked: restricted, + }, + this.isCommentOut, + context, + ); + }); + } }