Merged PR 577: 文字起こしするタスクを複数持てないようにする

## 概要
[Task2223: 文字起こしするタスクを複数持てないようにする](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2223)

- タスクをチェックアウト済みの場合は他のタスクをチェックアウトできないように修正しました。

## レビューポイント
- テストケース追加は適切でしょうか?
- エラーとなった場合のエラーケースを`E010601`として返していますが処理として適切でしょうか?
  - タスクを変更できる状態でないということでこれを採用しています。

## UIの変更
- なし
## 動作確認状況
- ローカルで確認
This commit is contained in:
makabe.t 2023-11-16 01:40:02 +00:00
parent ad49a19f04
commit 64fe6bcbe7
4 changed files with 219 additions and 88 deletions

View File

@ -1421,12 +1421,7 @@ describe('checkout', () => {
it('ユーザーのRoleがTypistで、タスクのチェックアウト権限が個人指定である時、タスクをチェックアウトできる', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: typistUserId } = await makeTestUser(source, {
@ -1486,12 +1481,7 @@ describe('checkout', () => {
it('ユーザーのRoleがTypistで、タスクのチェックアウト権限がグループ指定である時、タスクをチェックアウトできる', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: typistUserId } = await makeTestUser(source, {
@ -1551,12 +1541,7 @@ describe('checkout', () => {
it('ユーザーのRoleがTypistで、タスクのステータスがPendingである時、タスクをチェックアウトできる', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: typistUserId } = await makeTestUser(source, {
@ -1610,12 +1595,7 @@ describe('checkout', () => {
it('ユーザーのRoleがTypistで、対象のタスクのStatus[Uploaded,Inprogress,Pending]以外の時、タスクをチェックアウトできない', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
await makeTestUser(source, {
@ -1642,26 +1622,27 @@ describe('checkout', () => {
);
const service = module.get<TasksService>(TasksService);
await expect(
service.checkout(
try {
await service.checkout(
makeContext('trackingId'),
1,
['typist'],
'typist-user-external-id',
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
);
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010601'));
} else {
fail();
}
}
});
it('ユーザーのRoleがTypistで、チェックアウト権限が存在しない時、タスクをチェックアウトできない', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
await makeTestUser(source, {
@ -1688,26 +1669,153 @@ describe('checkout', () => {
);
const service = module.get<TasksService>(TasksService);
await expect(
service.checkout(
try {
await service.checkout(
makeContext('trackingId'),
1,
['typist'],
'typist-user-external-id',
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010602'), HttpStatus.BAD_REQUEST),
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010602'));
} else {
fail();
}
}
});
it('ユーザーのRoleがTypistで、既にチェックアウト中のタスクInProgressがある時、タスクをチェックアウトできない', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: typistUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id',
role: 'typist',
});
const { id: authorUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'MY_AUTHOR_ID',
});
await createTask(
source,
accountId,
authorUserId,
'MY_AUTHOR_ID',
'',
'01',
'00000001',
TASK_STATUS.IN_PROGRESS,
typistUserId,
);
const { taskId, audioFileId } = await createTask(
source,
accountId,
authorUserId,
'MY_AUTHOR_ID',
'',
'01',
'00000002',
TASK_STATUS.UPLOADED,
);
await createCheckoutPermissions(source, taskId, typistUserId);
const service = module.get<TasksService>(TasksService);
try {
await service.checkout(
makeContext('trackingId'),
audioFileId,
['typist'],
'typist-user-external-id',
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010601'));
} else {
fail();
}
}
});
it('ユーザーのRoleがTypistで、別ユーザーによってチェックアウト中のタスクInProgressがある時、タスクをチェックアウトできる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: typistUserId1 } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id1',
role: 'typist',
});
const { id: typistUserId2 } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id2',
role: 'typist',
});
const { id: authorUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'MY_AUTHOR_ID',
});
await createTask(
source,
accountId,
authorUserId,
'MY_AUTHOR_ID',
'',
'01',
'00000001',
TASK_STATUS.IN_PROGRESS,
typistUserId1,
);
const { taskId } = await createTask(
source,
accountId,
authorUserId,
'MY_AUTHOR_ID',
'',
'01',
'00000002',
TASK_STATUS.UPLOADED,
);
await createCheckoutPermissions(source, taskId, typistUserId2);
const service = module.get<TasksService>(TasksService);
await service.checkout(
makeContext('trackingId'),
2,
['typist'],
'typist-user-external-id2',
);
const resultTask = await getTask(source, taskId);
const permisions = await getCheckoutPermissions(source, taskId);
expect(resultTask?.status).toEqual(TASK_STATUS.IN_PROGRESS);
expect(resultTask?.typist_user_id).toEqual(typistUserId2);
expect(permisions.length).toBe(1);
expect(permisions[0].task_id).toBe(taskId);
expect(permisions[0].user_id).toBe(typistUserId2);
});
it('ユーザーのRoleがAuthorで、アップロードした音声ファイルに紐づいたタスクをチェックアウトできる(Uploaded)', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: authorUserId } = await makeTestUser(source, {
@ -1740,12 +1848,7 @@ describe('checkout', () => {
it('ユーザーのRoleがAuthorで、アップロードした音声ファイルに紐づいたタスクをチェックアウトできる(Finished)', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: authorUserId } = await makeTestUser(source, {
@ -1766,6 +1869,7 @@ describe('checkout', () => {
);
const service = module.get<TasksService>(TasksService);
expect(
await service.checkout(
makeContext('trackingId'),
@ -1778,12 +1882,7 @@ describe('checkout', () => {
it('ユーザーのRoleがAuthorで、アップロードした音声ファイルに紐づいたタスクが存在しない場合、タスクをチェックアウトできない', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
await makeTestUser(source, {
@ -1794,26 +1893,27 @@ describe('checkout', () => {
});
const service = module.get<TasksService>(TasksService);
await expect(
service.checkout(
try {
await service.checkout(
makeContext('trackingId'),
1,
['author'],
'author-user-external-id',
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.NOT_FOUND);
expect(e.getResponse()).toEqual(makeErrorResponse('E010601'));
} else {
fail();
}
}
});
it('ユーザーのRoleがAuthorで、音声ファイルに紐づいたタスクでユーザーと一致するAuthorIDでない場合、タスクをチェックアウトできない', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: authorUserId } = await makeTestUser(source, {
@ -1834,26 +1934,27 @@ describe('checkout', () => {
);
const service = module.get<TasksService>(TasksService);
await expect(
service.checkout(
try {
await service.checkout(
makeContext('trackingId'),
1,
['author'],
'author-user-external-id',
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010602'), HttpStatus.BAD_REQUEST),
);
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010602'));
} else {
fail();
}
}
});
it('ユーザーのRoleに[Typist,author]が設定されていない時、タスクをチェックアウトできない', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
await makeTestUser(source, {
@ -1864,16 +1965,22 @@ describe('checkout', () => {
});
const service = module.get<TasksService>(TasksService);
await expect(
service.checkout(
try {
await service.checkout(
makeContext('trackingId'),
1,
['none'],
'none-user-external-id',
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010602'), HttpStatus.BAD_REQUEST),
);
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010602'));
} else {
fail();
}
}
});
});

View File

@ -19,6 +19,7 @@ import { AdB2cUser } from '../../gateways/adb2c/types/types';
import { CheckoutPermission } from '../../repositories/checkout_permissions/entity/checkout_permission.entity';
import {
AccountNotMatchError,
AlreadyHasInProgressTaskError,
CheckoutPermissionNotFoundError,
StatusNotMatchError,
TaskAuthorIdNotMatchError,
@ -295,6 +296,7 @@ export class TasksService {
);
case AccountNotMatchError:
case StatusNotMatchError:
case AlreadyHasInProgressTaskError:
throw new HttpException(
makeErrorResponse('E010601'),
HttpStatus.BAD_REQUEST,

View File

@ -14,3 +14,5 @@ export class StatusNotMatchError extends Error {}
export class TypistUserNotMatchError extends Error {}
// Account不一致エラー
export class AccountNotMatchError extends Error {}
// タスクチェックアウト済みエラー
export class AlreadyHasInProgressTaskError extends Error {}

View File

@ -6,6 +6,7 @@ import {
FindOptionsOrderValue,
In,
IsNull,
Not,
Repository,
} from 'typeorm';
import { Task } from './entity/task.entity';
@ -26,6 +27,7 @@ import { UserGroup } from '../user_groups/entity/user_group.entity';
import { User } from '../users/entity/user.entity';
import {
AccountNotMatchError,
AlreadyHasInProgressTaskError,
CheckoutPermissionNotFoundError,
StatusNotMatchError,
TaskAuthorIdNotMatchError,
@ -163,6 +165,24 @@ export class TasksRepositoryService {
`task not found. audio_file_id:${audio_file_id}`,
);
}
// 指定したタスク以外に実行ユーザー担当のInprogressのタスクが存在する場合はエラー
const tasks = await taskRepo.find({
where: {
audio_file_id: Not(audio_file_id),
status: TASK_STATUS.IN_PROGRESS,
typist_user_id: user_id,
},
});
if (tasks.length > 0) {
throw new AlreadyHasInProgressTaskError(
`checkout task already exists. task:${tasks.map(
(x) => x.audio_file_id,
)}`,
);
}
// アカウントチェック
if (task.account_id !== account_id) {
throw new AccountNotMatchError(