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![Review cut](artifact:${id})`} , ), ); 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")); });