Merged PR 396: API実装(オプションアイテム取得)
## 概要 [Task2592: API実装(オプションアイテム取得)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2592) - オプションアイテム取得APIとテストを実装しました。 ## レビューポイント - リポジトリの取得ロジックは想定通りか - テストケースは適切か ## UIの変更 - なし ## 動作確認状況 - ローカルで確認
This commit is contained in:
parent
d8d5789f5a
commit
2dcb1c1f84
@ -41,7 +41,6 @@ import { UserGroupsRepositoryModule } from './repositories/user_groups/user_grou
|
||||
import { SortCriteriaRepositoryModule } from './repositories/sort_criteria/sort_criteria.repository.module';
|
||||
import { TemplateFilesRepositoryModule } from './repositories/template_files/template_files.repository.module';
|
||||
import { WorktypesRepositoryModule } from './repositories/worktypes/worktypes.repository.module';
|
||||
import { OptionItemsRepositoryModule } from './repositories/option_items/option_items.repository.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -97,7 +96,6 @@ import { OptionItemsRepositoryModule } from './repositories/option_items/option_
|
||||
AuthGuardsModule,
|
||||
SortCriteriaRepositoryModule,
|
||||
WorktypesRepositoryModule,
|
||||
OptionItemsRepositoryModule,
|
||||
],
|
||||
controllers: [
|
||||
HealthController,
|
||||
|
||||
@ -30,7 +30,6 @@ import { NotificationhubService } from '../../gateways/notificationhub/notificat
|
||||
import { FilesService } from '../../features/files/files.service';
|
||||
import { LicensesService } from '../../features/licenses/licenses.service';
|
||||
import { TasksService } from '../../features/tasks/tasks.service';
|
||||
import { OptionItemsRepositoryModule } from '../../repositories/option_items/option_items.repository.module';
|
||||
|
||||
export const makeTestingModule = async (
|
||||
datasource: DataSource,
|
||||
@ -66,7 +65,6 @@ export const makeTestingModule = async (
|
||||
AuthGuardsModule,
|
||||
SortCriteriaRepositoryModule,
|
||||
WorktypesRepositoryModule,
|
||||
OptionItemsRepositoryModule,
|
||||
],
|
||||
providers: [
|
||||
AuthService,
|
||||
|
||||
@ -815,63 +815,13 @@ export class AccountsController {
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
console.log('id', id);
|
||||
console.log(context.trackingId);
|
||||
const optionItems = await this.accountService.getOptionItems(
|
||||
context,
|
||||
userId,
|
||||
id,
|
||||
);
|
||||
|
||||
return {
|
||||
optionItems: [
|
||||
{
|
||||
itemLabel: '',
|
||||
defaultValueType: OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
initialValue: '',
|
||||
},
|
||||
{
|
||||
itemLabel: '',
|
||||
defaultValueType: OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
initialValue: '',
|
||||
},
|
||||
{
|
||||
itemLabel: '',
|
||||
defaultValueType: OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
initialValue: '',
|
||||
},
|
||||
{
|
||||
itemLabel: '',
|
||||
defaultValueType: OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
initialValue: '',
|
||||
},
|
||||
{
|
||||
itemLabel: '',
|
||||
defaultValueType: OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
initialValue: '',
|
||||
},
|
||||
{
|
||||
itemLabel: '',
|
||||
defaultValueType: OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
initialValue: '',
|
||||
},
|
||||
{
|
||||
itemLabel: '',
|
||||
defaultValueType: OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
initialValue: '',
|
||||
},
|
||||
{
|
||||
itemLabel: '',
|
||||
defaultValueType: OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
initialValue: '',
|
||||
},
|
||||
{
|
||||
itemLabel: '',
|
||||
defaultValueType: OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
initialValue: '',
|
||||
},
|
||||
{
|
||||
itemLabel: '',
|
||||
defaultValueType: OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
initialValue: '',
|
||||
},
|
||||
],
|
||||
};
|
||||
return optionItems;
|
||||
}
|
||||
|
||||
@Post('/worktypes/:id/option-items')
|
||||
|
||||
@ -16,6 +16,7 @@ import {
|
||||
createLicense,
|
||||
createLicenseOrder,
|
||||
createLicenseSetExpiryDateAndStatus,
|
||||
createOptionItems,
|
||||
createWorktype,
|
||||
getOptionItems,
|
||||
getSortCriteria,
|
||||
@ -1505,6 +1506,7 @@ describe('AccountsService', () => {
|
||||
makeDefaultLicensesRepositoryMockValue();
|
||||
const worktypesRepositoryMockValue =
|
||||
makeDefaultWorktypesRepositoryMockValue();
|
||||
|
||||
const service = await makeAccountsServiceMock(
|
||||
accountsRepositoryMockValue,
|
||||
usersRepositoryMockValue,
|
||||
@ -3863,6 +3865,135 @@ describe('updateWorktype', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOptionItems', () => {
|
||||
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に紐づいたOptionItemを取得できる', 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 worktype = await createWorktype(source, account.id, 'worktype1');
|
||||
const optionItems = await createOptionItems(source, worktype.id);
|
||||
|
||||
//作成したデータを確認
|
||||
{
|
||||
expect(optionItems.length).toBe(10);
|
||||
expect(optionItems[0].item_label).toBe('');
|
||||
expect(optionItems[0].default_value_type).toBe(
|
||||
OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
);
|
||||
expect(optionItems[0].initial_value).toBe('');
|
||||
}
|
||||
|
||||
const resOptionItems = await service.getOptionItems(
|
||||
context,
|
||||
admin.external_id,
|
||||
worktype.id,
|
||||
);
|
||||
|
||||
//実行結果を確認
|
||||
{
|
||||
expect(resOptionItems.optionItems.length).toBe(10);
|
||||
expect(resOptionItems.optionItems[0].itemLabel).toBe('');
|
||||
expect(resOptionItems.optionItems[0].defaultValueType).toBe(
|
||||
OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
);
|
||||
expect(resOptionItems.optionItems[0].initialValue).toBe('');
|
||||
}
|
||||
});
|
||||
|
||||
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 worktype = await createWorktype(source, account.id, 'worktype1');
|
||||
const optionItems = await createOptionItems(source, worktype.id);
|
||||
|
||||
//作成したデータを確認
|
||||
{
|
||||
expect(optionItems.length).toBe(10);
|
||||
expect(optionItems[0].item_label).toBe('');
|
||||
expect(optionItems[0].default_value_type).toBe(
|
||||
OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
);
|
||||
expect(optionItems[0].initial_value).toBe('');
|
||||
}
|
||||
|
||||
try {
|
||||
await service.getOptionItems(context, admin.external_id, 999);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E011003'));
|
||||
} 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 worktype = await createWorktype(source, account.id, 'worktype1');
|
||||
const optionItems = await createOptionItems(source, worktype.id);
|
||||
|
||||
//作成したデータを確認
|
||||
{
|
||||
expect(optionItems.length).toBe(10);
|
||||
expect(optionItems[0].item_label).toBe('');
|
||||
expect(optionItems[0].default_value_type).toBe(
|
||||
OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
);
|
||||
expect(optionItems[0].initial_value).toBe('');
|
||||
}
|
||||
|
||||
//DBアクセスに失敗するようにする
|
||||
const worktypesService = module.get<WorktypesRepositoryService>(
|
||||
WorktypesRepositoryService,
|
||||
);
|
||||
worktypesService.getOptionItems = jest.fn().mockRejectedValue('DB failed');
|
||||
|
||||
try {
|
||||
await service.getWorktypes(context, admin.external_id);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('ライセンス発行キャンセル', () => {
|
||||
let source: DataSource = null;
|
||||
beforeEach(async () => {
|
||||
|
||||
@ -23,6 +23,7 @@ import {
|
||||
GetMyAccountResponse,
|
||||
GetTypistGroupResponse,
|
||||
GetWorktypesResponse,
|
||||
GetOptionItemsResponse,
|
||||
GetPartnersResponse,
|
||||
} from './types/types';
|
||||
import {
|
||||
@ -1314,6 +1315,69 @@ export class AccountsService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ワークタイプに紐づいたオプションアイテム一覧を取得します
|
||||
* @param context
|
||||
* @param externalId
|
||||
* @param id Worktypeの内部ID
|
||||
* @returns option items
|
||||
*/
|
||||
async getOptionItems(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
id: number,
|
||||
): Promise<GetOptionItemsResponse> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.getOptionItems.name} | params: { ` +
|
||||
`externalId: ${externalId}, ` +
|
||||
`id: ${id} };`,
|
||||
);
|
||||
try {
|
||||
// 外部IDをもとにユーザー情報を取得する
|
||||
const { account_id: accountId } =
|
||||
await this.usersRepository.findUserByExternalId(externalId);
|
||||
|
||||
// オプションアイテム一覧を取得する
|
||||
const optionItems = await this.worktypesRepository.getOptionItems(
|
||||
accountId,
|
||||
id,
|
||||
);
|
||||
|
||||
return {
|
||||
optionItems: optionItems.map((x) => ({
|
||||
itemLabel: x.item_label,
|
||||
defaultValueType: x.default_value_type,
|
||||
initialValue: x.initial_value,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
this.logger.error(e);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
// 内部IDで指定されたWorktypeが存在しない場合は400エラーを返す
|
||||
case WorktypeIdNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E011003'),
|
||||
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.getOptionItems.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* パートナー一覧を取得します
|
||||
* @param context
|
||||
|
||||
@ -7,7 +7,8 @@ import { SortCriteria } from '../../../repositories/sort_criteria/entity/sort_cr
|
||||
import { UserGroup } from '../../../repositories/user_groups/entity/user_group.entity';
|
||||
import { UserGroupMember } from '../../../repositories/user_groups/entity/user_group_member.entity';
|
||||
import { Worktype } from '../../../repositories/worktypes/entity/worktype.entity';
|
||||
import { OptionItem } from '../../../repositories/option_items/entity/option_item.entity';
|
||||
import { OptionItem } from '../../../repositories/worktypes/entity/option_item.entity';
|
||||
import { OPTION_ITEM_VALUE_TYPE } from '../../../constants';
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: すべてのソート条件を取得する
|
||||
@ -153,6 +154,35 @@ export const getWorktypes = async (
|
||||
});
|
||||
};
|
||||
|
||||
// オプションアイテムを作成する
|
||||
export const createOptionItems = async (
|
||||
datasource: DataSource,
|
||||
worktypeId: number,
|
||||
): Promise<OptionItem[]> => {
|
||||
const optionItems = [];
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
optionItems.push({
|
||||
worktype_id: worktypeId,
|
||||
item_label: '',
|
||||
default_value_type: OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
initial_value: '',
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
await datasource.getRepository(OptionItem).insert(optionItems);
|
||||
|
||||
const items = datasource
|
||||
.getRepository(OptionItem)
|
||||
.find({ where: { worktype_id: worktypeId } });
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
// オプションアイテムを取得する
|
||||
export const getOptionItems = async (
|
||||
datasource: DataSource,
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { OptionItem } from './entity/option_item.entity';
|
||||
import { OptionItemsRepositoryService } from './option_items.repository.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([OptionItem])],
|
||||
providers: [OptionItemsRepositoryService],
|
||||
exports: [OptionItemsRepositoryService],
|
||||
})
|
||||
export class OptionItemsRepositoryModule {}
|
||||
@ -1,7 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class OptionItemsRepositoryService {
|
||||
constructor(private dataSource: DataSource) {}
|
||||
}
|
||||
@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Worktype } from './entity/worktype.entity';
|
||||
import { WorktypesRepositoryService } from './worktypes.repository.service';
|
||||
import { OptionItem } from './entity/option_item.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Worktype])],
|
||||
imports: [TypeOrmModule.forFeature([Worktype, OptionItem])],
|
||||
providers: [WorktypesRepositoryService],
|
||||
exports: [WorktypesRepositoryService],
|
||||
})
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
WorktypeIdMaxCountError,
|
||||
WorktypeIdNotFoundError,
|
||||
} from './errors/types';
|
||||
import { OptionItem } from '../option_items/entity/option_item.entity';
|
||||
import { OptionItem } from './entity/option_item.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WorktypesRepositoryService {
|
||||
@ -135,4 +135,37 @@ export class WorktypesRepositoryService {
|
||||
await worktypeRepo.save(worktype);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* オプションアイテム一覧を取得する
|
||||
* @param accountId
|
||||
* @param worktypeId worktypeの内部ID
|
||||
* @returns option items
|
||||
*/
|
||||
async getOptionItems(
|
||||
accountId: number,
|
||||
worktypeId: number,
|
||||
): Promise<OptionItem[]> {
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const repoWorktype = entityManager.getRepository(Worktype);
|
||||
const repoOptionItem = entityManager.getRepository(OptionItem);
|
||||
|
||||
const worktype = await repoWorktype.findOne({
|
||||
where: { account_id: accountId, id: worktypeId },
|
||||
});
|
||||
|
||||
// ワークタイプが存在しない場合はエラー
|
||||
if (!worktype) {
|
||||
throw new WorktypeIdNotFoundError(
|
||||
`Worktype is not found. id: ${worktypeId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const optionItems = await repoOptionItem.find({
|
||||
where: { worktype_id: worktypeId },
|
||||
});
|
||||
|
||||
return optionItems;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user