diff --git a/dictation_server/src/features/workflows/test/utility.ts b/dictation_server/src/features/workflows/test/utility.ts index 473f9df..a4d6ce6 100644 --- a/dictation_server/src/features/workflows/test/utility.ts +++ b/dictation_server/src/features/workflows/test/utility.ts @@ -37,6 +37,13 @@ export const getWorkflows = async ( }); }; +// Workflow一覧全体を取得する +export const getAllWorkflows = async ( + datasource: DataSource, +): Promise => { + return await datasource.getRepository(Workflow).find(); +}; + // Workflowを取得する export const getWorkflow = async ( datasource: DataSource, diff --git a/dictation_server/src/features/workflows/workflows.controller.ts b/dictation_server/src/features/workflows/workflows.controller.ts index c571842..46b0fa0 100644 --- a/dictation_server/src/features/workflows/workflows.controller.ts +++ b/dictation_server/src/features/workflows/workflows.controller.ts @@ -180,6 +180,11 @@ export class WorkflowsController { type: DeleteWorkflowResponse, description: '成功時のレスポンス', }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'パラメータ不正エラー', + type: ErrorResponse, + }) @ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: '認証エラー', @@ -207,8 +212,7 @@ export class WorkflowsController { const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); - console.log(workflowId); - console.log(context.trackingId); + await this.workflowsService.deleteWorkflow(context, userId, workflowId); return {}; } } diff --git a/dictation_server/src/features/workflows/workflows.service.spec.ts b/dictation_server/src/features/workflows/workflows.service.spec.ts index 10e5714..51a3cb4 100644 --- a/dictation_server/src/features/workflows/workflows.service.spec.ts +++ b/dictation_server/src/features/workflows/workflows.service.spec.ts @@ -10,6 +10,7 @@ import { createWorkflow, createWorkflowTypist, getAllWorkflowTypists, + getAllWorkflows, getWorkflowTypists, getWorkflows, } from './test/utility'; @@ -2000,3 +2001,302 @@ describe('updateWorkflow', () => { } }); }); + +describe('deleteWorkflows', () => { + let source: DataSource = null; + beforeEach(async () => { + source = new DataSource({ + type: 'sqlite', + database: ':memory:', + logging: false, + entities: [__dirname + '/../../**/*.entity{.ts,.js}'], + synchronize: true, // trueにすると自動的にmigrationが行われるため注意 + }); + return source.initialize(); + }); + afterEach(async () => { + await source.destroy(); + source = null; + }); + + it('アカウント内のWorkflowを削除できる', async () => { + const module = await makeTestingModule(source); + // 第五階層のアカウント作成 + const { account, admin } = await makeTestAccount(source, { tier: 5 }); + const { id: authorId } = await makeTestUser(source, { + external_id: 'author1', + author_id: 'AUTHOR1', + account_id: account.id, + role: USER_ROLES.AUTHOR, + }); + const { id: typistId } = await makeTestUser(source, { + external_id: 'typist1', + account_id: account.id, + role: USER_ROLES.TYPIST, + }); + + const workflow = await createWorkflow( + source, + account.id, + authorId, + undefined, + undefined, + ); + await createWorkflowTypist(source, workflow.id, typistId); + + //作成したデータを確認 + { + const workflows = await getWorkflows(source, account.id); + const workflowTypists = await getAllWorkflowTypists(source); + expect(workflows.length).toBe(1); + expect(workflowTypists.length).toBe(1); + } + + const service = module.get(WorkflowsService); + const context = makeContext(admin.external_id); + + await service.deleteWorkflow(context, admin.external_id, workflow.id); + + //実行結果を確認 + { + const workflows = await getWorkflows(source, account.id); + const workflowTypists = await getAllWorkflowTypists(source); + expect(workflows.length).toBe(0); + expect(workflowTypists.length).toBe(0); + } + }); + + it('アカウント内のWorkflowを削除できる(複数ワークフローがある場合)', async () => { + const module = await makeTestingModule(source); + // 第五階層のアカウント作成 + const { account, admin } = await makeTestAccount(source, { tier: 5 }); + const { id: authorId1 } = await makeTestUser(source, { + external_id: 'author1', + author_id: 'AUTHOR1', + account_id: account.id, + role: USER_ROLES.AUTHOR, + }); + const { id: authorId2 } = await makeTestUser(source, { + external_id: 'author2', + author_id: 'AUTHOR2', + account_id: account.id, + role: USER_ROLES.AUTHOR, + }); + const { id: typistId } = await makeTestUser(source, { + external_id: 'typist1', + account_id: account.id, + role: USER_ROLES.TYPIST, + }); + + const workflow1 = await createWorkflow(source, account.id, authorId1); + await createWorkflowTypist(source, workflow1.id, typistId); + const workflow2 = await createWorkflow(source, account.id, authorId2); + await createWorkflowTypist(source, workflow2.id, typistId); + + //作成したデータを確認 + { + const workflows = await getAllWorkflows(source); + const workflowTypists = await getAllWorkflowTypists(source); + expect(workflows.length).toBe(2); + expect(workflowTypists.length).toBe(2); + } + + const service = module.get(WorkflowsService); + const context = makeContext(admin.external_id); + + await service.deleteWorkflow(context, admin.external_id, workflow1.id); + + //実行結果を確認 + { + const workflows = await getAllWorkflows(source); + const workflowTypists = await getAllWorkflowTypists(source); + expect(workflows.length).toBe(1); + expect(workflows[0].id).toBe(workflow2.id); + expect(workflowTypists.length).toBe(1); + expect(workflowTypists[0].workflow_id).toBe(workflow2.id); + } + }); + + it('指定されたワークフローが存在しない場合、400エラーを返却する', async () => { + const module = await makeTestingModule(source); + // 第五階層のアカウント作成 + const { account, admin } = await makeTestAccount(source, { tier: 5 }); + const { id: authorId } = await makeTestUser(source, { + external_id: 'author1', + author_id: 'AUTHOR1', + account_id: account.id, + role: USER_ROLES.AUTHOR, + }); + const { id: typistId } = await makeTestUser(source, { + external_id: 'typist1', + account_id: account.id, + role: USER_ROLES.TYPIST, + }); + + const workflow = await createWorkflow( + source, + account.id, + authorId, + undefined, + undefined, + ); + await createWorkflowTypist(source, workflow.id, typistId); + + //作成したデータを確認 + { + const workflows = await getWorkflows(source, account.id); + const workflowTypists = await getAllWorkflowTypists(source); + expect(workflows.length).toBe(1); + expect(workflowTypists.length).toBe(1); + } + + const service = module.get(WorkflowsService); + const context = makeContext(admin.external_id); + + //実行結果を確認 + try { + await service.deleteWorkflow(context, admin.external_id, 9999); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST); + expect(e.getResponse()).toEqual(makeErrorResponse('E013002')); + } else { + fail(); + } + } + }); + + it('指定されたワークフローが存在しない場合、400エラーを返却する(ログインユーザーのアカウント外)', async () => { + const module = await makeTestingModule(source); + // 第五階層のアカウント作成 + const { account, admin } = await makeTestAccount(source, { tier: 5 }); + const { id: authorId } = await makeTestUser(source, { + external_id: 'author1', + author_id: 'AUTHOR1', + account_id: account.id, + role: USER_ROLES.AUTHOR, + }); + const { id: typistId } = await makeTestUser(source, { + external_id: 'typist1', + account_id: account.id, + role: USER_ROLES.TYPIST, + }); + + const workflow = await createWorkflow( + source, + account.id, + authorId, + undefined, + undefined, + ); + await createWorkflowTypist(source, workflow.id, typistId); + + const { account: otherAccount } = await makeTestAccount(source, { + tier: 5, + }); + const { id: otherAauthorId } = await makeTestUser(source, { + external_id: 'author1', + author_id: 'AUTHOR1', + account_id: otherAccount.id, + role: USER_ROLES.AUTHOR, + }); + const { id: otherTypistId } = await makeTestUser(source, { + external_id: 'typist1', + account_id: otherAccount.id, + role: USER_ROLES.TYPIST, + }); + + const otherWorkflow = await createWorkflow( + source, + otherAccount.id, + otherAauthorId, + undefined, + undefined, + ); + await createWorkflowTypist(source, otherWorkflow.id, otherTypistId); + + //作成したデータを確認 + { + const workflows = await getAllWorkflows(source); + const workflowTypists = await getAllWorkflowTypists(source); + expect(workflows.length).toBe(2); + expect(workflowTypists.length).toBe(2); + } + + const service = module.get(WorkflowsService); + const context = makeContext(admin.external_id); + + //実行結果を確認 + try { + await service.deleteWorkflow( + context, + admin.external_id, + otherWorkflow.id, + ); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST); + expect(e.getResponse()).toEqual(makeErrorResponse('E013002')); + } else { + fail(); + } + } + }); + + it('DBアクセスに失敗した場合、500エラーを返却する', async () => { + const module = await makeTestingModule(source); + // 第五階層のアカウント作成 + const { account, admin } = await makeTestAccount(source, { tier: 5 }); + const { id: authorId } = await makeTestUser(source, { + external_id: 'author1', + author_id: 'AUTHOR1', + account_id: account.id, + role: USER_ROLES.AUTHOR, + }); + const { id: typistId } = await makeTestUser(source, { + external_id: 'typist1', + account_id: account.id, + role: USER_ROLES.TYPIST, + }); + + const workflow = await createWorkflow( + source, + account.id, + authorId, + undefined, + undefined, + ); + await createWorkflowTypist(source, workflow.id, typistId); + + //作成したデータを確認 + { + const workflows = await getWorkflows(source, account.id); + const workflowTypists = await getAllWorkflowTypists(source); + expect(workflows.length).toBe(1); + expect(workflowTypists.length).toBe(1); + } + + const service = module.get(WorkflowsService); + const context = makeContext(admin.external_id); + + //DBアクセスに失敗するようにする + const workflowsRepositoryService = module.get( + WorkflowsRepositoryService, + ); + workflowsRepositoryService.deleteWorkflow = jest + .fn() + .mockRejectedValue('DB failed'); + + //実行結果を確認 + try { + await service.deleteWorkflow(context, admin.external_id, workflow.id); + } 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/workflows/workflows.service.ts b/dictation_server/src/features/workflows/workflows.service.ts index 288629a..de23f5f 100644 --- a/dictation_server/src/features/workflows/workflows.service.ts +++ b/dictation_server/src/features/workflows/workflows.service.ts @@ -11,8 +11,9 @@ import { WorktypeIdNotFoundError } from '../../repositories/worktypes/errors/typ import { TemplateFileNotExistError } from '../../repositories/template_files/errors/types'; import { AuthorIdAndWorktypeIdPairAlreadyExistsError, - WorkflowIdNotFoundError, + WorkflowNotFoundError, } from '../../repositories/workflows/errors/types'; +import { AccountNotFoundError } from '../../repositories/accounts/errors/types'; @Injectable() export class WorkflowsService { @@ -243,7 +244,7 @@ export class WorkflowsService { this.logger.error(`[${context.trackingId}] error=${e}`); if (e instanceof Error) { switch (e.constructor) { - case WorkflowIdNotFoundError: + case WorkflowNotFoundError: throw new HttpException( makeErrorResponse('E013002'), HttpStatus.BAD_REQUEST, @@ -290,4 +291,70 @@ export class WorkflowsService { ); } } + + /** + * ワークフローを削除する + * @param context + * @param externalId + * @param workflowId + * @returns workflow + */ + async deleteWorkflow( + context: Context, + externalId: string, + workflowId: number, + ): Promise { + this.logger.log( + `[IN] [${context.trackingId}] ${this.deleteWorkflow.name} | | params: { ` + + `externalId: ${externalId}, ` + + `workflowId: ${workflowId} };`, + ); + try { + const { account } = await this.usersRepository.findUserByExternalId( + externalId, + ); + + if (!account) { + throw new AccountNotFoundError( + `account not found. externalId: ${externalId}`, + ); + } + + await this.workflowsRepository.deleteWorkflow(account.id, workflowId); + } catch (e) { + this.logger.error(`[${context.trackingId}] error=${e}`); + if (e instanceof Error) { + switch (e.constructor) { + case UserNotFoundError: + throw new HttpException( + makeErrorResponse('E010204'), + HttpStatus.BAD_REQUEST, + ); + case AccountNotFoundError: + throw new HttpException( + makeErrorResponse('E010501'), + HttpStatus.BAD_REQUEST, + ); + case WorkflowNotFoundError: + throw new HttpException( + makeErrorResponse('E013002'), + HttpStatus.BAD_REQUEST, + ); + default: + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } finally { + this.logger.log( + `[OUT] [${context.trackingId}] ${this.deleteWorkflow.name}`, + ); + } + } } diff --git a/dictation_server/src/repositories/workflows/errors/types.ts b/dictation_server/src/repositories/workflows/errors/types.ts index 8680e30..633bc00 100644 --- a/dictation_server/src/repositories/workflows/errors/types.ts +++ b/dictation_server/src/repositories/workflows/errors/types.ts @@ -1,4 +1,4 @@ // AuthorIDとWorktypeIDのペア重複エラー export class AuthorIdAndWorktypeIdPairAlreadyExistsError extends Error {} -// WorkflowID存在エラー -export class WorkflowIdNotFoundError extends Error {} +// Workflow存在エラー +export class WorkflowNotFoundError extends Error {} diff --git a/dictation_server/src/repositories/workflows/workflows.repository.service.ts b/dictation_server/src/repositories/workflows/workflows.repository.service.ts index 0437d79..f658dcd 100644 --- a/dictation_server/src/repositories/workflows/workflows.repository.service.ts +++ b/dictation_server/src/repositories/workflows/workflows.repository.service.ts @@ -13,7 +13,7 @@ import { WorktypeIdNotFoundError } from '../worktypes/errors/types'; import { TemplateFileNotExistError } from '../template_files/errors/types'; import { AuthorIdAndWorktypeIdPairAlreadyExistsError, - WorkflowIdNotFoundError, + WorkflowNotFoundError, } from './errors/types'; @Injectable() @@ -190,7 +190,7 @@ export class WorkflowsRepositoryService { where: { account_id: accountId, id: workflowId }, }); if (!targetWorkflow) { - throw new WorkflowIdNotFoundError( + throw new WorkflowNotFoundError( `workflow not found. id: ${workflowId}`, ); } @@ -300,6 +300,32 @@ export class WorkflowsRepositoryService { }); } + /** + * ワークフローを削除する + * @param accountId + * @param workflowId + * @returns workflow + */ + async deleteWorkflow(accountId: number, workflowId: number): Promise { + return await this.dataSource.transaction(async (entityManager) => { + const workflowRepo = entityManager.getRepository(Workflow); + const workflowTypistsRepo = entityManager.getRepository(DbWorkflowTypist); + + // ワークフローの存在確認 + const workflow = await workflowRepo.findOne({ + where: { account_id: accountId, id: workflowId }, + }); + if (!workflow) { + throw new WorkflowNotFoundError( + `workflow not found. id: ${workflowId}`, + ); + } + + await workflowTypistsRepo.delete({ workflow_id: workflowId }); + await workflowRepo.delete(workflowId); + }); + } + /** * DBに保存するワークフローデータを作成する * @param accountId