// @vitest-environment node
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("server-only", () => ({}));
import {
deleteGateFile,
GateFileError,
getGateFileContent,
uploadGateFile,
} from "@/lib/chat/gate-files";
beforeEach(() => {
vi.stubEnv("AEGIDA_GATE_URL", "https://gate.example.test");
});
afterEach(() => {
vi.unstubAllEnvs();
vi.unstubAllGlobals();
});
describe("Aegida Gate Files client", () => {
it("streams the original File with purpose user_data and the exact user token", async () => {
const file = new File(["brief"], "brief.txt", { type: "text/plain" });
const arrayBuffer = vi.spyOn(file, "arrayBuffer").mockRejectedValue(
new Error("whole-file-arrayBuffer-must-not-be-used"),
);
const fetchMock = vi.fn().mockResolvedValue(
Response.json({
id: "file-opaque_123",
object: "file",
bytes: 5,
created_at: 1_785_758_400,
filename: "brief.txt",
purpose: "user_data",
}),
);
vi.stubGlobal("fetch", fetchMock);
const signal = new AbortController().signal;
await expect(uploadGateFile(file, "Bearer exact-user-jwt", signal)).resolves.toEqual({
id: "file-opaque_123",
filename: "brief.txt",
bytes: 5,
});
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit];
expect(url).toBe("https://gate.example.test/v1/files");
expect(init).toMatchObject({
method: "POST",
headers: { Authorization: "Bearer exact-user-jwt" },
cache: "no-store",
redirect: "error",
signal,
});
expect(init.headers).not.toHaveProperty("Content-Type");
expect(init.body).toBeInstanceOf(FormData);
expect((init.body as FormData).get("purpose")).toBe("user_data");
expect((init.body as FormData).get("file")).toBe(file);
expect(arrayBuffer).not.toHaveBeenCalled();
});
it.each([
null,
{},
{ id: "not-a-file", object: "file", bytes: 5, created_at: 1, filename: "a.txt", purpose: "user_data" },
{ id: "file-good", object: "other", bytes: 5, created_at: 1, filename: "a.txt", purpose: "user_data" },
{ id: "file-good", object: "file", bytes: -1, created_at: 1, filename: "a.txt", purpose: "user_data" },
{ id: "file-good", object: "file", bytes: 5, created_at: 1, filename: "", purpose: "user_data" },
{ id: "file-good", object: "file", bytes: 5, created_at: 1, filename: "a.txt", purpose: "assistants" },
])("rejects a malformed FileObject %#", async (value) => {
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(Response.json(value)));
await expect(
uploadGateFile(
new File(["brief"], "brief.txt", { type: "text/plain" }),
"Bearer user",
new AbortController().signal,
),
).rejects.toBeInstanceOf(GateFileError);
});
it("uses authenticated standard content and delete paths", async () => {
const fetchMock = vi
.fn()
.mockResolvedValueOnce(new Response("private file contents"))
.mockResolvedValueOnce(new Response(null, { status: 204 }));
vi.stubGlobal("fetch", fetchMock);
const signal = new AbortController().signal;
const content = await getGateFileContent("file-opaque_123", "Bearer user", signal);
expect(await new Response(content).text()).toBe("private file contents");
await deleteGateFile("file-opaque_123", "Bearer user", signal);
expect(fetchMock).toHaveBeenNthCalledWith(1, "https://gate.example.test/v1/files/file-opaque_123/content", {
method: "GET",
headers: { Authorization: "Bearer user" },
cache: "no-store",
redirect: "error",
signal,
});
expect(fetchMock).toHaveBeenNthCalledWith(2, "https://gate.example.test/v1/files/file-opaque_123", {
method: "DELETE",
headers: { Authorization: "Bearer user" },
cache: "no-store",
redirect: "error",
signal,
});
});
it("sanitizes non-2xx and oversized JSON responses", async () => {
vi.stubGlobal(
"fetch",
vi
.fn()
.mockResolvedValueOnce(new Response("private-dependency-sentinel", { status: 500 }))
.mockResolvedValueOnce(new Response("x".repeat(1_048_577))),
);
const file = new File(["brief"], "brief.txt", { type: "text/plain" });
for (let attempt = 0; attempt < 2; attempt += 1) {
let error: unknown;
try {
await uploadGateFile(file, "Bearer user", new AbortController().signal);
} catch (caught) {
error = caught;
}
expect(error).toBeInstanceOf(GateFileError);
expect(String(error)).not.toContain("private-dependency-sentinel");
expect(String(error)).not.toContain("brief");
}
});
});