import * as dotenv from "dotenv"; import { InvocationContext } from "@azure/functions"; import { DataSource } from "typeorm"; import { truncateAllTable } from "./common/init"; import { deleteAudioFilesProcessing, deleteRecords, getProcessTargets, } from "../functions/deleteAudioFiles"; import { getAudioFiles, getAudioOptionItems, getTasks, makeManyTestTasks, makeTestAccount, makeTestTask, } from "./common/utility"; import { MANUAL_RECOVERY_REQUIRED, TASK_STATUS } from "../constants"; import { TestLogger } from "./common/logger"; import { AudioFile } from "../entity/audio_file.entity"; import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"; import { AudioBlobStorageService } from "../blobstorage/audioBlobStorage.service"; import { User, UserArchive } from "../entity/user.entity"; import { Account, AccountArchive } from "../entity/account.entity"; import { Task } from "../entity/task.entity"; import { License, LicenseAllocationHistory, LicenseAllocationHistoryArchive, LicenseArchive, } from "../entity/license.entity"; import { AudioOptionItem } from "../entity/audio_option_item.entity"; import { TestInvocationContext } from "./common/context"; describe("getProcessTargets | 削除対象を特定するQueryが正常に動作するか確認する", () => { let source: DataSource | null = null; beforeAll(async () => { dotenv.config({ path: ".env" }); dotenv.config({ path: ".env.test", override: true }); if (source == null) { source = await (async () => { const s = new DataSource({ type: "mysql", host: "test_mysql_db", port: 3306, username: "user", password: "password", database: "odms", entities: [__dirname + "/../../**/*.entity{.ts,.js}"], synchronize: false, // trueにすると自動的にmigrationが行われるため注意 logger: new TestLogger("none"), logging: true, }); return await s.initialize(); })(); } }); beforeEach(async () => { if (source) { await truncateAllTable(source); } }); afterAll(async () => { await source?.destroy(); source = null; }); it("ファイル削除対象のタスクが存在しない場合、空の配列が取得できる", async () => { if (!source) fail(); // 2024/02/29 00:00:00を"今"とする const now = new Date("2024-02-29T00:00:00Z"); const { account, admin } = await makeTestAccount(source, { file_retention_days: 2, auto_file_delete: true, }); { // ファイル削除対象のタスクが存在しない場合、空の配列が取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([]); } // ちょうど2日前(削除対象外)のタスクを作成 await makeTestTask(source, account.id, admin.id, "case01", { status: TASK_STATUS.FINISHED, job_number: "job01", finished_at: new Date("2024-02-27T00:00:00Z"), }); // ちょうど1日前(削除対象外)のタスクを作成 await makeTestTask(source, account.id, admin.id, "case02", { status: TASK_STATUS.FINISHED, job_number: "job02", finished_at: new Date("2024-02-28T00:00:00Z"), }); { // ファイル削除対象のタスクが存在しない場合、空の配列が取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([]); } }); it("ファイル削除対象のタスク情報のみを取得できる(対象となる期限切れのタスク情報のみが取れる)", async () => { if (!source) fail(); // 2024/02/29 00:00:00を"今"とする const now = new Date("2024-02-29T00:00:00Z"); const { account, admin } = await makeTestAccount(source, { file_retention_days: 2, auto_file_delete: true, }); // ちょうど2日前(削除対象外)のタスクを作成 await makeTestTask(source, account.id, admin.id, "case01", { status: TASK_STATUS.FINISHED, job_number: "job01", finished_at: new Date("2024-02-27T00:00:00Z"), }); // ちょうど1日前(削除対象外)のタスクを作成 await makeTestTask(source, account.id, admin.id, "case02", { status: TASK_STATUS.FINISHED, job_number: "job02", finished_at: new Date("2024-02-28T00:00:00Z"), }); // 2日と1秒前(削除対象)のタスクを作成 const { task, file } = await makeTestTask( source, account.id, admin.id, "case03", { status: TASK_STATUS.FINISHED, job_number: "job03", finished_at: new Date("2024-02-26T23:59:59Z"), } ); { // ファイル削除対象のタスク情報1件のみを取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([ { id: task.id, audio_file_id: file.id, raw_file_name: file.raw_file_name, account_id: account.id, country: account.country, }, ]); } // ちょうど3日前(削除対象)のタスクを作成 const { task: task2, file: file2 } = await makeTestTask( source, account.id, admin.id, "case04", { status: TASK_STATUS.FINISHED, job_number: "job04", finished_at: new Date("2024-02-26T00:00:00Z"), } ); { // ファイル削除対象のタスク情報2件のみを取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([ { id: task.id, audio_file_id: file.id, raw_file_name: file.raw_file_name, account_id: account.id, country: account.country, }, { id: task2.id, audio_file_id: file2.id, raw_file_name: file2.raw_file_name, account_id: account.id, country: account.country, }, ]); } // ちょうど1日前(削除対象外)のタスクを作成 await makeTestTask(source, account.id, admin.id, "case05", { status: TASK_STATUS.FINISHED, job_number: "job05", finished_at: new Date("2024-02-28T00:00:00Z"), }); { // ファイル削除対象のタスク情報2件のみを取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([ { id: task.id, audio_file_id: file.id, raw_file_name: file.raw_file_name, account_id: account.id, country: account.country, }, { id: task2.id, audio_file_id: file2.id, raw_file_name: file2.raw_file_name, account_id: account.id, country: account.country, }, ]); } // 100日と5時間前(削除対象)のタスクを作成 const { task: task3, file: file3 } = await makeTestTask( source, account.id, admin.id, "case06", { status: TASK_STATUS.FINISHED, job_number: "job06", finished_at: new Date("2023-11-20T19:00:00Z"), } ); { // ファイル削除対象のタスク情報3件のみを取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([ { id: task.id, audio_file_id: file.id, raw_file_name: file.raw_file_name, account_id: account.id, country: account.country, }, { id: task2.id, audio_file_id: file2.id, raw_file_name: file2.raw_file_name, account_id: account.id, country: account.country, }, { id: task3.id, audio_file_id: file3.id, raw_file_name: file3.raw_file_name, account_id: account.id, country: account.country, }, ]); } // 1日後(削除対象外。本来はミリ秒単位の未来方向の時刻違いを想定)のタスクを作成 await makeTestTask(source, account.id, admin.id, "case07", { status: TASK_STATUS.FINISHED, job_number: "job07", finished_at: new Date("2024-03-01T00:00:00Z"), }); { // ファイル削除対象のタスク情報3件のみを取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([ { id: task.id, audio_file_id: file.id, raw_file_name: file.raw_file_name, account_id: account.id, country: account.country, }, { id: task2.id, audio_file_id: file2.id, raw_file_name: file2.raw_file_name, account_id: account.id, country: account.country, }, { id: task3.id, audio_file_id: file3.id, raw_file_name: file3.raw_file_name, account_id: account.id, country: account.country, }, ]); } }); it("ファイル削除対象のタスク情報のみを取得できる(auto_file_delete=falseのアカウントの情報のタスク情報は取れない)", async () => { if (!source) fail(); // 2024/02/29 00:00:00を"今"とする const now = new Date("2024-02-29T00:00:00Z"); // auto_file_deleteがtrueののアカウントを作成 const { account: account01, admin: admin01 } = await makeTestAccount( source, { file_retention_days: 2, auto_file_delete: true, } ); // auto_file_deleteがfalseののアカウントを作成 const { account: account02, admin: admin02 } = await makeTestAccount( source, { file_retention_days: 2, auto_file_delete: false, } ); // ちょうど2日前(削除対象外)のタスクを作成 await makeTestTask(source, account01.id, admin01.id, "case01", { status: TASK_STATUS.FINISHED, job_number: "job01", finished_at: new Date("2024-02-27T00:00:00Z"), }); await makeTestTask(source, account02.id, admin02.id, "case01", { status: TASK_STATUS.FINISHED, job_number: "job01", finished_at: new Date("2024-02-27T00:00:00Z"), }); // ちょうど1日前(削除対象外)のタスクを作成 await makeTestTask(source, account01.id, admin01.id, "case02", { status: TASK_STATUS.FINISHED, job_number: "job02", finished_at: new Date("2024-02-28T00:00:00Z"), }); await makeTestTask(source, account02.id, admin02.id, "case02", { status: TASK_STATUS.FINISHED, job_number: "job02", finished_at: new Date("2024-02-28T00:00:00Z"), }); // 2日と1秒前(削除対象)のタスクを作成 const { task, file } = await makeTestTask( source, account01.id, admin01.id, "case03", { status: TASK_STATUS.FINISHED, job_number: "job03", finished_at: new Date("2024-02-26T23:59:59Z"), } ); await makeTestTask(source, account02.id, admin02.id, "case03", { status: TASK_STATUS.FINISHED, job_number: "job03", finished_at: new Date("2024-02-26T23:59:59Z"), }); { // ファイル削除対象のタスク情報1件のみを取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([ { id: task.id, audio_file_id: file.id, raw_file_name: file.raw_file_name, account_id: account01.id, country: account01.country, }, ]); } { // ファイル削除対象のタスク情報1件のみを取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([ { id: task.id, audio_file_id: file.id, raw_file_name: file.raw_file_name, account_id: account01.id, country: account01.country, }, ]); } // ちょうど3日前(削除対象)のタスクを作成 const { task: task2, file: file2 } = await makeTestTask( source, account01.id, admin01.id, "case04", { status: TASK_STATUS.FINISHED, job_number: "job04", finished_at: new Date("2024-02-26T00:00:00Z"), } ); await makeTestTask(source, account02.id, admin02.id, "case04", { status: TASK_STATUS.FINISHED, job_number: "job04", finished_at: new Date("2024-02-26T00:00:00Z"), }); { // ファイル削除対象のタスク情報2件のみを取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([ { id: task.id, audio_file_id: file.id, raw_file_name: file.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task2.id, audio_file_id: file2.id, raw_file_name: file2.raw_file_name, account_id: account01.id, country: account01.country, }, ]); } // ちょうど1日前(削除対象外)のタスクを作成 await makeTestTask(source, account01.id, admin01.id, "case05", { status: TASK_STATUS.FINISHED, job_number: "job05", finished_at: new Date("2024-02-28T00:00:00Z"), }); await makeTestTask(source, account02.id, admin02.id, "case05", { status: TASK_STATUS.FINISHED, job_number: "job05", finished_at: new Date("2024-02-28T00:00:00Z"), }); { // ファイル削除対象のタスク情報2件のみを取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([ { id: task.id, audio_file_id: file.id, raw_file_name: file.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task2.id, audio_file_id: file2.id, raw_file_name: file2.raw_file_name, account_id: account01.id, country: account01.country, }, ]); } // 100日と5時間前(削除対象)のタスクを作成 const { task: task3, file: file3 } = await makeTestTask( source, account01.id, admin01.id, "case06", { status: TASK_STATUS.FINISHED, job_number: "job06", finished_at: new Date("2023-11-20T19:00:00Z"), } ); await makeTestTask(source, account02.id, admin02.id, "case06", { status: TASK_STATUS.FINISHED, job_number: "job06", finished_at: new Date("2023-11-20T19:00:00Z"), }); { // ファイル削除対象のタスク情報3件のみを取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([ { id: task.id, audio_file_id: file.id, raw_file_name: file.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task2.id, audio_file_id: file2.id, raw_file_name: file2.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task3.id, audio_file_id: file3.id, raw_file_name: file3.raw_file_name, account_id: account01.id, country: account01.country, }, ]); } // 1日後(削除対象外。本来はミリ秒単位の未来方向の時刻違いを想定)のタスクを作成 await makeTestTask(source, account01.id, admin01.id, "case07", { status: TASK_STATUS.FINISHED, job_number: "job07", finished_at: new Date("2024-03-01T00:00:00Z"), }); await makeTestTask(source, account02.id, admin02.id, "case07", { status: TASK_STATUS.FINISHED, job_number: "job07", finished_at: new Date("2024-03-01T00:00:00Z"), }); { // ファイル削除対象のタスク情報3件のみを取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([ { id: task.id, audio_file_id: file.id, raw_file_name: file.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task2.id, audio_file_id: file2.id, raw_file_name: file2.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task3.id, audio_file_id: file3.id, raw_file_name: file3.raw_file_name, account_id: account01.id, country: account01.country, }, ]); } }); it("ファイル削除対象のタスク情報のみを取得できる(auto_file_delete=trueのアカウントの情報のタスク情報は全て取れる)", async () => { if (!source) fail(); // 2024/02/29 00:00:00を"今"とする const now = new Date("2024-02-29T00:00:00Z"); // auto_file_deleteがtrueののアカウントを作成 const { account: account01, admin: admin01 } = await makeTestAccount( source, { file_retention_days: 2, auto_file_delete: true, } ); const { account: account02, admin: admin02 } = await makeTestAccount( source, { file_retention_days: 2, auto_file_delete: true, } ); // auto_file_deleteがfalseののアカウントを作成 const { account: account03, admin: admin03 } = await makeTestAccount( source, { file_retention_days: 2, auto_file_delete: false, } ); // ちょうど2日前(削除対象外)のタスクを作成 await makeTestTask(source, account01.id, admin01.id, "case01", { status: TASK_STATUS.FINISHED, job_number: "job01", finished_at: new Date("2024-02-27T00:00:00Z"), }); await makeTestTask(source, account02.id, admin02.id, "case01", { status: TASK_STATUS.FINISHED, job_number: "job01", finished_at: new Date("2024-02-27T00:00:00Z"), }); await makeTestTask(source, account03.id, admin03.id, "case01", { status: TASK_STATUS.FINISHED, job_number: "job01", finished_at: new Date("2024-02-27T00:00:00Z"), }); // ちょうど1日前(削除対象外)のタスクを作成 await makeTestTask(source, account01.id, admin01.id, "case02", { status: TASK_STATUS.FINISHED, job_number: "job02", finished_at: new Date("2024-02-28T00:00:00Z"), }); await makeTestTask(source, account02.id, admin02.id, "case02", { status: TASK_STATUS.FINISHED, job_number: "job02", finished_at: new Date("2024-02-28T00:00:00Z"), }); await makeTestTask(source, account03.id, admin03.id, "case02", { status: TASK_STATUS.FINISHED, job_number: "job02", finished_at: new Date("2024-02-28T00:00:00Z"), }); // 2日と1秒前(削除対象)のタスクを作成 const { task: task01, file: file01 } = await makeTestTask( source, account01.id, admin01.id, "case03", { status: TASK_STATUS.FINISHED, job_number: "job03", finished_at: new Date("2024-02-26T23:59:59Z"), } ); const { task: task02, file: file02 } = await makeTestTask( source, account02.id, admin02.id, "case03", { status: TASK_STATUS.FINISHED, job_number: "job03", finished_at: new Date("2024-02-26T23:59:59Z"), } ); await makeTestTask(source, account03.id, admin03.id, "case03", { status: TASK_STATUS.FINISHED, job_number: "job03", finished_at: new Date("2024-02-26T23:59:59Z"), }); { // ファイル削除対象のタスク情報2件のみを取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([ { id: task01.id, audio_file_id: file01.id, raw_file_name: file01.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task02.id, audio_file_id: file02.id, raw_file_name: file02.raw_file_name, account_id: account02.id, country: account02.country, }, ]); } // ちょうど3日前(削除対象)のタスクを作成 const { task: task03, file: file03 } = await makeTestTask( source, account01.id, admin01.id, "case04", { status: TASK_STATUS.FINISHED, job_number: "job04", finished_at: new Date("2024-02-26T00:00:00Z"), } ); const { task: task04, file: file04 } = await makeTestTask( source, account02.id, admin02.id, "case04", { status: TASK_STATUS.FINISHED, job_number: "job04", finished_at: new Date("2024-02-26T00:00:00Z"), } ); await makeTestTask(source, account03.id, admin03.id, "case04", { status: TASK_STATUS.FINISHED, job_number: "job04", finished_at: new Date("2024-02-26T00:00:00Z"), }); { // ファイル削除対象のタスク情報4件のみを取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([ { id: task01.id, audio_file_id: file01.id, raw_file_name: file01.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task02.id, audio_file_id: file02.id, raw_file_name: file02.raw_file_name, account_id: account02.id, country: account02.country, }, { id: task03.id, audio_file_id: file03.id, raw_file_name: file03.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task04.id, audio_file_id: file04.id, raw_file_name: file04.raw_file_name, account_id: account02.id, country: account02.country, }, ]); } // ちょうど1日前(削除対象外)のタスクを作成 await makeTestTask(source, account01.id, admin01.id, "case05", { status: TASK_STATUS.FINISHED, job_number: "job05", finished_at: new Date("2024-02-28T00:00:00Z"), }); await makeTestTask(source, account02.id, admin02.id, "case05", { status: TASK_STATUS.FINISHED, job_number: "job05", finished_at: new Date("2024-02-28T00:00:00Z"), }); await makeTestTask(source, account03.id, admin03.id, "case05", { status: TASK_STATUS.FINISHED, job_number: "job05", finished_at: new Date("2024-02-28T00:00:00Z"), }); { // ファイル削除対象のタスク情報4件のみを取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([ { id: task01.id, audio_file_id: file01.id, raw_file_name: file01.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task02.id, audio_file_id: file02.id, raw_file_name: file02.raw_file_name, account_id: account02.id, country: account02.country, }, { id: task03.id, audio_file_id: file03.id, raw_file_name: file03.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task04.id, audio_file_id: file04.id, raw_file_name: file04.raw_file_name, account_id: account02.id, country: account02.country, }, ]); } // 100日と5時間前(削除対象)のタスクを作成 const { task: task05, file: file05 } = await makeTestTask( source, account01.id, admin01.id, "case06", { status: TASK_STATUS.FINISHED, job_number: "job06", finished_at: new Date("2023-11-20T19:00:00Z"), } ); const { task: task06, file: file06 } = await makeTestTask( source, account02.id, admin02.id, "case06", { status: TASK_STATUS.FINISHED, job_number: "job06", finished_at: new Date("2023-11-20T19:00:00Z"), } ); await makeTestTask(source, account03.id, admin03.id, "case06", { status: TASK_STATUS.FINISHED, job_number: "job06", finished_at: new Date("2023-11-20T19:00:00Z"), }); { // ファイル削除対象のタスク情報6件のみを取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([ { id: task01.id, audio_file_id: file01.id, raw_file_name: file01.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task02.id, audio_file_id: file02.id, raw_file_name: file02.raw_file_name, account_id: account02.id, country: account02.country, }, { id: task03.id, audio_file_id: file03.id, raw_file_name: file03.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task04.id, audio_file_id: file04.id, raw_file_name: file04.raw_file_name, account_id: account02.id, country: account02.country, }, { id: task05.id, audio_file_id: file05.id, raw_file_name: file05.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task06.id, audio_file_id: file06.id, raw_file_name: file06.raw_file_name, account_id: account02.id, country: account02.country, }, ]); } // 1日後(削除対象外。本来はミリ秒単位の未来方向の時刻違いを想定)のタスクを作成 await makeTestTask(source, account01.id, admin01.id, "case07", { status: TASK_STATUS.FINISHED, job_number: "job07", finished_at: new Date("2024-03-01T00:00:00Z"), }); await makeTestTask(source, account02.id, admin02.id, "case07", { status: TASK_STATUS.FINISHED, job_number: "job07", finished_at: new Date("2024-03-01T00:00:00Z"), }); { // ファイル削除対象のタスク情報6件のみを取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([ { id: task01.id, audio_file_id: file01.id, raw_file_name: file01.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task02.id, audio_file_id: file02.id, raw_file_name: file02.raw_file_name, account_id: account02.id, country: account02.country, }, { id: task03.id, audio_file_id: file03.id, raw_file_name: file03.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task04.id, audio_file_id: file04.id, raw_file_name: file04.raw_file_name, account_id: account02.id, country: account02.country, }, { id: task05.id, audio_file_id: file05.id, raw_file_name: file05.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task06.id, audio_file_id: file06.id, raw_file_name: file06.raw_file_name, account_id: account02.id, country: account02.country, }, ]); } }); }); describe("deleteRecords | 削除対象タスク等を削除できる", () => { let source: DataSource | null = null; beforeAll(async () => { dotenv.config({ path: ".env" }); dotenv.config({ path: ".env.local", override: true }); if (source == null) { source = await (async () => { const s = new DataSource({ type: "mysql", host: "test_mysql_db", port: 3306, username: "user", password: "password", database: "odms", entities: [ User, UserArchive, Account, AccountArchive, Task, AudioFile, AudioOptionItem, License, LicenseArchive, LicenseAllocationHistory, LicenseAllocationHistoryArchive, ], synchronize: false, // trueにすると自動的にmigrationが行われるため注意 logger: new TestLogger("none"), logging: true, }); return await s.initialize(); })(); } }); beforeEach(async () => { if (source) { await truncateAllTable(source); } }); afterAll(async () => { await source?.destroy(); source = null; }); it("競合により存在しないタスクを削除しようとしても例外は発生せず、成功扱いとなる", async () => { if (!source) fail(); await deleteRecords(source, [ { id: 1, audio_file_id: 1, raw_file_name: "test", account_id: 1, country: "US", }, ]); }); it("対象としたタスクやAudioFile等が削除される", async () => { if (!source) fail(); // 2024/02/29 00:00:00を"今"とする const now = new Date("2024-02-29T00:00:00Z"); // auto_file_deleteがtrueののアカウントを作成 const { account: account01, admin: admin01 } = await makeTestAccount( source, { file_retention_days: 2, auto_file_delete: true, } ); const { account: account02, admin: admin02 } = await makeTestAccount( source, { file_retention_days: 2, auto_file_delete: true, } ); // auto_file_deleteがfalseののアカウントを作成 const { account: account03, admin: admin03 } = await makeTestAccount( source, { file_retention_days: 2, auto_file_delete: false, } ); // ちょうど2日前(削除対象外)のタスクを作成 await makeTestTask(source, account01.id, admin01.id, "case01", { status: TASK_STATUS.FINISHED, job_number: "job01", finished_at: new Date("2024-02-27T00:00:00Z"), }); await makeTestTask(source, account02.id, admin02.id, "case01", { status: TASK_STATUS.FINISHED, job_number: "job01", finished_at: new Date("2024-02-27T00:00:00Z"), }); await makeTestTask(source, account03.id, admin03.id, "case01", { status: TASK_STATUS.FINISHED, job_number: "job01", finished_at: new Date("2024-02-27T00:00:00Z"), }); // ちょうど1日前(削除対象外)のタスクを作成 await makeTestTask(source, account01.id, admin01.id, "case02", { status: TASK_STATUS.FINISHED, job_number: "job02", finished_at: new Date("2024-02-28T00:00:00Z"), }); await makeTestTask(source, account02.id, admin02.id, "case02", { status: TASK_STATUS.FINISHED, job_number: "job02", finished_at: new Date("2024-02-28T00:00:00Z"), }); await makeTestTask(source, account03.id, admin03.id, "case02", { status: TASK_STATUS.FINISHED, job_number: "job02", finished_at: new Date("2024-02-28T00:00:00Z"), }); // 2日と1秒前(削除対象)のタスクを作成 const { task: task01, file: file01 } = await makeTestTask( source, account01.id, admin01.id, "case03", { status: TASK_STATUS.FINISHED, job_number: "job03", finished_at: new Date("2024-02-26T23:59:59Z"), } ); const { task: task02, file: file02 } = await makeTestTask( source, account02.id, admin02.id, "case03", { status: TASK_STATUS.FINISHED, job_number: "job03", finished_at: new Date("2024-02-26T23:59:59Z"), } ); await makeTestTask(source, account03.id, admin03.id, "case03", { status: TASK_STATUS.FINISHED, job_number: "job03", finished_at: new Date("2024-02-26T23:59:59Z"), }); // ちょうど3日前(削除対象)のタスクを作成 const { task: task03, file: file03 } = await makeTestTask( source, account01.id, admin01.id, "case04", { status: TASK_STATUS.FINISHED, job_number: "job04", finished_at: new Date("2024-02-26T00:00:00Z"), } ); const { task: task04, file: file04 } = await makeTestTask( source, account02.id, admin02.id, "case04", { status: TASK_STATUS.FINISHED, job_number: "job04", finished_at: new Date("2024-02-26T00:00:00Z"), } ); await makeTestTask(source, account03.id, admin03.id, "case04", { status: TASK_STATUS.FINISHED, job_number: "job04", finished_at: new Date("2024-02-26T00:00:00Z"), }); // ちょうど1日前(削除対象外)のタスクを作成 await makeTestTask(source, account01.id, admin01.id, "case05", { status: TASK_STATUS.FINISHED, job_number: "job05", finished_at: new Date("2024-02-28T00:00:00Z"), }); await makeTestTask(source, account02.id, admin02.id, "case05", { status: TASK_STATUS.FINISHED, job_number: "job05", finished_at: new Date("2024-02-28T00:00:00Z"), }); await makeTestTask(source, account03.id, admin03.id, "case05", { status: TASK_STATUS.FINISHED, job_number: "job05", finished_at: new Date("2024-02-28T00:00:00Z"), }); // 100日と5時間前(削除対象)のタスクを作成 const { task: task05, file: file05 } = await makeTestTask( source, account01.id, admin01.id, "case06", { status: TASK_STATUS.FINISHED, job_number: "job06", finished_at: new Date("2023-11-20T19:00:00Z"), } ); const { task: task06, file: file06 } = await makeTestTask( source, account02.id, admin02.id, "case06", { status: TASK_STATUS.FINISHED, job_number: "job06", finished_at: new Date("2023-11-20T19:00:00Z"), } ); await makeTestTask(source, account03.id, admin03.id, "case06", { status: TASK_STATUS.FINISHED, job_number: "job06", finished_at: new Date("2023-11-20T19:00:00Z"), }); // 1日後(削除対象外。本来はミリ秒単位の未来方向の時刻違いを想定)のタスクを作成 await makeTestTask(source, account01.id, admin01.id, "case07", { status: TASK_STATUS.FINISHED, job_number: "job07", finished_at: new Date("2024-03-01T00:00:00Z"), }); await makeTestTask(source, account02.id, admin02.id, "case07", { status: TASK_STATUS.FINISHED, job_number: "job07", finished_at: new Date("2024-03-01T00:00:00Z"), }); { // ファイル削除対象のタスク情報6件のみを取得できる const result = await getProcessTargets(source, now); result.sort((a, b) => a.id - b.id); expect(result).toEqual([ { id: task01.id, audio_file_id: file01.id, raw_file_name: file01.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task02.id, audio_file_id: file02.id, raw_file_name: file02.raw_file_name, account_id: account02.id, country: account02.country, }, { id: task03.id, audio_file_id: file03.id, raw_file_name: file03.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task04.id, audio_file_id: file04.id, raw_file_name: file04.raw_file_name, account_id: account02.id, country: account02.country, }, { id: task05.id, audio_file_id: file05.id, raw_file_name: file05.raw_file_name, account_id: account01.id, country: account01.country, }, { id: task06.id, audio_file_id: file06.id, raw_file_name: file06.raw_file_name, account_id: account02.id, country: account02.country, }, ]); { // DB全体のレコードを確認 const tasks = await getTasks(source); expect(tasks.length).toEqual(20); const files = await getAudioFiles(source); expect(files.length).toEqual(20); const optionItems = await getAudioOptionItems(source); expect(optionItems.length).toEqual(40); } // 削除対象のタスク情報を削除 await deleteRecords(source, result); // 削除後のタスク情報を取得 const result2 = await getProcessTargets(source, now); // 削除対象のタスク情報が削除されているので取得が0件になる expect(result2).toEqual([]); { // DB全体のレコードを確認 // 削除対象外のタスク情報のみが残っている const tasks = await getTasks(source); expect(tasks.length).toEqual(14); const files = await getAudioFiles(source); expect(files.length).toEqual(14); const optionItems = await getAudioOptionItems(source); expect(optionItems.length).toEqual(28); } } }); it("対象としたタスクやAudioFile等が大量に存在しても削除される", async () => { if (!source) fail(); // 2024/02/29 00:00:00を"今"とする const now = new Date("2024-02-29T00:00:00Z"); // "大量"の数を定義 const count = 10000; // auto_file_deleteがtrueののアカウントを作成 const { account, admin } = await makeTestAccount(source, { file_retention_days: 30, auto_file_delete: true, }); // ファイルを10000件作成 const createdFiles = [...Array(count).keys()].map( (index): QueryDeepPartialEntity => { return { account_id: account.id, owner_user_id: admin.id, url: `https://example.com/${index}`, file_name: `test${index}.wav`, raw_file_name: `test${index}.wav`, author_id: "test_author", work_type_id: "test_work_type", started_at: new Date(), duration: 0, finished_at: new Date(), uploaded_at: new Date(), file_size: 1024, priority: "01", audio_format: "wav", comment: `test_comment_${index}`, deleted_at: new Date(), is_encrypted: false, }; } ); // ファイルを元に、10年前に完了した扱いのタスクを作成 const finished_at = new Date("2014-02-26T23:59:59Z"); await makeManyTestTasks(source, createdFiles, finished_at); { // DB全体のレコードを確認 const tasks = await getTasks(source); expect(tasks.length).toEqual(count); const files = await getAudioFiles(source); expect(files.length).toEqual(count); const optionItems = await getAudioOptionItems(source); expect(optionItems.length).toEqual(2 * count); } const result = await getProcessTargets(source, now); expect(result.length).toEqual(count); await deleteRecords(source, result); { // 削除後のタスク情報を取得 const tasks = await getTasks(source); expect(tasks.length).toEqual(0); const files = await getAudioFiles(source); expect(files.length).toEqual(0); const optionItems = await getAudioOptionItems(source); expect(optionItems.length).toEqual(0); } }, 100000); }); describe("deleteAudioFilesProcessing", () => { let source: DataSource | null = null; beforeAll(async () => { dotenv.config({ path: ".env" }); dotenv.config({ path: ".env.test", override: true }); if (source == null) { source = await (async () => { const s = new DataSource({ type: "mysql", host: "test_mysql_db", port: 3306, username: "user", password: "password", database: "odms", entities: [__dirname + "/../../**/*.entity{.ts,.js}"], synchronize: false, // trueにすると自動的にmigrationが行われるため注意 logger: new TestLogger("none"), logging: true, }); return await s.initialize(); })(); } }); beforeEach(async () => { if (source) { await truncateAllTable(source); } }); afterAll(async () => { await source?.destroy(); source = null; }); it("BlobとDBの削除が正常に行われる", async () => { if (!source) fail(); // 2024/02/29 00:00:00を"今"とする const now = new Date("2024-02-29T00:00:00Z"); const { account: account01, admin: admin01 } = await makeTestAccount( source, { file_retention_days: 2, auto_file_delete: true, } ); const { account: account02, admin: admin02 } = await makeTestAccount( source, { file_retention_days: 2, auto_file_delete: true, country: "JP", } ); // ちょうど2日前(削除対象外)のタスクを作成 await makeTestTask(source, account01.id, admin01.id, "case01", { status: TASK_STATUS.FINISHED, job_number: "job01", finished_at: new Date("2024-02-27T00:00:00Z"), }); // ちょうど1日前(削除対象外)のタスクを作成 await makeTestTask(source, account01.id, admin01.id, "case02", { status: TASK_STATUS.FINISHED, job_number: "job02", finished_at: new Date("2024-02-28T00:00:00Z"), }); // 2日と1秒前(削除対象)のタスクを作成 const { file: file1 } = await makeTestTask( source, account01.id, admin01.id, "case03", { status: TASK_STATUS.FINISHED, job_number: "job03", finished_at: new Date("2024-02-26T23:59:59Z"), } ); // 2日と1秒前(削除対象)のタスクを作成 const { file: file2 } = await makeTestTask( source, account01.id, admin01.id, "case04", { status: TASK_STATUS.FINISHED, job_number: "job04", finished_at: new Date("2024-02-26T23:59:59Z"), } ); // 2日と1秒前(削除対象)のタスクを作成 const { file: file3 } = await makeTestTask( source, account02.id, admin02.id, "case05", { status: TASK_STATUS.FINISHED, job_number: "job05", finished_at: new Date("2024-02-26T23:59:59Z"), } ); const args: { accountId: number; fileName: string; country: string }[] = []; const blobstorage = new AudioBlobStorageService(); Object.defineProperty(blobstorage, blobstorage.deleteFile.name, { value: async ( context: InvocationContext, accountId: number, country: string, fileName: string ): Promise => { args.push({ accountId, country, fileName }); }, writable: true, }); { // DB全体のレコードを確認 const tasks = await getTasks(source); expect(tasks.length).toEqual(5); const files = await getAudioFiles(source); expect(files.length).toEqual(5); const optionItems = await getAudioOptionItems(source); expect(optionItems.length).toEqual(10); } const context = new InvocationContext(); await deleteAudioFilesProcessing(context, source, blobstorage, now); // 想定通りの呼び出しが行われているか { const { accountId, country, fileName } = args[0]; expect(fileName).toEqual(file1.raw_file_name); expect(accountId).toEqual(account01.id); expect(country).toEqual(account01.country); } { const { accountId, country, fileName } = args[1]; expect(fileName).toEqual(file2.raw_file_name); expect(accountId).toEqual(account01.id); expect(country).toEqual(account01.country); } { const { accountId, country, fileName } = args[2]; expect(fileName).toEqual(file3.raw_file_name); expect(accountId).toEqual(account02.id); expect(country).toEqual(account02.country); } { // DB全体のレコードを確認 const tasks = await getTasks(source); expect(tasks.length).toEqual(2); const files = await getAudioFiles(source); expect(files.length).toEqual(2); const optionItems = await getAudioOptionItems(source); expect(optionItems.length).toEqual(4); } }); it("Blobの削除でエラーが発生した場合、アラーム発報用のログが出力される", async () => { if (!source) fail(); // 2024/02/29 00:00:00を"今"とする const now = new Date("2024-02-29T00:00:00Z"); const { account: account01, admin: admin01 } = await makeTestAccount( source, { file_retention_days: 2, auto_file_delete: true, } ); // 2日と1秒前(削除対象)のタスクを作成 const { file: file1 } = await makeTestTask( source, account01.id, admin01.id, "case03", { status: TASK_STATUS.FINISHED, job_number: "job03", finished_at: new Date("2024-02-26T23:59:59Z"), } ); const args: { accountId: number; fileName: string; country: string }[] = []; const blobstorage = new AudioBlobStorageService(); Object.defineProperty(blobstorage, blobstorage.deleteFile.name, { value: async ( context: InvocationContext, accountId: number, country: string, fileName: string ): Promise => { throw new Error( `delete blob failed. succeeded: ${false}, errorCode: ${"ERROR_CODE"}, date: ${"DATE"}` ); }, writable: true, }); { // DB全体のレコードを確認 const tasks = await getTasks(source); expect(tasks.length).toEqual(1); const files = await getAudioFiles(source); expect(files.length).toEqual(1); const optionItems = await getAudioOptionItems(source); expect(optionItems.length).toEqual(2); } const context = new TestInvocationContext(); await deleteAudioFilesProcessing(context, source, blobstorage, now); const log = context .getLogs() .find((log) => log.includes(MANUAL_RECOVERY_REQUIRED)); expect(log).toBeDefined(); expect(log).toEqual( `[MANUAL_RECOVERY_REQUIRED] file delete failed. target={"id":"1","audio_file_id":"1","account_id":"1","country":"US","raw_file_name":"testcase03.wav"}` ); }); it("DBの削除でエラーが発生した場合、アラーム発報用のログが出力される", async () => { if (!source) fail(); // 2024/02/29 00:00:00を"今"とする const now = new Date("2024-02-29T00:00:00Z"); const { account: account01, admin: admin01 } = await makeTestAccount( source, { file_retention_days: 2, auto_file_delete: true, } ); // 2日と1秒前(削除対象)のタスクを作成 await makeTestTask(source, account01.id, admin01.id, "case03", { status: TASK_STATUS.FINISHED, job_number: "job03", finished_at: new Date("2024-02-26T23:59:59Z"), }); const args: { accountId: number; fileName: string; country: string }[] = []; const blobstorage = new AudioBlobStorageService(); Object.defineProperty(blobstorage, blobstorage.deleteFile.name, { value: async ( context: InvocationContext, accountId: number, country: string, fileName: string ): Promise => { args.push({ accountId, country, fileName }); }, writable: true, }); { // DB全体のレコードを確認 const tasks = await getTasks(source); expect(tasks.length).toEqual(1); const files = await getAudioFiles(source); expect(files.length).toEqual(1); const optionItems = await getAudioOptionItems(source); expect(optionItems.length).toEqual(2); } Object.defineProperty(source, "transaction", { value: async () => { throw new Error("transaction error"); }, writable: true, }); const context = new TestInvocationContext(); await expect( deleteAudioFilesProcessing(context, source, blobstorage, now) ).rejects.toThrow(); const log = context .getLogs() .find((log) => log.includes(MANUAL_RECOVERY_REQUIRED)); expect(log).toBeDefined(); expect(log).toEqual( `[MANUAL_RECOVERY_REQUIRED] Failed to execute auto file deletion function. error=Error: transaction error` ); }); });