Merged PR 575: タイピスト割り当てで、未認証のユーザーは割り当てられないようにする

## 概要
[Task2818: タイピスト割り当てで、未認証のユーザーは割り当てられないようにする](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2818)

- 以下のAPIについて未認証のタイピストを割り当て内容にユーザー取得処理とテストを修正しました。
  - Typist一覧取得
  - Workflow追加
  - Workflow編集
  - ユーザーグループ追加
  - ユーザーグループ編集
  - 割り当て候補変更

## レビューポイント
- 対応箇所は適切か

## UIの変更
- なし

## 動作確認状況
- ローカルで確認
This commit is contained in:
makabe.t 2023-11-14 02:30:46 +00:00
parent 5c0d5f6476
commit effbfe9d46
7 changed files with 599 additions and 42 deletions

View File

@ -2752,7 +2752,6 @@ describe('createTypistGroup', () => {
expect(typistGroupUsers.map((user) => user.user_id)).toEqual(userIds);
}
});
it('typistIdsにRole:typist以外のユーザーが含まれていた場合、400エラーを返却する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
@ -2859,6 +2858,58 @@ describe('createTypistGroup', () => {
new HttpException(makeErrorResponse('E010204'), HttpStatus.BAD_REQUEST),
);
});
it('typistIdsにメール未認証のユーザーが含まれていた場合、400エラーを返却する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const adminExternalId = 'admin-external-id';
// 第五階層のアカウント作成
const { id: accountId } = (
await makeTestAccount(
source,
{ tier: 5 },
{ external_id: adminExternalId },
)
).account;
// 作成したアカウントにユーザーを3名追加する
const typiptUserExternalIds = [
'typist-user-external-id1',
'typist-user-external-id2',
'typist-user-external-id3',
];
const userIds: number[] = [];
for (const typiptUserExternalId of typiptUserExternalIds) {
const user = await makeTestUser(source, {
account_id: accountId,
external_id: typiptUserExternalId,
role: 'typist',
email_verified: false, // メール未認証のユーザーを追加
});
userIds.push(user?.id ?? 0);
}
//作成したデータを確認
{
const accounts = await getAccounts(source);
expect(accounts.length).toBe(1);
expect(accounts[0].id).toBe(accountId);
const users = await getUsers(source);
expect(users.length).toBe(4);
}
const service = module.get<AccountsService>(AccountsService);
const typistGroupName = 'typist-group-name';
const typistUserIds = [...userIds];
const context = makeContext(adminExternalId);
await expect(
service.createTypistGroup(
context,
adminExternalId,
typistGroupName,
typistUserIds,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010204'), HttpStatus.BAD_REQUEST),
);
});
it('DBアクセスに失敗した場合、500エラーを返却する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
@ -3338,6 +3389,69 @@ describe('updateTypistGroup', () => {
}
}
});
it('typistIdsにメール未認証のユーザーが含まれていた場合、400エラーを返却する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
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 user = await makeTestUser(source, {
account_id: account.id,
external_id: typiptUserExternalId,
role: USER_ROLES.TYPIST,
email_verified: typiptUserExternalId !== 'typist-user-external-id3', //typist-user-external-id3のみメール未認証
});
userIds.push(user?.id ?? 0);
}
const typistGroupName = 'typist-group-name';
const service = module.get<AccountsService>(AccountsService);
const typistUserIds = [...userIds];
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 () => {
if (!source) fail();
const module = await makeTestingModule(source);
@ -5843,6 +5957,185 @@ describe('getAuthors', () => {
}
});
});
describe('getTypists', () => {
let source: DataSource | null = 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 () => {
if (!source) return;
await source.destroy();
source = null;
});
it('アカウント内のTypistユーザーの一覧を取得できる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
const userId1 = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.TYPIST,
external_id: 'typist1',
});
const userId2 = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.TYPIST,
external_id: 'typist2',
});
const userId3 = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.AUTHOR,
author_id: 'AUTHOR_ID_1',
external_id: 'author1',
});
// 作成したデータを確認
{
const users = await getUsers(source);
expect(users.length).toBe(4);
expect(users[1].id).toBe(userId1.id);
expect(users[2].id).toBe(userId2.id);
expect(users[3].id).toBe(userId3.id);
}
const service = module.get<AccountsService>(AccountsService);
overrideAdB2cService(service, {
getUsers: async () => [
{ id: admin.external_id, displayName: '' },
{ id: userId1.external_id, displayName: '' },
{ id: userId2.external_id, displayName: '' },
{ id: userId3.external_id, displayName: '' },
],
});
const typists = await service.getTypists(admin.external_id);
//実行結果を確認
{
expect(typists.length).toBe(2);
expect(typists[0].id).toBe(userId1.id);
expect(typists[1].id).toBe(userId2.id);
}
});
it('アカウント内のTypistユーザーの一覧を取得できる0件', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 5 });
// 作成したデータを確認
{
const users = await getUsers(source);
expect(users.length).toBe(1);
}
const service = module.get<AccountsService>(AccountsService);
overrideAdB2cService(service, {
getUsers: async () => [{ id: admin.external_id, displayName: '' }],
});
const typists = await service.getTypists(admin.external_id);
//実行結果を確認
{
expect(typists.length).toBe(0);
}
});
it('アカウント内のTypistユーザーの一覧を取得できるメール認証済みユーザーのみ', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
const userId1 = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.TYPIST,
});
const userId2 = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.TYPIST,
});
const userId3 = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.TYPIST,
email_verified: false,
});
// 作成したデータを確認
{
const users = await getUsers(source);
expect(users.length).toBe(4);
expect(users[1].id).toBe(userId1.id);
expect(users[2].id).toBe(userId2.id);
expect(users[3].id).toBe(userId3.id);
}
const service = module.get<AccountsService>(AccountsService);
overrideAdB2cService(service, {
getUsers: async () => [
{ id: admin.external_id, displayName: '' },
{ id: userId1.external_id, displayName: '' },
{ id: userId2.external_id, displayName: '' },
{ id: userId3.external_id, displayName: '' },
],
});
const typists = await service.getTypists(admin.external_id);
//実行結果を確認
{
expect(typists.length).toBe(2);
expect(typists[0].id).toBe(userId1.id);
expect(typists[1].id).toBe(userId2.id);
}
});
it('DBアクセスに失敗した場合、500エラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 5 });
const service = module.get<AccountsService>(AccountsService);
overrideAdB2cService(service, {
getUsers: async () => [{ id: admin.external_id, displayName: '' }],
});
//DBアクセスに失敗するようにする
const usersService = module.get<UsersRepositoryService>(
UsersRepositoryService,
);
usersService.findTypistUsers = jest.fn().mockRejectedValue('DB failed');
//実行結果を確認
try {
await service.getTypists(admin.external_id);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
} else {
fail();
}
}
});
});
describe('deleteAccountAndData', () => {
let source: DataSource | null = null;
beforeEach(async () => {

View File

@ -1012,17 +1012,89 @@ describe('changeCheckoutPermission', () => {
await createCheckoutPermissions(source, taskId, undefined, userGroupId);
const service = module.get<TasksService>(TasksService);
await expect(
service.changeCheckoutPermission(
try {
await service.changeCheckoutPermission(
makeContext('trackingId'),
1,
[{ typistName: 'not-exist-user', typistUserId: 999 }],
'author-user-external-id',
['admin'],
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010204'), HttpStatus.BAD_REQUEST),
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
} else {
fail();
}
}
});
it('ユーザーがメール認証されていない場合、タスクのチェックアウト権限を変更できない', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: typistUserId_1 } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id_1',
role: 'typist',
});
const { id: typistUserId_2 } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id_2',
role: 'typist',
email_verified: false,
});
const { id: authorUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'MY_AUTHOR_ID',
});
const { taskId } = await createTask(
source,
accountId,
authorUserId,
'MY_AUTHOR_ID',
'',
'01',
'00000001',
'Uploaded',
);
const { userGroupId } = await createUserGroup(
source,
accountId,
'USER_GROUP_A',
[typistUserId_1],
);
await createCheckoutPermissions(source, taskId, typistUserId_1);
await createCheckoutPermissions(source, taskId, undefined, userGroupId);
const service = module.get<TasksService>(TasksService);
try {
await service.changeCheckoutPermission(
makeContext('trackingId'),
1,
[{ typistName: 'not-verified-user', typistUserId: typistUserId_2 }],
'author-user-external-id',
['admin'],
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
} else {
fail();
}
}
});
it('ユーザーグループが存在しない場合、タスクのチェックアウト権限を変更できない', async () => {
@ -1066,17 +1138,23 @@ describe('changeCheckoutPermission', () => {
await createCheckoutPermissions(source, taskId, undefined, userGroupId);
const service = module.get<TasksService>(TasksService);
await expect(
service.changeCheckoutPermission(
try {
await service.changeCheckoutPermission(
makeContext('trackingId'),
1,
[{ typistName: 'not-exist-user-group', typistGroupId: 999 }],
'author-user-external-id',
['admin'],
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010204'), HttpStatus.BAD_REQUEST),
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
} else {
fail();
}
}
});
it('タスクが存在しない場合、タスクのチェックアウト権限を変更できない', async () => {
@ -1102,17 +1180,23 @@ describe('changeCheckoutPermission', () => {
});
const service = module.get<TasksService>(TasksService);
await expect(
service.changeCheckoutPermission(
try {
await service.changeCheckoutPermission(
makeContext('trackingId'),
1,
[{ typistName: 'typist-user', typistUserId: typistUserId }],
'author-user-external-id',
['admin'],
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010601'));
} else {
fail();
}
}
});
it('タスクのステータスがUploadedでない場合、タスクのチェックアウト権限を変更できない', async () => {
@ -1148,17 +1232,23 @@ describe('changeCheckoutPermission', () => {
);
const service = module.get<TasksService>(TasksService);
await expect(
service.changeCheckoutPermission(
try {
await service.changeCheckoutPermission(
makeContext('trackingId'),
1,
[{ typistName: 'typist-user', typistUserId: typistUserId }],
'author-user-external-id',
['admin'],
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010601'));
} else {
fail();
}
}
});
it('ユーザーのRoleがAuthorでタスクのAuthorIDと自身のAuthorIDが一致しない場合、タスクのチェックアウト権限を変更できない', async () => {
@ -1194,17 +1284,23 @@ describe('changeCheckoutPermission', () => {
);
const service = module.get<TasksService>(TasksService);
await expect(
service.changeCheckoutPermission(
try {
await service.changeCheckoutPermission(
makeContext('trackingId'),
1,
[{ typistName: 'typist-user', typistUserId: typistUserId }],
'author-user-external-id',
['author'],
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010601'));
} else {
fail();
}
}
});
it('通知に失敗した場合、エラーとなる', async () => {
@ -1254,20 +1350,23 @@ describe('changeCheckoutPermission', () => {
await createCheckoutPermissions(source, taskId, undefined, userGroupId);
const service = module.get<TasksService>(TasksService);
await expect(
service.changeCheckoutPermission(
try {
await service.changeCheckoutPermission(
makeContext('trackingId'),
1,
[{ typistName: 'typist-user-2', typistUserId: typistUserId_2 }],
'author-user-external-id',
['admin'],
),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
),
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
} else {
fail();
}
}
});
});

View File

@ -883,6 +883,74 @@ describe('createWorkflows', () => {
}
});
it('ルーティング候補ユーザーがメール未認証の場合、400エラーとなること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
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,
email_verified: false,
});
const { id: worktypeId } = await createWorktype(
source,
account.id,
'worktype1',
);
const { id: templateId } = await createTemplateFile(
source,
account.id,
'fileName1',
'url1',
);
//作成したデータを確認
{
const workflows = await getWorkflows(source, account.id);
const workflowTypists = await getAllWorkflowTypists(source);
expect(workflows.length).toBe(0);
expect(workflowTypists.length).toBe(0);
}
const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id);
//実行結果を確認
try {
await service.createWorkflow(
context,
admin.external_id,
authorId,
[
{
typistId: typistId,
},
],
worktypeId,
templateId,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
} else {
fail();
}
}
});
it('DBにルーティング候補グループが存在しない場合、400エラーとなること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
@ -1976,6 +2044,87 @@ describe('updateWorkflow', () => {
}
});
it('Dルーティング候補ユーザーがメール未認証の場合、400エラーとなること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
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: typistId1 } = await makeTestUser(source, {
external_id: 'typist1',
account_id: account.id,
role: USER_ROLES.TYPIST,
});
const { id: typistId2 } = await makeTestUser(source, {
external_id: 'typist2',
account_id: account.id,
role: USER_ROLES.TYPIST,
email_verified: false,
});
const { id: worktypeId } = await createWorktype(
source,
account.id,
'worktype1',
);
const { id: templateId } = await createTemplateFile(
source,
account.id,
'fileName1',
'url1',
);
const preWorkflow = await createWorkflow(
source,
account.id,
authorId1,
undefined,
undefined,
);
await createWorkflowTypist(source, preWorkflow.id, typistId1);
//作成したデータを確認
{
const workflows = await getWorkflows(source, account.id);
expect(workflows.length).toBe(1);
}
const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id);
//実行結果を確認
try {
await service.updateWorkflow(
context,
admin.external_id,
preWorkflow.id,
authorId1,
[
{
typistId: typistId2,
},
],
worktypeId,
templateId,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
} else {
fail();
}
}
});
it('DBにルーティング候補グループが存在しない場合、400エラーとなること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);

View File

@ -804,13 +804,14 @@ export class TasksRepositoryService {
id: In(typistUserIds),
account_id: account_id,
role: USER_ROLES.TYPIST,
email_verified: true,
deleted_at: IsNull(),
},
});
// idはユニークであるため取得件数の一致でユーザーの存在を確認
if (typistUserIds.length !== userRecords.length) {
throw new TypistUserNotFoundError(
`User not exists Error. reqUserId:${typistUserIds}; resUserId:${userRecords.map(
`User not exists or email not verified Error. reqUserId:${typistUserIds}; resUserId:${userRecords.map(
(x) => x.id,
)}`,
);

View File

@ -102,11 +102,12 @@ export class UserGroupsRepositoryService {
id: In(typistIds),
account_id: accountId,
role: USER_ROLES.TYPIST,
email_verified: true,
},
});
if (userRecords.length !== typistIds.length) {
throw new TypistIdInvalidError(
`Typist user not exists Error. typistIds:${typistIds}; typistIds(DB):${userRecords.map(
`Typist user not exists or email not verified Error. typistIds:${typistIds}; typistIds(DB):${userRecords.map(
(x) => x.id,
)}`,
);
@ -153,11 +154,12 @@ export class UserGroupsRepositoryService {
id: In(typistIds),
account_id: accountId,
role: USER_ROLES.TYPIST,
email_verified: true,
},
});
if (userRecords.length !== typistIds.length) {
throw new TypistIdInvalidError(
`Typist user not exists Error. typistIds:${typistIds}; typistIds(DB):${userRecords.map(
`Typist user not exists or email not verified Error. typistIds:${typistIds}; typistIds(DB):${userRecords.map(
(x) => x.id,
)}`,
);

View File

@ -366,7 +366,7 @@ export class UsersRepositoryService {
}
/**
*
*
* @param sub
* @returns typist users
*/
@ -389,6 +389,7 @@ export class UsersRepositoryService {
where: {
account_id: user.account_id,
role: USER_ROLES.TYPIST,
email_verified: true,
deleted_at: IsNull(),
},
});

View File

@ -106,10 +106,16 @@ export class WorkflowsRepositoryService {
typist.typistId ? [typist.typistId] : [],
);
const typistUsers = await userRepo.find({
where: { account_id: accountId, id: In(typistIds) },
where: {
account_id: accountId,
id: In(typistIds),
email_verified: true,
},
});
if (typistUsers.length !== typistIds.length) {
throw new UserNotFoundError(`typist not found. ids: ${typistIds}`);
throw new UserNotFoundError(
`typist not found or email not verified. ids: ${typistIds}`,
);
}
// ルーティング候補ユーザーグループの存在確認
@ -239,10 +245,16 @@ export class WorkflowsRepositoryService {
typist.typistId ? [typist.typistId] : [],
);
const typistUsers = await userRepo.find({
where: { account_id: accountId, id: In(typistIds) },
where: {
account_id: accountId,
id: In(typistIds),
email_verified: true,
},
});
if (typistUsers.length !== typistIds.length) {
throw new UserNotFoundError(`typist not found. ids: ${typistIds}`);
throw new UserNotFoundError(
`typist not found or email not verified. ids: ${typistIds}`,
);
}
// ルーティング候補ユーザーグループの存在確認