Files
2026-07-13 13:39:12 +08:00

229 lines
7.1 KiB
TypeScript

import { describe, it, afterEach } from "node:test";
import assert from "node:assert";
import { createFile, listFiles, deleteFile, getFile } from "@/lib/localDb";
describe("Files API - Integration Tests", () => {
afterEach(() => {
const allFiles = listFiles({ limit: 1000 });
for (const f of allFiles) {
if (f.filename?.includes("test-")) {
deleteFile(f.id);
}
}
});
describe("POST /v1/files - File creation with auto-expiration", () => {
it("should create batch file with 30-day expiration", () => {
const now = Math.floor(Date.now() / 1000);
const thirtyDaysInSeconds = 30 * 24 * 60 * 60;
const file = createFile({
bytes: 100,
filename: "test-api-batch.jsonl",
purpose: "batch",
content: Buffer.from("test batch content"),
mimeType: "application/jsonl",
});
assert(file.expiresAt !== null);
const expectedMin = now + thirtyDaysInSeconds - 5; // 5 second tolerance
const expectedMax = now + thirtyDaysInSeconds + 5;
assert(file.expiresAt >= expectedMin && file.expiresAt <= expectedMax);
});
it("should create non-batch file without auto-expiration", () => {
const file = createFile({
bytes: 100,
filename: "test-api-fine-tune.jsonl",
purpose: "fine-tune",
content: Buffer.from("test ft content"),
mimeType: "application/jsonl",
});
assert(file.expiresAt === null || file.expiresAt === undefined);
});
it("should respect custom expires_after parameter", () => {
const now = Math.floor(Date.now() / 1000);
const sevenDaysInSeconds = 7 * 24 * 60 * 60;
const customExpiry = now + sevenDaysInSeconds;
const file = createFile({
bytes: 100,
filename: "test-api-custom.jsonl",
purpose: "assistants",
content: Buffer.from("test custom"),
mimeType: "application/jsonl",
expiresAt: customExpiry,
});
assert(file.expiresAt === customExpiry);
});
});
describe("GET /v1/files - List files with expiration info", () => {
it("should include expires_at in file list response", () => {
const batchFile = createFile({
bytes: 100,
filename: "test-list-batch.jsonl",
purpose: "batch",
content: Buffer.from("batch content"),
mimeType: "application/jsonl",
});
const files = listFiles({ limit: 10 });
const foundFile = files.find((f) => f.id === batchFile.id);
assert(foundFile !== undefined);
assert("expiresAt" in foundFile);
assert(foundFile.expiresAt === batchFile.expiresAt);
});
it("should show null expires_at for persistent files", () => {
const persistedFile = createFile({
bytes: 100,
filename: "test-list-persisted.txt",
purpose: "assistants",
content: Buffer.from("persisted"),
mimeType: "text/plain",
});
const files = listFiles({ limit: 10 });
const foundFile = files.find((f) => f.id === persistedFile.id);
assert(foundFile !== undefined);
assert(foundFile.expiresAt === null || foundFile.expiresAt === undefined);
});
it("should list multiple files with different expiration policies", () => {
const batchFile = createFile({
bytes: 100,
filename: "test-multi-batch.jsonl",
purpose: "batch",
content: Buffer.from("batch"),
mimeType: "application/jsonl",
});
const persistedFile = createFile({
bytes: 100,
filename: "test-multi-persisted.txt",
purpose: "fine-tune",
content: Buffer.from("persisted"),
mimeType: "text/plain",
});
const files = listFiles({ limit: 20 });
const batch = files.find((f) => f.id === batchFile.id);
const persisted = files.find((f) => f.id === persistedFile.id);
assert(batch !== undefined && batch.expiresAt !== null);
assert(
persisted !== undefined &&
(persisted.expiresAt === null || persisted.expiresAt === undefined)
);
});
it("should filter by purpose while preserving expiration info", () => {
createFile({
bytes: 100,
filename: "test-filter-batch.jsonl",
purpose: "batch",
content: Buffer.from("batch"),
mimeType: "application/jsonl",
});
createFile({
bytes: 100,
filename: "test-filter-ft.jsonl",
purpose: "fine-tune",
content: Buffer.from("ft"),
mimeType: "application/jsonl",
});
const batchFiles = listFiles({ purpose: "batch", limit: 10 });
assert(batchFiles.every((f) => f.purpose === "batch"));
assert(batchFiles.every((f) => f.expiresAt !== null || true));
});
});
describe("DELETE /v1/files/{file_id} - File deletion", () => {
it("should soft-delete file by setting deleted_at", () => {
const file = createFile({
bytes: 100,
filename: "test-delete.txt",
purpose: "assistants",
content: Buffer.from("deletable"),
mimeType: "text/plain",
});
const beforeDelete = getFile(file.id);
assert(beforeDelete !== null);
assert(beforeDelete.deletedAt === null || beforeDelete.deletedAt === undefined);
deleteFile(file.id);
const afterDelete = getFile(file.id);
assert(afterDelete === null, "Deleted file should not be retrievable");
});
it("should remove deleted file from list", () => {
const file = createFile({
bytes: 100,
filename: "test-delete-list.txt",
purpose: "assistants",
content: Buffer.from("to delete"),
mimeType: "text/plain",
});
const beforeDelete = listFiles({ limit: 100 }).some((f) => f.id === file.id);
assert(beforeDelete === true);
deleteFile(file.id);
const afterDelete = listFiles({ limit: 100 }).some((f) => f.id === file.id);
assert(afterDelete === false);
});
it("should handle deletion of non-existent file", () => {
const result = deleteFile("file-nonexistent-xyz");
// Should not throw, just return false
assert(typeof result === "boolean");
});
});
describe("File expiration data persistence", () => {
it("should preserve expires_at when retrieving file", () => {
const file = createFile({
bytes: 100,
filename: "test-persist-expiry.jsonl",
purpose: "batch",
content: Buffer.from("test"),
mimeType: "application/jsonl",
});
const retrieved = getFile(file.id);
assert(retrieved !== null);
assert(retrieved.expiresAt === file.expiresAt);
});
it("should maintain expiration data across list operations", () => {
const file = createFile({
bytes: 100,
filename: "test-expiry-list-persist.jsonl",
purpose: "batch",
content: Buffer.from("test"),
mimeType: "application/jsonl",
});
const originalExpiresAt = file.expiresAt;
// Fetch from list
const files = listFiles({ limit: 10 });
const listedFile = files.find((f) => f.id === file.id);
assert(listedFile !== undefined);
assert(listedFile.expiresAt === originalExpiresAt);
});
});
});