diff --git a/dictation_client/src/common/parser.test.ts b/dictation_client/src/common/parser.test.ts index d4aff39..a318a76 100644 --- a/dictation_client/src/common/parser.test.ts +++ b/dictation_client/src/common/parser.test.ts @@ -131,4 +131,23 @@ describe("parse", () => { expect(actualValue.role).toEqual(expectValue.role); } }); + + it("author_id,encryption_passwordが数値のみの場合でも、文字列として変換できる", async () => { + const text = fs.readFileSync("src/common/test/test_008.csv", "utf-8"); + const actualData = await parseCSV(text); + const expectData: CSVType[] = [ + { + name: "hoge", + email: "sample@example.com", + role: 1, + author_id: "1111", + auto_renew: 1, + notification: 1, + encryption: 1, + encryption_password: "222222", + prompt: 0, + }, + ]; + expect(actualData).toEqual(expectData); + }); }); diff --git a/dictation_client/src/common/parser.ts b/dictation_client/src/common/parser.ts index 15fe85b..30e0b43 100644 --- a/dictation_client/src/common/parser.ts +++ b/dictation_client/src/common/parser.ts @@ -42,7 +42,24 @@ export const parseCSV = async (csvString: string): Promise => download: false, worker: false, // XXX: workerを使うとエラーが発生するためfalseに設定 header: true, - dynamicTyping: true, + dynamicTyping: { + // author_id, encryption_passwordは数値のみの場合、numberに変換されたくないためdynamicTypingをtrueにしない + role: true, + auto_renew: true, + notification: true, + encryption: true, + prompt: true, + }, + // dynamicTypingがfalseの場合、空文字をnullに変換できないためtransformを使用する + transform: (value, field) => { + if (field === "author_id" || field === "encryption_password") { + // 空文字の場合はnullに変換する + if (value === "") { + return null; + } + } + return value; + }, complete: (results: ParseResult) => { // ヘッダーがCSVTypeFieldsと一致しない場合はエラーを返す if (!equals(results.meta.fields ?? [], CSVTypeFields)) { diff --git a/dictation_client/src/common/test/test_008.csv b/dictation_client/src/common/test/test_008.csv new file mode 100644 index 0000000..ccc9f52 --- /dev/null +++ b/dictation_client/src/common/test/test_008.csv @@ -0,0 +1,2 @@ +name,email,role,author_id,auto_renew,notification,encryption,encryption_password,prompt +hoge,sample@example.com,1,1111,1,1,1,222222,0 \ No newline at end of file diff --git a/dictation_client/src/pages/DictationPage/index.tsx b/dictation_client/src/pages/DictationPage/index.tsx index f73dd27..1c8a47b 100644 --- a/dictation_client/src/pages/DictationPage/index.tsx +++ b/dictation_client/src/pages/DictationPage/index.tsx @@ -1266,9 +1266,11 @@ const DictationPage: React.FC = (): JSX.Element => {
  • {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */} { source = null; }); - it('管理者として、アカウント内のタスクを削除できる', async () => { + it('管理者として、アカウント内のタスクを削除できる(タスクのStatusがUploaded)', async () => { if (!source) fail(); const module = await makeTestingModule(source); if (!module) fail(); @@ -4420,7 +4420,7 @@ describe('deleteTask', () => { ); } }); - it('Authorとして、自身が追加したタスクを削除できる', async () => { + it('Authorとして、自身が追加したタスクを削除できる(タスクのStatusがFinished)', async () => { if (!source) fail(); const module = await makeTestingModule(source); if (!module) fail(); @@ -4448,7 +4448,7 @@ describe('deleteTask', () => { '', '01', '00000001', - TASK_STATUS.UPLOADED, + TASK_STATUS.FINISHED, ); await createCheckoutPermissions(source, taskId, typistUserId); @@ -4460,7 +4460,92 @@ describe('deleteTask', () => { const optionItems = await getAudioOptionItems(source, taskId); expect(task?.id).toBe(taskId); - expect(task?.status).toBe(TASK_STATUS.UPLOADED); + expect(task?.status).toBe(TASK_STATUS.FINISHED); + expect(task?.audio_file_id).toBe(audioFileId); + + expect(audioFile?.id).toBe(audioFileId); + expect(audioFile?.file_name).toBe('x.zip'); + expect(audioFile?.author_id).toBe(authorId); + + expect(checkoutPermissions.length).toBe(1); + expect(checkoutPermissions[0].user_id).toBe(typistUserId); + + expect(optionItems.length).toBe(10); + } + + const service = module.get(TasksService); + const blobStorageService = + module.get(BlobstorageService); + const context = makeContext(authorExternalId, 'requestId'); + + overrideBlobstorageService(service, { + deleteFile: jest.fn(), + }); + + await service.deleteTask(context, authorExternalId, audioFileId); + + // 実行結果が正しいか確認 + { + const task = await getTask(source, taskId); + const audioFile = await getAudioFile(source, audioFileId); + const checkoutPermissions = await getCheckoutPermissions(source, taskId); + const optionItems = await getAudioOptionItems(source, taskId); + + expect(task).toBe(null); + expect(audioFile).toBe(null); + expect(checkoutPermissions.length).toBe(0); + expect(optionItems.length).toBe(0); + + // Blob削除メソッドが呼ばれているか確認 + expect(blobStorageService.deleteFile).toBeCalledWith( + context, + account.id, + account.country, + 'y.zip', + ); + } + }); + it('Authorとして、自身が追加したタスクを削除できる(タスクのStatusがBackup)', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + // 第五階層のアカウント作成 + const { account } = await makeTestAccount(source, { tier: 5 }); + const authorId = 'AUTHOR_ID'; + const { id: authorUserId, external_id: authorExternalId } = + await makeTestUser(source, { + account_id: account.id, + author_id: 'AUTHOR_ID', + external_id: 'author-user-external-id', + role: USER_ROLES.AUTHOR, + }); + const { id: typistUserId } = await makeTestUser(source, { + account_id: account.id, + external_id: 'typist-user-external-id', + role: USER_ROLES.TYPIST, + }); + + const { taskId, audioFileId } = await createTask( + source, + account.id, + authorUserId, + authorId, + '', + '01', + '00000001', + TASK_STATUS.BACKUP, + ); + await createCheckoutPermissions(source, taskId, typistUserId); + + // 作成したデータを確認 + { + const task = await getTask(source, taskId); + const audioFile = await getAudioFile(source, audioFileId); + const checkoutPermissions = await getCheckoutPermissions(source, taskId); + const optionItems = await getAudioOptionItems(source, taskId); + + expect(task?.id).toBe(taskId); + expect(task?.status).toBe(TASK_STATUS.BACKUP); expect(task?.audio_file_id).toBe(audioFileId); expect(audioFile?.id).toBe(audioFileId); @@ -4578,6 +4663,79 @@ describe('deleteTask', () => { } } }); + it('ステータスがPendingのタスクを削除しようとした場合、エラーとなること', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + // 第五階層のアカウント作成 + const { account } = await makeTestAccount(source, { tier: 5 }); + const authorId = 'AUTHOR_ID'; + const { id: authorUserId, external_id: authorExternalId } = + await makeTestUser(source, { + account_id: account.id, + author_id: 'AUTHOR_ID', + external_id: 'author-user-external-id', + role: USER_ROLES.AUTHOR, + }); + const { id: typistUserId } = await makeTestUser(source, { + account_id: account.id, + external_id: 'typist-user-external-id', + role: USER_ROLES.TYPIST, + }); + + const { taskId, audioFileId } = await createTask( + source, + account.id, + authorUserId, + authorId, + '', + '01', + '00000001', + TASK_STATUS.PENDING, + ); + await createCheckoutPermissions(source, taskId, typistUserId); + + // 作成したデータを確認 + { + const task = await getTask(source, taskId); + const audioFile = await getAudioFile(source, audioFileId); + const checkoutPermissions = await getCheckoutPermissions(source, taskId); + const optionItems = await getAudioOptionItems(source, taskId); + + expect(task?.id).toBe(taskId); + expect(task?.status).toBe(TASK_STATUS.PENDING); + expect(task?.audio_file_id).toBe(audioFileId); + + expect(audioFile?.id).toBe(audioFileId); + expect(audioFile?.file_name).toBe('x.zip'); + expect(audioFile?.author_id).toBe(authorId); + + expect(checkoutPermissions.length).toBe(1); + expect(checkoutPermissions[0].user_id).toBe(typistUserId); + + expect(optionItems.length).toBe(10); + } + + const service = module.get(TasksService); + const context = makeContext(authorExternalId, 'requestId'); + + overrideBlobstorageService(service, { + // eslint-disable-next-line @typescript-eslint/no-empty-function + deleteFile: async () => {}, + }); + + try { + await service.deleteTask(context, authorExternalId, audioFileId); + fail(); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST); + expect(e.getResponse()).toEqual(makeErrorResponse('E010601')); + } else { + fail(); + } + } + }); it('Authorが自身が作成したタスク以外を削除しようとした場合、エラーとなること', async () => { if (!source) fail(); const module = await makeTestingModule(source); diff --git a/dictation_server/src/repositories/tasks/tasks.repository.service.ts b/dictation_server/src/repositories/tasks/tasks.repository.service.ts index aef734f..e43c2cf 100644 --- a/dictation_server/src/repositories/tasks/tasks.repository.service.ts +++ b/dictation_server/src/repositories/tasks/tasks.repository.service.ts @@ -1436,13 +1436,15 @@ export class TasksRepositoryService { } } - // タスクのステータスがInProgressの場合はエラー - if (task.status === TASK_STATUS.IN_PROGRESS) { + // タスクのステータスがInProgress・Pendingの時はエラー + if ( + task.status === TASK_STATUS.IN_PROGRESS || + task.status === TASK_STATUS.PENDING + ) { throw new StatusNotMatchError( - `task status is InProgress. audio_file_id:${audioFileId}`, + `task status is InProgress or Pending. status:${task.status} , audio_file_id:${audioFileId}`, ); } - // タスクに紐づくオプションアイテムを削除 const optionItemRepo = entityManager.getRepository(AudioOptionItem); await deleteEntity(