From b7925f311b32c1258ffe5864bf05724b23455882 Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Mon, 3 Jun 2024 05:07:08 +0000 Subject: [PATCH 1/3] =?UTF-8?q?Merged=20PR=20907:=20=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=88=E3=82=BF=E3=82=B9=E3=82=AF=E5=89=8A?= =?UTF-8?q?=E9=99=A4=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=AE=E6=B4=BB=E6=80=A7?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E3=82=92=E4=BF=AE=E6=AD=A3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task4200: 画面修正(タスク削除ボタンの活性条件を修正)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4200) - タスク削除ボタンの活性条件を修正 - Pendingの時は削除できないようにする ## レビューポイント - 特になし ## UIの変更 - https://ndstokyo.sharepoint.com/:i:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task4200/Pending%E5%89%8A%E9%99%A4%E4%B8%8D%E5%8F%AF.png?csf=1&web=1&e=ATzEZ8 ## 動作確認状況 - ローカルで確認 - 行った修正がデグレを発生させていないことを確認できるか - Inprogressのステータスで削除できないこと - Uploaded・Finished・backupで削除できる ## 補足 - 相談、参考資料などがあれば --- dictation_client/src/pages/DictationPage/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 */} Date: Mon, 3 Jun 2024 05:39:53 +0000 Subject: [PATCH 2/3] =?UTF-8?q?Merged=20PR=20908:=20API=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=EF=BC=88=E3=82=BF=E3=82=B9=E3=82=AF=E5=89=8A=E9=99=A4=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E3=81=AE=E3=82=B9=E3=83=86=E3=83=BC=E3=82=BF=E3=82=B9?= =?UTF-8?q?=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF=E3=82=92=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task4201: API修正(タスク削除条件のステータスチェックを修正)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4201) - タスク削除不可条件を修正 - Pendingの時も削除できないようにする ## レビューポイント - 修正の認識があっているか ## 動作確認状況 - ローカルで確認 - 行った修正がデグレを発生させていないことを確認できるか - タスクの各ステータスで削除可否を確かめられるように修正 ## 補足 - 相談、参考資料などがあれば --- .../src/features/tasks/tasks.service.spec.ts | 166 +++++++++++++++++- .../tasks/tasks.repository.service.ts | 10 +- 2 files changed, 168 insertions(+), 8 deletions(-) diff --git a/dictation_server/src/features/tasks/tasks.service.spec.ts b/dictation_server/src/features/tasks/tasks.service.spec.ts index 04af977..cb59c38 100644 --- a/dictation_server/src/features/tasks/tasks.service.spec.ts +++ b/dictation_server/src/features/tasks/tasks.service.spec.ts @@ -4336,7 +4336,7 @@ describe('deleteTask', () => { 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( From 909c2a6d55a3dfc1d8addfba0863d1ca075d6c21 Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Mon, 3 Jun 2024 06:36:05 +0000 Subject: [PATCH 3/3] =?UTF-8?q?Merged=20PR=20909:=20=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=88CSV=E3=83=95=E3=82=A1=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E3=81=AE=E5=A4=89=E6=8F=9B=E5=87=A6=E7=90=86=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task4202: 画面修正(CSVファイルの変換処理)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4202) - CSVデータの変換関数を修正 - author_id,encryption_passwordが数値のみの場合でも、文字列として扱うようにする ## レビューポイント - ほかに確認したほうが良いテストケースはあるか ## 動作確認状況 - ローカルで確認 - 行った修正がデグレを発生させていないことを確認できるか - 具体的にどのような確認をしたか - 既存のテストはすべて通ることを確認 ## 補足 - 相談、参考資料などがあれば --- dictation_client/src/common/parser.test.ts | 19 +++++++++++++++++++ dictation_client/src/common/parser.ts | 19 ++++++++++++++++++- dictation_client/src/common/test/test_008.csv | 2 ++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 dictation_client/src/common/test/test_008.csv 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