162 lines
5.3 KiB
TypeScript
162 lines
5.3 KiB
TypeScript
import { describe, it, beforeEach, afterEach, after } from "node:test";
|
|
import assert from "node:assert";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-file-deletion-"));
|
|
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
|
|
process.env.DATA_DIR = TEST_DATA_DIR;
|
|
|
|
const { createFile, deleteFile, listFiles, createBatch, getBatch, updateBatch } =
|
|
await import("@/lib/localDb");
|
|
const { getDbInstance, resetDbInstance } = await import("@/lib/db/core");
|
|
|
|
after(() => {
|
|
resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
|
if (ORIGINAL_DATA_DIR === undefined) {
|
|
delete process.env.DATA_DIR;
|
|
} else {
|
|
process.env.DATA_DIR = ORIGINAL_DATA_DIR;
|
|
}
|
|
});
|
|
|
|
describe("File Deletion API", () => {
|
|
let testFileId: string;
|
|
|
|
afterEach(() => {
|
|
// Ensure cleanup
|
|
const file = listFiles({ limit: 100 }).find((f) => f.filename === "test-delete-file.txt");
|
|
if (file && !file.deletedAt) {
|
|
deleteFile(file.id);
|
|
}
|
|
});
|
|
|
|
beforeEach(() => {
|
|
const file = createFile({
|
|
bytes: 100,
|
|
filename: "test-delete-file.txt",
|
|
purpose: "assistants",
|
|
content: Buffer.from("test content"),
|
|
mimeType: "text/plain",
|
|
});
|
|
testFileId = file.id;
|
|
});
|
|
|
|
it("should delete a file successfully", () => {
|
|
const file = listFiles({ limit: 100 }).find((f) => f.id === testFileId);
|
|
assert(file !== undefined);
|
|
assert(file.deletedAt === null || file.deletedAt === undefined);
|
|
|
|
const success = deleteFile(testFileId);
|
|
assert(success === true);
|
|
|
|
const deletedFile = listFiles({ limit: 100 }).find((f) => f.id === testFileId);
|
|
assert(deletedFile === undefined, "Deleted file should not appear in list");
|
|
});
|
|
|
|
it("should set deleted_at timestamp on deletion", () => {
|
|
const now = Math.floor(Date.now() / 1000);
|
|
deleteFile(testFileId);
|
|
|
|
// Check directly from DB since listFiles filters out deleted files
|
|
const db = getDbInstance();
|
|
const row = db.prepare("SELECT deleted_at FROM files WHERE id = ?").get(testFileId);
|
|
|
|
assert(row !== undefined);
|
|
assert(row.deleted_at !== null);
|
|
assert(typeof row.deleted_at === "number");
|
|
// Should be very recent
|
|
assert(row.deleted_at >= now);
|
|
assert(row.deleted_at <= now + 10);
|
|
});
|
|
|
|
it("should not list deleted files", () => {
|
|
const beforeCount = listFiles({ limit: 1000 }).length;
|
|
deleteFile(testFileId);
|
|
const afterCount = listFiles({ limit: 1000 }).length;
|
|
|
|
assert(afterCount === beforeCount - 1);
|
|
});
|
|
|
|
it("should handle deletion of non-existent file gracefully", () => {
|
|
const success = deleteFile("file-nonexistent-12345");
|
|
// Should return false or handle gracefully (depending on implementation)
|
|
assert(typeof success === "boolean");
|
|
});
|
|
|
|
describe("Batch file deletion during cleanup", () => {
|
|
it("should delete batch input file after 30 days of completion", () => {
|
|
// Create test batch
|
|
const inputFile = createFile({
|
|
bytes: 100,
|
|
filename: "test-batch-input.jsonl",
|
|
purpose: "batch",
|
|
content: Buffer.from(
|
|
'{"custom_id":"1","method":"POST","url":"/v1/chat/completions","body":{"model":"gpt-4","messages":[{"role":"user","content":"hi"}]}}'
|
|
),
|
|
mimeType: "application/jsonl",
|
|
});
|
|
|
|
const batch = createBatch({
|
|
endpoint: "/v1/chat/completions",
|
|
completionWindow: "24h",
|
|
inputFileId: inputFile.id,
|
|
});
|
|
|
|
// Verify batch and file exist
|
|
let batchData = getBatch(batch.id);
|
|
assert(batchData !== null);
|
|
let fileList = listFiles({ limit: 1000 });
|
|
assert(fileList.some((f) => f.id === inputFile.id));
|
|
|
|
// Simulate batch completion (completed 31 days ago)
|
|
const thirtyOneDaysAgo = Math.floor(Date.now() / 1000) - 31 * 24 * 60 * 60;
|
|
updateBatch(batch.id, {
|
|
status: "completed",
|
|
completedAt: thirtyOneDaysAgo,
|
|
});
|
|
|
|
// After cleanup runs, file should be deleted
|
|
// (Note: this tests the logic, actual cleanup runs in the processor)
|
|
const expiresAt = thirtyOneDaysAgo + 30 * 24 * 60 * 60;
|
|
const now = Math.floor(Date.now() / 1000);
|
|
assert(now > expiresAt, "Current time should be past expiration");
|
|
});
|
|
|
|
it("should NOT delete batch input file if not yet expired", () => {
|
|
const inputFile = createFile({
|
|
bytes: 100,
|
|
filename: "test-batch-not-expired.jsonl",
|
|
purpose: "batch",
|
|
content: Buffer.from(
|
|
'{"custom_id":"1","method":"POST","url":"/v1/chat/completions","body":{"model":"gpt-4","messages":[{"role":"user","content":"hi"}]}}'
|
|
),
|
|
mimeType: "application/jsonl",
|
|
});
|
|
|
|
const batch = createBatch({
|
|
endpoint: "/v1/chat/completions",
|
|
completionWindow: "24h",
|
|
inputFileId: inputFile.id,
|
|
});
|
|
|
|
// Simulate batch completion (completed 15 days ago)
|
|
const fifteenDaysAgo = Math.floor(Date.now() / 1000) - 15 * 24 * 60 * 60;
|
|
updateBatch(batch.id, {
|
|
status: "completed",
|
|
completedAt: fifteenDaysAgo,
|
|
});
|
|
|
|
// File should still exist
|
|
const fileList = listFiles({ limit: 1000 });
|
|
const file = fileList.find((f) => f.id === inputFile.id);
|
|
assert(file !== undefined, "File should still exist before expiration");
|
|
|
|
// Cleanup
|
|
deleteFile(inputFile.id);
|
|
});
|
|
});
|
|
});
|