import { configurePageAppHost } from "../../lib/app-host";
import { afterAll, afterEach, beforeEach, expect, mock, test } from "bun:test";
import { GlobalRegistrator } from "@happy-dom/global-registrator";
import { act, type AnchorHTMLAttributes, type ReactNode } from "react";
import { createRoot, type Root } from "react-dom/client";
import type { RetainedArtifactReference } from "@opengeni/sdk";
import { Markdown } from "../../../components/markdown";
import { ArtifactLinkBoundary } from "../session/artifact-link-boundary";
GlobalRegistrator.register({ url: "https://console.example" });
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT =
true;
const id = "33333333-3333-4333-8333-333333333333";
const workspaceId = "11111111-1111-4111-8111-111111111111";
let artifact: RetainedArtifactReference;
let accessKeyVersion = 1;
const client = {
getFile: mock(async () => ({ id, workspaceId, filename: "recording.mp4" })),
getRetainedArtifact: mock(async () => artifact),
createRetainedArtifactDownloadUrl: mock(
async (_workspaceId: string, _artifact: RetainedArtifactReference) => ({
url: "https://media.example/video.mp4",
expiresAt: "2099-01-01T00:00:00Z",
}),
),
createVideoArtifactPlaybackSource: mock(async () => ({
url: "https://media.example/generated.mp4",
})),
downloadRetainedArtifact: mock(async () => ({
bytes: new Uint8Array([37, 80, 68, 70]),
artifact,
})),
};
let activeClient = client;
mock.module("../../app-context", () => ({
useAppContext: () => ({ client: activeClient, accessKeyVersion }),
}));
configurePageAppHost({
surfaces: {
pdfPreview: ({ title }: { title: string }) => {
if (title === "Broken PDF") throw new Error("PDF renderer failed");
return {title} rendered PDF;
},
},
});
// Render router links as marked anchors so the fixture can tell them from raw
// `` full page loads.
mock.module("../../lib/navigation", () => ({
Link: ({
to,
params,
search: _search,
children,
...rest
}: {
to: string;
params?: Record | undefined;
search?: unknown | undefined;
children?: ReactNode | undefined;
} & AnchorHTMLAttributes) => (
params?.[key] ?? "")}
>
{children}
),
}));
const { InlineChatArtifact, RetainedFilePreview, retainedPreviewKind } =
await import("./retained-file-preview");
// Keep syntax-highlighting infrastructure out of these lifecycle tests.
mock.module("../../../components/pierre-file", () => ({
PierreFile: ({ contents }: { contents: string }) => {contents},
}));
let root: Root;
let container: HTMLDivElement;
async function waitForPreview(assertion: () => void): Promise {
const deadline = Date.now() + 2_000;
for (;;) {
try {
assertion();
return;
} catch (error) {
if (Date.now() >= deadline) throw error;
}
// Commit the render before waiting: React.lazy module evaluation and the
// authenticated byte read can settle after the original act scope exits.
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 0));
});
}
}
beforeEach(() => {
artifact = {
available: true,
artifactId: id,
kind: "file",
contentType: "video/mp4",
originalBytes: 4,
sha256: "a".repeat(64),
retainedAt: "2026-09-01T00:00:00Z",
retention: { policy: "workspace_file", expiresAt: null },
retrieval: {
method: "GET",
path: `/v1/workspaces/${workspaceId}/artifacts/${id}/content`,
acceptRanges: "bytes",
maxRangeBytes: 1048576,
},
};
accessKeyVersion = 1;
activeClient = client;
for (const fn of Object.values(client)) fn.mockClear();
// Unconsumed one-shot reads from a failed lazy-render test must not leak into
// later previews (especially the PDF Blob lifecycle assertion).
client.downloadRetainedArtifact.mockReset().mockImplementation(async () => ({
bytes: new Uint8Array([37, 80, 68, 70]),
artifact,
}));
container = document.createElement("div");
document.body.append(container);
root = createRoot(container);
});
afterEach(async () => {
await act(async () => root.unmount());
container.remove();
});
test("text stays download-only outside the explicit workbench opt-in", async () => {
artifact = { ...artifact, contentType: "text/x-patch" };
await act(async () =>
root.render(
,
),
);
expect(container.textContent).toContain("Preview is not available");
expect(client.downloadRetainedArtifact).not.toHaveBeenCalled();
await act(async () =>
root.render(),
);
expect(container.textContent).not.toContain("Read-only source");
expect(client.downloadRetainedArtifact).not.toHaveBeenCalled();
});
test("workbench text uses authenticated SDK bytes and keeps HTML inert", async () => {
artifact = { ...artifact, contentType: "text/html" };
client.downloadRetainedArtifact.mockResolvedValueOnce({
artifact,
bytes: new TextEncoder().encode(""),
});
await act(async () => {
root.render(
,
);
});
await waitForPreview(() => expect(container.textContent).toContain(""));
expect(client.downloadRetainedArtifact).toHaveBeenCalledWith(workspaceId, artifact, {
signal: expect.any(AbortSignal),
});
expect(container.querySelector("script")).toBeNull();
});
test("oversized workbench text is rejected before downloading", async () => {
artifact = { ...artifact, contentType: "text/plain", originalBytes: 262145 };
await act(async () =>
root.render(
,
),
);
await waitForPreview(() => expect(container.textContent).toContain("256 KiB"));
expect(client.downloadRetainedArtifact).not.toHaveBeenCalled();
});
test("workbench retry recovers and receipt changes abort stale text", async () => {
artifact = { ...artifact, contentType: "text/plain" };
client.downloadRetainedArtifact.mockRejectedValueOnce(new Error("checksum mismatch"));
const render = () => (
);
await act(async () => root.render(render()));
await waitForPreview(() =>
expect(container.textContent).toContain("Preview could not be loaded"),
);
client.downloadRetainedArtifact.mockResolvedValueOnce({
artifact,
bytes: new TextEncoder().encode("Verified retry"),
});
await act(async () => (container.querySelector("button") as HTMLButtonElement).click());
await waitForPreview(() => expect(container.textContent).toContain("Verified retry"));
let resolveOld!: (value: {
artifact: RetainedArtifactReference;
bytes: Uint8Array;
}) => void;
client.downloadRetainedArtifact.mockImplementationOnce(
() =>
new Promise((resolve) => {
resolveOld = resolve;
}),
);
artifact = { ...artifact, sha256: "b".repeat(64) };
await act(async () => root.render(render()));
const oldSignal = (
client.downloadRetainedArtifact.mock.calls.at(-1) as unknown as [
string,
RetainedArtifactReference,
{ signal: AbortSignal },
]
)[2].signal;
client.downloadRetainedArtifact.mockResolvedValueOnce({
artifact,
bytes: new TextEncoder().encode("New identity"),
});
accessKeyVersion++;
await act(async () => root.render(render()));
expect(oldSignal.aborted).toBe(true);
await act(async () => resolveOld({ artifact, bytes: new TextEncoder().encode("STALE SECRET") }));
await waitForPreview(() => expect(container.textContent).toContain("New identity"));
expect(container.textContent).not.toContain("STALE SECRET");
});
test("workbench hides stale content on client and workspace replacement", async () => {
artifact = { ...artifact, contentType: "text/plain" };
client.downloadRetainedArtifact.mockResolvedValueOnce({
artifact,
bytes: new TextEncoder().encode("Old client source"),
});
const render = (scope = workspaceId) => (
);
await act(async () => root.render(render()));
await waitForPreview(() => expect(container.textContent).toContain("Old client source"));
let resolveNew!: (value: {
artifact: RetainedArtifactReference;
bytes: Uint8Array;
}) => void;
activeClient = {
...client,
downloadRetainedArtifact: mock(
() =>
new Promise((resolve) => {
resolveNew = resolve;
}),
),
};
await act(async () => root.render(render()));
expect(container.textContent).toContain("Loading preview");
expect(container.textContent).not.toContain("Old client source");
await act(async () =>
resolveNew({ artifact, bytes: new TextEncoder().encode("New client source") }),
);
await waitForPreview(() => expect(container.textContent).toContain("New client source"));
await act(async () => root.render(render("44444444-4444-4444-8444-444444444444")));
expect(container.textContent).not.toContain("New client source");
expect(container.textContent).toContain("Loading preview");
const signal = (
activeClient.downloadRetainedArtifact.mock.calls.at(-1) as unknown as [
string,
RetainedArtifactReference,
{ signal: AbortSignal },
]
)[2].signal;
await act(async () => root.unmount());
expect(signal.aborted).toBe(true);
});
afterAll(() => GlobalRegistrator.unregister());
test("legacy media uses the saved filename without changing the download receipt", async () => {
artifact = { ...artifact, contentType: "application/octet-stream" };
await act(async () =>
root.render(
,
),
);
expect(container.querySelector("video")?.getAttribute("src")).toBe(
"https://media.example/video.mp4",
);
expect(client.createRetainedArtifactDownloadUrl.mock.calls[0]?.[1]).toBe(artifact);
expect(artifact.contentType).toBe("application/octet-stream");
expect(client.downloadRetainedArtifact).not.toHaveBeenCalled();
});
test("filename fallback stays limited to generic media, not active documents or explicit types", () => {
expect(retainedPreviewKind("application/octet-stream", "full-recording.mp4")).toBe("video");
expect(retainedPreviewKind("application/octet-stream", "recording.wav")).toBe("audio");
for (const filename of ["source.zip", "page.html", "drawing.svg", "movie.mp4.exe"]) {
expect(retainedPreviewKind("application/octet-stream", filename)).toBeNull();
}
expect(retainedPreviewKind("application/octet-stream")).toBeNull();
expect(retainedPreviewKind("text/html", "movie.mp4")).toBeNull();
});
test("equivalent receipts preserve playback while authorization changes refresh the source", async () => {
await act(async () =>
root.render(
,
),
);
const video = container.querySelector("video");
await act(async () =>
root.render(
,
),
);
expect(container.querySelector("video")).toBe(video);
expect(client.createRetainedArtifactDownloadUrl).toHaveBeenCalledTimes(1);
accessKeyVersion++;
await act(async () =>
root.render(
,
),
);
expect(client.createRetainedArtifactDownloadUrl).toHaveBeenCalledTimes(2);
expect(container.querySelector("video")).not.toBe(video);
artifact = { ...artifact, sha256: "b".repeat(64) };
await act(async () =>
root.render(
,
),
);
expect(client.createRetainedArtifactDownloadUrl).toHaveBeenCalledTimes(3);
});
test("PDF renderer failures stay inside the preview", async () => {
artifact = { ...artifact, contentType: "application/pdf" };
await act(async () =>
root.render(
Conversation remains
,
),
);
expect(container.textContent).toContain("Conversation remains");
await waitForPreview(() => expect(container.textContent).toContain("PDF preview unavailable"));
});
test("published link opens sidebar and embed renders playable video with stable chat space", async () => {
const open = mock(() => true);
await act(async () =>
root.render(
`/workspaces/${workspaceId}/artifacts/files/${value}`}
renderImage={() => (
)}
>{`[Watch](artifact:${id})\n\n`}
,
),
);
await act(async () => container.querySelector("button")?.click());
await act(async () =>
container
.querySelector("a")!
.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true })),
);
expect(client.getFile).not.toHaveBeenCalled();
expect(open).toHaveBeenCalledWith({ id, editable: false, kind: "file" });
expect(container.querySelector("video")?.getAttribute("src")).toBe(
"https://media.example/video.mp4",
);
expect(container.querySelector("video")?.hasAttribute("controls")).toBe(true);
expect(container.querySelector("video")?.hasAttribute("autoplay")).toBe(false);
expect(container.querySelector(".h-\\[400px\\]")).not.toBeNull();
expect(client.downloadRetainedArtifact).not.toHaveBeenCalled();
});
test("audio and generated video use authorized sources; unknown formats never fetch bytes", async () => {
artifact = { ...artifact, contentType: "audio/mpeg" };
await act(async () =>
root.render(
,
),
);
expect(container.querySelector("audio")).not.toBeNull();
artifact = { ...artifact, kind: "generated_video", contentType: "video/mp4" };
await act(async () =>
root.render(
,
),
);
expect(client.createVideoArtifactPlaybackSource).toHaveBeenCalled();
artifact = { ...artifact, kind: "file", contentType: "text/html" };
await act(async () =>
root.render(),
);
expect(container.querySelector("iframe,object,video,audio")).toBeNull();
expect(container.textContent).toContain("Download it");
});
test("media failure offers retry and refreshes authorization", async () => {
await act(async () =>
root.render(
,
),
);
await act(async () => container.querySelector("video")!.dispatchEvent(new Event("error")));
expect(container.textContent).toContain("Retry preview");
await act(async () => container.querySelector("button")!.click());
expect(client.createRetainedArtifactDownloadUrl).toHaveBeenCalledTimes(2);
accessKeyVersion++;
await act(async () =>
root.render(
,
),
);
expect(client.createRetainedArtifactDownloadUrl).toHaveBeenCalledTimes(3);
});
test("PDF bytes use a typed disposable Blob and revoke it when the preview closes", async () => {
const originalCreate = URL.createObjectURL;
const originalRevoke = URL.revokeObjectURL;
const create = mock((_blob: Blob) => "blob:test-pdf");
const revoke = mock((_url: string) => {});
URL.createObjectURL = create;
URL.revokeObjectURL = revoke;
try {
artifact = { ...artifact, contentType: "application/pdf" };
await act(async () =>
root.render(
,
),
);
expect(client.downloadRetainedArtifact).toHaveBeenCalledTimes(1);
await waitForPreview(() => expect(create).toHaveBeenCalledTimes(1));
expect(create.mock.calls[0]![0].type).toBe("application/pdf");
await act(async () => root.render(null));
expect(revoke).toHaveBeenCalledWith("blob:test-pdf");
} finally {
URL.createObjectURL = originalCreate;
URL.revokeObjectURL = originalRevoke;
}
});
test("unavailable artifact retains an actionable link without requesting media", async () => {
client.getRetainedArtifact.mockRejectedValueOnce(new Error("Access denied"));
await act(async () =>
root.render(
,
),
);
await act(async () => container.querySelector("button")?.click());
expect(container.textContent).toContain("Artifact unavailable");
const link = container.querySelector("a");
expect(link?.getAttribute("href")).toBe(`/workspaces/${workspaceId}/artifacts/files/${id}`);
// A router link, not a raw anchor that reloads the whole app.
expect(link?.hasAttribute("data-router-link")).toBe(true);
expect(client.createRetainedArtifactDownloadUrl).not.toHaveBeenCalled();
});
for (const [filename, element] of [
["recording.mp4", "video"],
["recording.wav", "audio"],
]) {
test(`inline legacy ${element} uses authorized filename and unchanged receipt`, async () => {
artifact = { ...artifact, contentType: "application/octet-stream" };
client.getFile.mockResolvedValueOnce({ id, workspaceId, filename: filename! });
await act(async () =>
root.render(),
);
await act(async () => container.querySelector("button")?.click());
expect(client.getFile).toHaveBeenCalledWith(workspaceId, id);
expect(container.querySelector(element!)).not.toBeNull();
expect(client.createRetainedArtifactDownloadUrl.mock.calls[0]?.[1]).toBe(artifact);
expect(artifact.contentType).toBe("application/octet-stream");
expect(client.downloadRetainedArtifact).not.toHaveBeenCalled();
});
}
for (const kind of ["wrong workspace", "wrong id", "unavailable", "non-media"]) {
test(`inline filename fallback rejects ${kind} even with a video alt label`, async () => {
artifact = { ...artifact, contentType: "application/octet-stream" };
if (kind === "unavailable") client.getFile.mockRejectedValueOnce(new Error("Denied"));
else
client.getFile.mockResolvedValueOnce({
id: kind === "wrong id" ? workspaceId : id,
workspaceId: kind === "wrong workspace" ? id : workspaceId,
filename: kind === "non-media" ? "source.zip" : "recording.mp4",
});
await act(async () =>
root.render(
,
),
);
await act(async () => container.querySelector("button")?.click());
expect(container.querySelector("video,audio")).toBeNull();
expect(container.textContent).toContain("Preview is not available");
expect(client.createRetainedArtifactDownloadUrl).not.toHaveBeenCalled();
});
}
test("chat images open by click without an Artifacts link; other files keep it", async () => {
artifact = { ...artifact, contentType: "image/png" };
await act(async () =>
root.render(),
);
await act(async () => container.querySelector("button")?.click());
await waitForPreview(() => expect(client.getRetainedArtifact).toHaveBeenCalled());
await waitForPreview(() => expect(container.textContent).not.toContain("Loading artifact"));
expect(container.textContent).not.toContain("in Artifacts");
artifact = { ...artifact, contentType: "video/mp4" };
await act(async () => root.render(null));
await act(async () =>
root.render(),
);
await act(async () => container.querySelector("button")?.click());
await waitForPreview(() => expect(container.textContent).toContain("Open Demo in Artifacts"));
});