Merged PR 490: ワークタイプ削除API実装
## 概要 [Task2611: ワークタイプ削除API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2611) - ワークタイプの削除APIとテストを実装しました。 ※API IF部分は対象外です。 ## レビューポイント - リポジトリのエラーチェックに問題はないか - テストケースは適切か ## UIの変更 - なし ## 動作確認状況 - ローカルで確認
This commit is contained in:
parent
00f4966aa9
commit
c9bc6393c6
@ -57,6 +57,7 @@ export const ErrorCodes = [
|
||||
'E011001', // ワークタイプ重複エラー
|
||||
'E011002', // ワークタイプ登録上限超過エラー
|
||||
'E011003', // ワークタイプ不在エラー
|
||||
'E011004', // ワークタイプ使用中エラー
|
||||
'E012001', // テンプレートファイル不在エラー
|
||||
'E013001', // ワークフローのAuthorIDとWorktypeIDのペア重複エラー
|
||||
'E013002', // ワークフロー不在エラー
|
||||
|
||||
@ -46,6 +46,7 @@ export const errors: Errors = {
|
||||
E011001: 'This WorkTypeID already used Error',
|
||||
E011002: 'WorkTypeID create limit exceeded Error',
|
||||
E011003: 'WorkTypeID not found Error',
|
||||
E011004: 'WorkTypeID is in use Error',
|
||||
E012001: 'Template file not found Error',
|
||||
E013001: 'AuthorId and WorktypeId pair already exists Error',
|
||||
E013002: 'Workflow not found Error',
|
||||
|
||||
@ -865,9 +865,7 @@ export class AccountsController {
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
console.log(context.trackingId);
|
||||
console.log(`worktypeId: ${id}`);
|
||||
|
||||
await this.accountService.deleteWorktype(context, userId, id);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@ -76,7 +76,9 @@ import { AdB2cUser } from '../../gateways/adb2c/types/types';
|
||||
import { Worktype } from '../../repositories/worktypes/entity/worktype.entity';
|
||||
import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service';
|
||||
import { UsersRepositoryService } from '../../repositories/users/users.repository.service';
|
||||
import { createWorkflow, getWorkflows } from '../workflows/test/utility';
|
||||
import { UsersService } from '../users/users.service';
|
||||
|
||||
describe('createAccount', () => {
|
||||
let source: DataSource = null;
|
||||
beforeEach(async () => {
|
||||
@ -3900,6 +3902,198 @@ describe('updateWorktype', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteWorktype', () => {
|
||||
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('WorktypeIDを削除できる', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
const { id: worktypeId1 } = await createWorktype(
|
||||
source,
|
||||
account.id,
|
||||
'worktype1',
|
||||
);
|
||||
const { id: worktypeId2 } = await createWorktype(
|
||||
source,
|
||||
account.id,
|
||||
'worktype2',
|
||||
);
|
||||
await createOptionItems(source, worktypeId1);
|
||||
await createOptionItems(source, worktypeId2);
|
||||
|
||||
// 作成したデータを確認
|
||||
{
|
||||
const worktypes = await getWorktypes(source, account.id);
|
||||
const optionItems = await getOptionItems(source);
|
||||
expect(worktypes.length).toBe(2);
|
||||
expect(worktypes[0].id).toBe(worktypeId1);
|
||||
expect(worktypes[0].custom_worktype_id).toBe('worktype1');
|
||||
expect(worktypes[1].id).toBe(worktypeId2);
|
||||
expect(worktypes[1].custom_worktype_id).toBe('worktype2');
|
||||
expect(optionItems.length).toBe(20);
|
||||
}
|
||||
|
||||
await service.deleteWorktype(context, admin.external_id, worktypeId1);
|
||||
|
||||
//実行結果を確認
|
||||
{
|
||||
const worktypes = await getWorktypes(source, account.id);
|
||||
const optionItems = await getOptionItems(source);
|
||||
expect(worktypes.length).toBe(1);
|
||||
expect(worktypes[0].id).toBe(worktypeId2);
|
||||
expect(worktypes[0].custom_worktype_id).toBe('worktype2');
|
||||
expect(optionItems.length).toBe(10);
|
||||
}
|
||||
});
|
||||
|
||||
it('指定したWorktypeIDが登録されていない場合、400エラーとなること', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
const { id: worktypeId1 } = await createWorktype(
|
||||
source,
|
||||
account.id,
|
||||
'worktype1',
|
||||
);
|
||||
await createOptionItems(source, worktypeId1);
|
||||
|
||||
// 作成したデータを確認
|
||||
{
|
||||
const worktypes = await getWorktypes(source, account.id);
|
||||
const optionItems = await getOptionItems(source);
|
||||
expect(worktypes.length).toBe(1);
|
||||
expect(worktypes[0].id).toBe(worktypeId1);
|
||||
expect(worktypes[0].custom_worktype_id).toBe('worktype1');
|
||||
expect(optionItems.length).toBe(10);
|
||||
}
|
||||
|
||||
try {
|
||||
await service.deleteWorktype(context, admin.external_id, 9999);
|
||||
fail(); // 例外が発生しない場合はテスト失敗
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E011003'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('指定したIDがWorkflowで使用されている場合、400エラーとなること', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
const { id: author } = await makeTestUser(source, {
|
||||
account_id: account.id,
|
||||
role: USER_ROLES.AUTHOR,
|
||||
});
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
const { id: worktypeId1 } = await createWorktype(
|
||||
source,
|
||||
account.id,
|
||||
'worktype1',
|
||||
);
|
||||
await createOptionItems(source, worktypeId1);
|
||||
await createWorkflow(source, account.id, author, worktypeId1);
|
||||
|
||||
// 作成したデータを確認
|
||||
{
|
||||
const worktypes = await getWorktypes(source, account.id);
|
||||
const optionItems = await getOptionItems(source);
|
||||
const workflows = await getWorkflows(source, account.id);
|
||||
expect(worktypes.length).toBe(1);
|
||||
expect(worktypes[0].id).toBe(worktypeId1);
|
||||
expect(worktypes[0].custom_worktype_id).toBe('worktype1');
|
||||
expect(optionItems.length).toBe(10);
|
||||
expect(workflows.length).toBe(1);
|
||||
expect(workflows[0].worktype_id).toBe(worktypeId1);
|
||||
}
|
||||
|
||||
try {
|
||||
await service.deleteWorktype(context, admin.external_id, worktypeId1);
|
||||
fail(); // 例外が発生しない場合はテスト失敗
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E011004'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('DBアクセスに失敗した場合、500エラーを返却する', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
const { id: worktypeId1 } = await createWorktype(
|
||||
source,
|
||||
account.id,
|
||||
'worktype1',
|
||||
);
|
||||
await createOptionItems(source, worktypeId1);
|
||||
|
||||
// 作成したデータを確認
|
||||
{
|
||||
const worktypes = await getWorktypes(source, account.id);
|
||||
const optionItems = await getOptionItems(source);
|
||||
expect(worktypes.length).toBe(1);
|
||||
expect(worktypes[0].id).toBe(worktypeId1);
|
||||
expect(worktypes[0].custom_worktype_id).toBe('worktype1');
|
||||
expect(optionItems.length).toBe(10);
|
||||
}
|
||||
//DBアクセスに失敗するようにする
|
||||
const worktypeService = module.get<WorktypesRepositoryService>(
|
||||
WorktypesRepositoryService,
|
||||
);
|
||||
worktypeService.deleteWorktype = jest.fn().mockRejectedValue('DB failed');
|
||||
|
||||
try {
|
||||
await service.deleteWorktype(context, admin.external_id, worktypeId1);
|
||||
fail(); // 例外が発生しない場合はテスト失敗
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOptionItems', () => {
|
||||
let source: DataSource = null;
|
||||
beforeEach(async () => {
|
||||
|
||||
@ -65,6 +65,7 @@ import {
|
||||
import { WorktypesRepositoryService } from '../../repositories/worktypes/worktypes.repository.service';
|
||||
import {
|
||||
WorktypeIdAlreadyExistsError,
|
||||
WorktypeIdInUseError,
|
||||
WorktypeIdMaxCountError,
|
||||
WorktypeIdNotFoundError,
|
||||
} from '../../repositories/worktypes/errors/types';
|
||||
@ -1417,6 +1418,81 @@ export class AccountsService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ワークタイプを削除します
|
||||
* @param context
|
||||
* @param externalId
|
||||
* @param id
|
||||
* @returns worktype
|
||||
*/
|
||||
async deleteWorktype(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
id: number,
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.deleteWorktype.name} | params: { ` +
|
||||
`externalId: ${externalId}, ` +
|
||||
`id: ${id} };`,
|
||||
);
|
||||
|
||||
try {
|
||||
// 外部IDをもとにユーザー情報を取得する
|
||||
const { account, account_id: accountId } =
|
||||
await this.usersRepository.findUserByExternalId(externalId);
|
||||
|
||||
if (!account) {
|
||||
throw new AccountNotFoundError(
|
||||
`account not found. externalId: ${externalId}`,
|
||||
);
|
||||
}
|
||||
|
||||
// ワークタイプを削除する
|
||||
await this.worktypesRepository.deleteWorktype(accountId, id);
|
||||
} catch (e) {
|
||||
this.logger.error(`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,
|
||||
);
|
||||
// 内部IDで指定されたWorktypeが存在しない場合は400エラーを返す
|
||||
case WorktypeIdNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E011003'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
// 内部IDで指定されたWorktypeがWorkflowで使用中の場合は400エラーを返す
|
||||
case WorktypeIdInUseError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E011004'),
|
||||
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.deleteWorktype.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ワークタイプに紐づいたオプションアイテム一覧を取得します
|
||||
* @param context
|
||||
|
||||
@ -4,3 +4,5 @@ export class WorktypeIdAlreadyExistsError extends Error {}
|
||||
export class WorktypeIdMaxCountError extends Error {}
|
||||
// WorktypeID不在エラー
|
||||
export class WorktypeIdNotFoundError extends Error {}
|
||||
// WorktypeID使用中エラー
|
||||
export class WorktypeIdInUseError extends Error {}
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
} from '../../constants';
|
||||
import {
|
||||
WorktypeIdAlreadyExistsError,
|
||||
WorktypeIdInUseError,
|
||||
WorktypeIdMaxCountError,
|
||||
WorktypeIdNotFoundError,
|
||||
} from './errors/types';
|
||||
@ -15,6 +16,7 @@ import { OptionItem } from './entity/option_item.entity';
|
||||
import { PostWorktypeOptionItem } from '../../features/accounts/types/types';
|
||||
import { AccountNotFoundError } from '../accounts/errors/types';
|
||||
import { Account } from '../accounts/entity/account.entity';
|
||||
import { Workflow } from '../workflows/entity/workflow.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WorktypesRepositoryService {
|
||||
@ -156,6 +158,45 @@ export class WorktypesRepositoryService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ワークタイプを削除する
|
||||
* @param accountId
|
||||
* @param id
|
||||
* @returns worktype
|
||||
*/
|
||||
async deleteWorktype(accountId: number, id: number): Promise<void> {
|
||||
await this.dataSource.transaction(async (entityManager) => {
|
||||
const worktypeRepo = entityManager.getRepository(Worktype);
|
||||
|
||||
const worktype = await worktypeRepo.findOne({
|
||||
where: { account_id: accountId, id: id },
|
||||
});
|
||||
// ワークタイプが存在しない場合はエラー
|
||||
if (!worktype) {
|
||||
throw new WorktypeIdNotFoundError(`Worktype is not found. id: ${id}`);
|
||||
}
|
||||
|
||||
// ワークタイプがワークフローに紐づいている場合はエラー
|
||||
const workflowRepo = entityManager.getRepository(Workflow);
|
||||
const workflows = await workflowRepo.find({
|
||||
where: { account_id: accountId, worktype_id: id },
|
||||
});
|
||||
if (workflows.length > 0) {
|
||||
const workflowIds = workflows.map((workflow) => workflow.id);
|
||||
throw new WorktypeIdInUseError(
|
||||
`Worktype is in use by workflow. worktype id: ${id}, workflow ids: [${workflowIds}]`,
|
||||
);
|
||||
}
|
||||
|
||||
// ワークタイプに紐づくオプションアイテムを削除
|
||||
const optionItemRepo = entityManager.getRepository(OptionItem);
|
||||
await optionItemRepo.delete({ worktype_id: id });
|
||||
|
||||
// ワークタイプを削除
|
||||
await worktypeRepo.delete({ id: id });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* オプションアイテム一覧を取得する
|
||||
* @param accountId
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user