Merged PR 468: API実装(ワークフロー削除API)

## 概要
[Task2785: API実装(ワークフロー削除API)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2785)

- ワークフロー削除APIとテストを実装しました。

## レビューポイント
- リポジトリの削除ロジックは適切か
- テストケースは適切か

## UIの変更
- なし

## 動作確認状況
- ローカルで確認
This commit is contained in:
makabe.t 2023-10-11 09:14:41 +00:00
parent 5b0058b707
commit d48afdbffd
6 changed files with 412 additions and 8 deletions

View File

@ -37,6 +37,13 @@ export const getWorkflows = async (
});
};
// Workflow一覧全体を取得する
export const getAllWorkflows = async (
datasource: DataSource,
): Promise<Workflow[]> => {
return await datasource.getRepository(Workflow).find();
};
// Workflowを取得する
export const getWorkflow = async (
datasource: DataSource,

View File

@ -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 {};
}
}

View File

@ -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>(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>(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>(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>(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>(WorkflowsService);
const context = makeContext(admin.external_id);
//DBアクセスに失敗するようにする
const workflowsRepositoryService = module.get<WorkflowsRepositoryService>(
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();
}
}
});
});

View File

@ -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<void> {
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}`,
);
}
}
}

View File

@ -1,4 +1,4 @@
// AuthorIDとWorktypeIDのペア重複エラー
export class AuthorIdAndWorktypeIdPairAlreadyExistsError extends Error {}
// WorkflowID存在エラー
export class WorkflowIdNotFoundError extends Error {}
// Workflow存在エラー
export class WorkflowNotFoundError extends Error {}

View File

@ -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<void> {
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