Merged PR 364: API実装(TypistGroup更新API)

## 概要
[Task2460: API実装(TypistGroup更新API)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2460)

- TypistGroup更新APIとテストを実装しました。

## レビューポイント
- DBの更新ロジックに問題はないか
- テストケースは適切か

## UIの変更
- なし

## 動作確認状況
- ローカルで確認
This commit is contained in:
makabe.t 2023-08-29 08:10:16 +00:00
parent 5b1c3a0e99
commit bba69651fe
7 changed files with 522 additions and 59 deletions

View File

@ -409,7 +409,7 @@
"post": {
"operationId": "updateTypistGroup",
"summary": "",
"description": "ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを編集します",
"description": "ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを更新します",
"parameters": [
{
"name": "typistGroupId",

View File

@ -363,7 +363,7 @@ export class AccountsController {
@ApiOperation({
operationId: 'updateTypistGroup',
description:
'ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを編集します',
'ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを更新します',
})
@ApiBearerAuth()
@UseGuards(AuthGuard)
@ -374,13 +374,22 @@ export class AccountsController {
@Body() body: UpdateTypistGroupRequest,
@Param() param: UpdateTypistGroupRequestParam,
): Promise<CreateTypistGroupResponse> {
const { typistGroupName, typistIds } = body;
const { typistGroupId } = param;
// アクセストークン取得
const accessToken = retrieveAuthorizationToken(req);
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
console.log(param.typistGroupId);
console.log(body.typistGroupName);
console.log(body.typistIds);
const context = makeContext(userId);
await this.accountService.updateTypistGroup(
context,
userId,
typistGroupId,
typistGroupName,
typistIds,
);
return {};
}

View File

@ -18,7 +18,6 @@ import {
getSortCriteria,
getTypistGroup,
getTypistGroupMember,
createTypistGroup,
} from './test/utility';
import { DataSource } from 'typeorm';
import { makeTestingModule } from '../../common/test/modules';
@ -2633,28 +2632,31 @@ describe('getTypistGroup', () => {
// アカウントにタイピストグループを作成する
const typistGroupName = 'typist-group-name';
const { id: typistGroupId } = await createTypistGroup(
source,
account.id,
const service = module.get<AccountsService>(AccountsService);
const context = makeContext(admin.external_id);
await service.createTypistGroup(
context,
admin.external_id,
typistGroupName,
userIds,
);
//作成したデータを確認
{
const group = await getTypistGroup(source, account.id);
{
expect(group.length).toBe(1);
expect(group[0].name).toBe(typistGroupName);
const groupUsers = await getTypistGroupMember(source, group[0].id);
expect(groupUsers.length).toBe(3);
expect(groupUsers.map((user) => user.user_id)).toEqual(userIds);
}
const service = module.get<AccountsService>(AccountsService);
const context = makeContext(admin.external_id);
const typistGroup = await service.getTypistGroup(
context,
admin.external_id,
typistGroupId,
group[0].id,
);
//実行結果を確認
{
@ -2688,7 +2690,14 @@ describe('getTypistGroup', () => {
// アカウントにタイピストグループを作成する
const typistGroupName = 'typist-group-name';
await createTypistGroup(source, account.id, typistGroupName, userIds);
const service = module.get<AccountsService>(AccountsService);
const context = makeContext(admin.external_id);
await service.createTypistGroup(
context,
admin.external_id,
typistGroupName,
userIds,
);
//作成したデータを確認
{
@ -2699,8 +2708,6 @@ describe('getTypistGroup', () => {
expect(groupUsers.length).toBe(3);
expect(groupUsers.map((user) => user.user_id)).toEqual(userIds);
}
const service = module.get<AccountsService>(AccountsService);
const context = makeContext(admin.external_id);
try {
await service.getTypistGroup(context, admin.external_id, 999);
@ -2736,25 +2743,25 @@ describe('getTypistGroup', () => {
// アカウントにタイピストグループを作成する
const typistGroupName = 'typist-group-name';
const service = module.get<AccountsService>(AccountsService);
const context = makeContext(admin.external_id);
const { id: typistGroupId } = await createTypistGroup(
source,
account.id,
await service.createTypistGroup(
context,
admin.external_id,
typistGroupName,
userIds,
);
//作成したデータを確認
{
const group = await getTypistGroup(source, account.id);
{
expect(group.length).toBe(1);
expect(group[0].name).toBe(typistGroupName);
const groupUsers = await getTypistGroupMember(source, group[0].id);
expect(groupUsers.length).toBe(3);
expect(groupUsers.map((user) => user.user_id)).toEqual(userIds);
}
const service = module.get<AccountsService>(AccountsService);
const context = makeContext(admin.external_id);
//DBアクセスに失敗するようにする
const typistGroupService = module.get<UserGroupsRepositoryService>(
@ -2765,7 +2772,349 @@ describe('getTypistGroup', () => {
.mockRejectedValue('DB failed');
try {
await service.getTypistGroup(context, admin.external_id, typistGroupId);
await service.getTypistGroup(context, admin.external_id, group[0].id);
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
} else {
fail();
}
}
});
});
describe('updateTypistGroup', () => {
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('TypistGroupを更新できる', async () => {
const module = await makeTestingModule(source);
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
// 作成したアカウントにユーザーを3名追加する
const typiptUserExternalIds = [
'typist-user-external-id1',
'typist-user-external-id2',
'typist-user-external-id3',
];
const userIds: number[] = [];
for (const typiptUserExternalId of typiptUserExternalIds) {
const { id: userId } = await makeTestUser(source, {
account_id: account.id,
external_id: typiptUserExternalId,
role: USER_ROLES.TYPIST,
});
userIds.push(userId);
}
const service = module.get<AccountsService>(AccountsService);
const typistUserIds = [userIds[1]];
const context = makeContext(admin.external_id);
const typistGroupName = 'typist-group-name';
await service.createTypistGroup(
context,
admin.external_id,
typistGroupName,
[userIds[0]],
);
//作成したデータを確認
const group = await getTypistGroup(source, account.id);
{
const group = await getTypistGroup(source, account.id);
expect(group.length).toBe(1);
expect(group[0].name).toBe(typistGroupName);
const groupUsers = await getTypistGroupMember(source, group[0].id);
expect(groupUsers.length).toBe(1);
expect(groupUsers[0].user_group_id).toEqual(group[0].id);
expect(groupUsers[0].user_id).toEqual(userIds[0]);
}
const updateTypistGroupName = 'typist-group-name-update';
await service.updateTypistGroup(
context,
admin.external_id,
group[0].id,
updateTypistGroupName,
typistUserIds,
);
//実行結果を確認
{
const typistGroups = await getTypistGroup(source, account.id);
expect(typistGroups.length).toBe(1);
expect(typistGroups[0].name).toBe(updateTypistGroupName);
const typistGroupUsers = await getTypistGroupMember(
source,
typistGroups[0].id,
);
expect(typistGroupUsers.length).toBe(1);
expect(typistGroupUsers[0].user_id).toEqual(userIds[1]);
}
});
it('typistIdsにRole:typist以外のユーザーが含まれていた場合、400エラーを返却する', async () => {
const module = await makeTestingModule(source);
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
// 作成したアカウントにユーザーを3名追加する
const typiptUserExternalIds = [
'typist-user-external-id1',
'typist-user-external-id2',
'typist-user-external-id3',
];
const userIds: number[] = [];
for (const typiptUserExternalId of typiptUserExternalIds) {
const { id: userId } = await makeTestUser(source, {
account_id: account.id,
external_id: typiptUserExternalId,
role:
typiptUserExternalId === 'typist-user-external-id3'
? USER_ROLES.NONE
: USER_ROLES.TYPIST, //typist-user-external-id3のみRole:none
});
userIds.push(userId);
}
const typistGroupName = 'typist-group-name';
const service = module.get<AccountsService>(AccountsService);
const typistUserIds = [userIds[2]];
const context = makeContext(admin.external_id);
await service.createTypistGroup(
context,
admin.external_id,
typistGroupName,
[userIds[0]],
);
//作成したデータを確認
const group = await getTypistGroup(source, account.id);
{
expect(group.length).toBe(1);
expect(group[0].name).toBe(typistGroupName);
const groupUsers = await getTypistGroupMember(source, group[0].id);
expect(groupUsers.length).toBe(1);
expect(groupUsers[0].user_group_id).toEqual(group[0].id);
expect(groupUsers[0].user_id).toEqual(userIds[0]);
}
const updateTypistGroupName = 'typist-group-name-update';
try {
await service.updateTypistGroup(
context,
admin.external_id,
group[0].id,
updateTypistGroupName,
typistUserIds,
);
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
} else {
fail();
}
}
});
it('typistIdsに存在しないユーザーが含まれていた場合、400エラーを返却する', async () => {
const module = await makeTestingModule(source);
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
// 作成したアカウントにユーザーを3名追加する
const typiptUserExternalIds = [
'typist-user-external-id1',
'typist-user-external-id2',
'typist-user-external-id3',
];
const userIds: number[] = [];
for (const typiptUserExternalId of typiptUserExternalIds) {
const { id: userId } = await makeTestUser(source, {
account_id: account.id,
external_id: typiptUserExternalId,
role: USER_ROLES.TYPIST,
});
userIds.push(userId);
}
const typistGroupName = 'typist-group-name';
const service = module.get<AccountsService>(AccountsService);
const typistUserIds = [999];
const context = makeContext(admin.external_id);
await service.createTypistGroup(
context,
admin.external_id,
typistGroupName,
[userIds[0]],
);
//作成したデータを確認
const group = await getTypistGroup(source, account.id);
{
expect(group.length).toBe(1);
expect(group[0].name).toBe(typistGroupName);
const groupUsers = await getTypistGroupMember(source, group[0].id);
expect(groupUsers.length).toBe(1);
expect(groupUsers[0].user_group_id).toEqual(group[0].id);
expect(groupUsers[0].user_id).toEqual(userIds[0]);
}
const updateTypistGroupName = 'typist-group-name-update';
try {
await service.updateTypistGroup(
context,
admin.external_id,
group[0].id,
updateTypistGroupName,
typistUserIds,
);
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
} else {
fail();
}
}
});
it('タイピストグループが存在しない場合、400エラーを返却する', async () => {
const module = await makeTestingModule(source);
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
// 作成したアカウントにユーザーを3名追加する
const typiptUserExternalIds = [
'typist-user-external-id1',
'typist-user-external-id2',
'typist-user-external-id3',
];
const userIds: number[] = [];
for (const typiptUserExternalId of typiptUserExternalIds) {
const { id: userId } = await makeTestUser(source, {
account_id: account.id,
external_id: typiptUserExternalId,
role: USER_ROLES.TYPIST,
});
userIds.push(userId);
}
const typistGroupName = 'typist-group-name';
const service = module.get<AccountsService>(AccountsService);
const typistUserIds = [userIds[1]];
const context = makeContext(admin.external_id);
await service.createTypistGroup(
context,
admin.external_id,
typistGroupName,
[userIds[0]],
);
//作成したデータを確認
const group = await getTypistGroup(source, account.id);
{
expect(group.length).toBe(1);
expect(group[0].name).toBe(typistGroupName);
const groupUsers = await getTypistGroupMember(source, group[0].id);
expect(groupUsers.length).toBe(1);
expect(groupUsers[0].user_group_id).toEqual(group[0].id);
expect(groupUsers[0].user_id).toEqual(userIds[0]);
}
const updateTypistGroupName = 'typist-group-name-update';
try {
await service.updateTypistGroup(
context,
admin.external_id,
999,
updateTypistGroupName,
typistUserIds,
);
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010908'));
} else {
fail();
}
}
});
it('DBアクセスに失敗した場合、500エラーを返却する', async () => {
const module = await makeTestingModule(source);
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
// 作成したアカウントにユーザーを3名追加する
const typiptUserExternalIds = [
'typist-user-external-id1',
'typist-user-external-id2',
'typist-user-external-id3',
];
const userIds: number[] = [];
for (const typiptUserExternalId of typiptUserExternalIds) {
const { id: userId } = await makeTestUser(source, {
account_id: account.id,
external_id: typiptUserExternalId,
role: USER_ROLES.TYPIST,
});
userIds.push(userId);
}
const typistGroupName = 'typist-group-name';
const service = module.get<AccountsService>(AccountsService);
const typistUserIds = [userIds[1]];
const context = makeContext(admin.external_id);
await service.createTypistGroup(
context,
admin.external_id,
typistGroupName,
[userIds[0]],
);
//作成したデータを確認
const group = await getTypistGroup(source, account.id);
{
const group = await getTypistGroup(source, account.id);
expect(group.length).toBe(1);
expect(group[0].name).toBe(typistGroupName);
const groupUsers = await getTypistGroupMember(source, group[0].id);
expect(groupUsers.length).toBe(1);
expect(groupUsers[0].user_group_id).toEqual(group[0].id);
expect(groupUsers[0].user_id).toEqual(userIds[0]);
}
const updateTypistGroupName = 'typist-group-name-update';
//DBアクセスに失敗するようにする
const typistGroupService = module.get<UserGroupsRepositoryService>(
UserGroupsRepositoryService,
);
typistGroupService.updateTypistGroup = jest
.fn()
.mockRejectedValue('DB failed');
try {
await service.updateTypistGroup(
context,
admin.external_id,
group[0].id,
updateTypistGroupName,
typistUserIds,
);
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);

View File

@ -41,8 +41,10 @@ import {
OrderNotFoundError,
} from '../../repositories/licenses/errors/types';
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';
import { TypistGroupNotExistError } from '../../repositories/user_groups/errors/types';
import { TypistIdInvalidError } from '../../repositories/user_groups/errors/types';
import {
TypistGroupNotExistError,
TypistIdInvalidError,
} from '../../repositories/user_groups/errors/types';
@Injectable()
export class AccountsService {
@ -981,4 +983,70 @@ export class AccountsService {
);
}
}
/**
*
* @param context
* @param externalId
* @param typistGroupId
* @param typistGroupName
* @param typistIds
* @returns typist group
*/
async updateTypistGroup(
context: Context,
externalId: string,
typistGroupId: number,
typistGroupName: string,
typistIds: number[],
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.updateTypistGroup.name} | params: { typistGroupId: ${typistGroupId}, typistGroupName: ${typistGroupName}, typistIds: ${typistIds} };`,
);
try {
// 外部IDをもとにユーザー情報を取得する
const { account_id } = await this.usersRepository.findUserByExternalId(
externalId,
);
// タイピストグループと所属するタイピストを更新する
await this.userGroupsRepository.updateTypistGroup(
account_id,
typistGroupId,
typistGroupName,
typistIds,
);
} catch (e) {
this.logger.error(`error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
// タイピストIDが存在しない場合は400エラーを返す
case TypistIdInvalidError:
throw new HttpException(
makeErrorResponse('E010204'),
HttpStatus.BAD_REQUEST,
);
// タイピストグループIDが存在しない場合は400エラーを返す
case TypistGroupNotExistError:
throw new HttpException(
makeErrorResponse('E010908'),
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.updateTypistGroup.name}`,
);
}
}
}

View File

@ -108,34 +108,3 @@ export const getTypistGroupMember = async (
},
});
};
// タイピストグループを作成する
export const createTypistGroup = async (
datasource: DataSource,
accountId: number,
name: string,
memberIds: number[],
): Promise<UserGroup> => {
const { identifiers } = await datasource.getRepository(UserGroup).insert({
account_id: accountId,
name: name,
created_by: 'test_runner',
created_at: new Date(),
updated_by: 'updater',
updated_at: new Date(),
});
const userGroup = identifiers.pop() as UserGroup;
for (const memberId of memberIds) {
await datasource.getRepository(UserGroupMember).insert({
user_group_id: userGroup.id,
user_id: memberId,
created_by: 'test_runner',
created_at: new Date(),
updated_by: 'updater',
updated_at: new Date(),
});
}
return userGroup;
};

View File

@ -174,6 +174,7 @@ export class UpdateTypistGroupRequest {
@IsArray()
@IsInt({ each: true })
@Min(0, { each: true })
@IsUnique()
typistIds: number[];
}
export class UpdateTypistGroupRequestParam {

View File

@ -2,9 +2,8 @@ import { Injectable } from '@nestjs/common';
import { DataSource, In, IsNull } from 'typeorm';
import { UserGroup } from './entity/user_group.entity';
import { UserGroupMember } from './entity/user_group_member.entity';
import { TypistGroupNotExistError } from './errors/types';
import { User } from '../users/entity/user.entity';
import { TypistIdInvalidError } from './errors/types';
import { TypistGroupNotExistError, TypistIdInvalidError } from './errors/types';
import { USER_ROLES } from '../../constants';
@Injectable()
@ -130,4 +129,72 @@ export class UserGroupsRepositoryService {
return userGroup;
});
}
/**
* IDのタイピストグループを更新しtypistIdsのユーザーを紐付ける
* @param accountId
* @param name
* @param typistIds
* @returns createdTypistGroup
*/
async updateTypistGroup(
accountId: number,
typistGroupId: number,
typistGroupName: string,
typistIds: number[],
): Promise<UserGroup> {
return await this.dataSource.transaction(async (entityManager) => {
const userGroupRepo = entityManager.getRepository(UserGroup);
const userGroupMemberRepo = entityManager.getRepository(UserGroupMember);
// typistIdsのidを持つユーザーが、account_idのアカウントに所属していて、かつ、roleがtypistであることを確認する
const userRepo = entityManager.getRepository(User);
const userRecords = await userRepo.find({
where: {
id: In(typistIds),
account_id: accountId,
role: USER_ROLES.TYPIST,
},
});
if (userRecords.length !== typistIds.length) {
throw new TypistIdInvalidError(
`Typist user not exists Error. typistIds:${typistIds}; typistIds(DB):${userRecords.map(
(x) => x.id,
)}`,
);
}
// GroupIdが自アカウント内に存在するか確認する
const typistGroup = await userGroupRepo.findOne({
where: {
id: typistGroupId,
account_id: accountId,
},
});
if (!typistGroup) {
throw new TypistGroupNotExistError(
`TypistGroup not exists Error. accountId: ${accountId}; typistGroupId: ${typistGroupId}`,
);
}
// 対象のタイピストグループを更新する
// ユーザーグループ名を更新する
typistGroup.name = typistGroupName;
await userGroupRepo.save(typistGroup);
// user_group_membersテーブルから対象のタイピストグループのユーザーを削除する
await userGroupMemberRepo.delete({
user_group_id: typistGroupId,
});
const typistGroupMembers = userRecords.map((typist) => {
return {
user_group_id: typistGroup.id,
user_id: typist.id,
};
});
await userGroupMemberRepo.save(typistGroupMembers);
return typistGroup;
});
}
}