diff --git a/dictation_server/src/features/tasks/tasks.service.spec.ts b/dictation_server/src/features/tasks/tasks.service.spec.ts index 3eaef7d..c74de6b 100644 --- a/dictation_server/src/features/tasks/tasks.service.spec.ts +++ b/dictation_server/src/features/tasks/tasks.service.spec.ts @@ -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); - 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); - 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); + + 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); + + 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); + 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); - 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); - 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); - 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(); + } + } }); }); diff --git a/dictation_server/src/features/tasks/tasks.service.ts b/dictation_server/src/features/tasks/tasks.service.ts index ccd1f9d..408f7ef 100644 --- a/dictation_server/src/features/tasks/tasks.service.ts +++ b/dictation_server/src/features/tasks/tasks.service.ts @@ -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, diff --git a/dictation_server/src/repositories/tasks/errors/types.ts b/dictation_server/src/repositories/tasks/errors/types.ts index 3d2b2d4..b82c3ed 100644 --- a/dictation_server/src/repositories/tasks/errors/types.ts +++ b/dictation_server/src/repositories/tasks/errors/types.ts @@ -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 {} diff --git a/dictation_server/src/repositories/tasks/tasks.repository.service.ts b/dictation_server/src/repositories/tasks/tasks.repository.service.ts index 0c8b4cc..784dcec 100644 --- a/dictation_server/src/repositories/tasks/tasks.repository.service.ts +++ b/dictation_server/src/repositories/tasks/tasks.repository.service.ts @@ -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(