← Files Biological Sequence & Alignment ViewerARCHIVED FILE
src/views/workbench-persistence.test.ts
28.4 KB · Sep 30, 2026 · 23:01 UTC
import { createHash, randomUUID } from "node:crypto";
import { mkdtemp, rm } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import type { App } from "@modelcontextprotocol/ext-apps";
import { afterEach, describe, expect, it, vi } from "vitest";
import { SEQUENCE_VIEWER_LIMITS } from "../runtime-contract";
import { createServerWorkbenchStore } from "../server-workbench-store";
import { SequenceWorkbenchUploadStore } from "../server-workbench-upload";
import {
SEQUENCE_ABORT_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME,
SEQUENCE_APPEND_WORKBENCH_PAYLOAD_CHUNK_TOOL_NAME,
SEQUENCE_BEGIN_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME,
SEQUENCE_FINISH_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME,
SEQUENCE_PERSIST_WORKBENCH_PAYLOAD_TOOL_NAME,
SEQUENCE_PREPARE_WORKSPACE_EXPORT_TOOL_NAME,
} from "../workbench-persistence-protocol";
import {
SEQUENCE_CREATE_WORKSPACE_DIRECTORY_TOOL_NAME,
SEQUENCE_LIST_WORKSPACE_DIRECTORY_TOOL_NAME,
} from "../workspace-browser-protocol";
import {
encodedSequenceToolRequestBytes,
createSequenceWorkspaceArtifactPublisher,
persistSequenceCommandResult,
persistSequenceWorkbenchPayload,
publishPreparedSequenceWorkspaceArtifact,
} from "./workbench-persistence";
const temporaryDirectories: Array<string> = [];
const stores: Array<SequenceWorkbenchUploadStore> = [];
afterEach(async () => {
vi.restoreAllMocks();
await Promise.allSettled(stores.splice(0).map((store) => store.dispose()));
await Promise.all(
temporaryDirectories
.splice(0)
.map((directory) => rm(directory, { force: true, recursive: true })),
);
});
describe("proxy-safe workbench persistence client", () => {
it("preserves actionable MCP error-result messages during upload reconciliation", async () => {
const callServerTool = vi.fn(
async (request: Parameters<App["callServerTool"]>[0]) => {
const uploadId = (request.arguments as { uploadId: string }).uploadId;
if (request.name === SEQUENCE_BEGIN_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME) {
return {
content: [
{
text: "The workspace export or provenance sidecar already exists.",
type: "text",
},
],
isError: true,
} as Awaited<ReturnType<App["callServerTool"]>>;
}
expect(request.name).toBe(
SEQUENCE_ABORT_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME,
);
return {
content: [],
structuredContent: { aborted: true, uploadId },
} as Awaited<ReturnType<App["callServerTool"]>>;
},
);
await expect(
persistSequenceWorkbenchPayload(
{ callServerTool } as unknown as Pick<App, "callServerTool">,
{
commandId: randomUUID(),
content: "(P01116,P01111,P01112);\n",
destination: {
base: "opened-source",
collisionPolicy: "exact",
kind: "workspace",
relativePath: "RAS.nwk",
},
format: "newick",
kind: "artifact",
mediaType: "text/x-newick",
name: "RAS.nwk",
provenance: {
engine: "sequence-viewer-guide-tree-v1",
parameters: {},
sourceRevision: 0,
},
sessionId: randomUUID(),
},
),
).rejects.toThrow(
"The workspace export or provenance sidecar already exists.",
);
expect(callServerTool.mock.calls.map(([request]) => request.name)).toEqual([
SEQUENCE_BEGIN_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME,
SEQUENCE_ABORT_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME,
]);
});
it("preflights UI workspace exports and uses the bounded streaming transport", async () => {
const commandId = randomUUID();
const content = ">ui\nACGT\n";
const digest = createHash("sha256").update(content).digest("hex");
const callServerTool = vi.fn(
async (request: Parameters<App["callServerTool"]>[0]) => {
if (request.name === SEQUENCE_PREPARE_WORKSPACE_EXPORT_TOOL_NAME) {
return {
content: [],
structuredContent: {
commandId,
destination: { base: "opened-source", kind: "workspace" },
},
} as Awaited<ReturnType<App["callServerTool"]>>;
}
if (request.name === SEQUENCE_BEGIN_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME) {
return {
content: [],
structuredContent: {
maxChunkBytes: SEQUENCE_VIEWER_LIMITS.persistence.maxChunkBytes,
maxWorkspaceArtifactBytes:
SEQUENCE_VIEWER_LIMITS.workspace.maxArtifactBytes,
receivedBytes: 0,
uploadId: (request.arguments as { uploadId: string }).uploadId,
},
} as Awaited<ReturnType<App["callServerTool"]>>;
}
if (request.name === SEQUENCE_APPEND_WORKBENCH_PAYLOAD_CHUNK_TOOL_NAME) {
return {
content: [],
structuredContent: {
maxChunkBytes: SEQUENCE_VIEWER_LIMITS.persistence.maxChunkBytes,
maxWorkspaceArtifactBytes:
SEQUENCE_VIEWER_LIMITS.workspace.maxArtifactBytes,
receivedBytes: Buffer.byteLength(content),
uploadId: (request.arguments as { uploadId: string }).uploadId,
},
} as Awaited<ReturnType<App["callServerTool"]>>;
}
expect(request.name).toBe(
SEQUENCE_FINISH_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME,
);
return {
content: [],
structuredContent: {
destination: { base: "opened-source", kind: "workspace" },
format: "fasta",
kind: "artifact",
mediaType: "text/x-fasta",
name: "ui-derived.fasta",
outputWorkspacePath: "data/exports/ui-derived.fasta",
provenanceWorkspacePath:
"data/exports/ui-derived.fasta.provenance.json",
sha256: digest,
size: Buffer.byteLength(content),
version: 1,
},
} as Awaited<ReturnType<App["callServerTool"]>>;
},
);
const app = { callServerTool } as unknown as Pick<App, "callServerTool">;
const result = await publishPreparedSequenceWorkspaceArtifact(app, {
artifact: {
content,
format: "fasta",
mediaType: "text/x-fasta",
name: "suggested.fasta",
provenance: {
engine: "sequence-viewer-browser-export-v1",
parameters: { kind: "record-fasta" },
sourceRevision: 0,
},
},
relativePath: "exports/ui-derived.fasta",
sessionId: randomUUID(),
});
expect(result).toMatchObject({
kind: "artifact",
name: "ui-derived.fasta",
outputWorkspacePath: "data/exports/ui-derived.fasta",
});
expect(callServerTool).toHaveBeenCalledTimes(4);
const [preflight, begin, append, finish] = callServerTool.mock.calls.map(([request]) =>
request,
);
expect(preflight).toMatchObject({
arguments: {
destination: {
base: "opened-source",
collisionPolicy: "exact",
kind: "workspace",
relativePath: "exports/ui-derived.fasta",
},
name: "ui-derived.fasta",
sha256: digest,
},
name: SEQUENCE_PREPARE_WORKSPACE_EXPORT_TOOL_NAME,
});
expect(begin).toMatchObject({
arguments: { commandId, name: "ui-derived.fasta" },
name: SEQUENCE_BEGIN_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME,
});
expect(append?.name).toBe(
SEQUENCE_APPEND_WORKBENCH_PAYLOAD_CHUNK_TOOL_NAME,
);
expect(finish?.name).toBe(
SEQUENCE_FINISH_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME,
);
expect(
Math.max(
...[preflight, begin, append, finish].map((request) =>
encodedSequenceToolRequestBytes(request!),
),
),
).toBeLessThanOrEqual(SEQUENCE_VIEWER_LIMITS.persistence.proxyEnvelopeBytes);
});
it("binds browser and folder calls to the live viewer session", async () => {
const sessionId = randomUUID();
const callServerTool = vi.fn(
async (request: Parameters<App["callServerTool"]>[0]) => {
if (request.name === SEQUENCE_LIST_WORKSPACE_DIRECTORY_TOOL_NAME) {
return {
content: [],
structuredContent: {
directory: {
breadcrumbs: [
{ label: "Workspace", relativePath: ".", workspacePath: "." },
],
relativePath: ".",
sourceDirectoryWorkspacePath: ".",
workspacePath: ".",
},
entries: [],
omittedEntries: 0,
},
} as Awaited<ReturnType<App["callServerTool"]>>;
}
expect(request.name).toBe(SEQUENCE_CREATE_WORKSPACE_DIRECTORY_TOOL_NAME);
return {
content: [],
structuredContent: {
name: "analysis",
relativePath: "analysis",
workspacePath: "analysis",
},
} as Awaited<ReturnType<App["callServerTool"]>>;
},
);
const publisher = createSequenceWorkspaceArtifactPublisher(
{ callServerTool } as unknown as Pick<App, "callServerTool">,
sessionId,
);
await publisher.listDirectory({ directory: ".", limit: 50 });
await publisher.createDirectory({
name: "analysis",
parentDirectory: ".",
});
expect(callServerTool).toHaveBeenNthCalledWith(1, {
arguments: { directory: ".", limit: 50, sessionId },
name: SEQUENCE_LIST_WORKSPACE_DIRECTORY_TOOL_NAME,
});
expect(callServerTool).toHaveBeenNthCalledWith(2, {
arguments: { name: "analysis", parentDirectory: ".", sessionId },
name: SEQUENCE_CREATE_WORKSPACE_DIRECTORY_TOOL_NAME,
});
});
it("accepts only deterministic server-selected names for explicit next-version saves", async () => {
const content = ">versioned\nAC\n";
const digest = createHash("sha256").update(content).digest("hex");
const commandId = randomUUID();
const callServerTool = vi.fn(
async (request: Parameters<App["callServerTool"]>[0]) => {
if (request.name === SEQUENCE_ABORT_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME) {
return {
content: [],
structuredContent: {
aborted: true,
uploadId: (request.arguments as { uploadId: string }).uploadId,
},
} as Awaited<ReturnType<App["callServerTool"]>>;
}
if (request.name === SEQUENCE_PREPARE_WORKSPACE_EXPORT_TOOL_NAME) {
return {
content: [],
structuredContent: {
commandId,
destination: { base: "opened-source", kind: "workspace" },
},
} as Awaited<ReturnType<App["callServerTool"]>>;
}
if (request.name === SEQUENCE_BEGIN_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME) {
return {
content: [],
structuredContent: {
maxChunkBytes: SEQUENCE_VIEWER_LIMITS.persistence.maxChunkBytes,
maxWorkspaceArtifactBytes:
SEQUENCE_VIEWER_LIMITS.workspace.maxArtifactBytes,
receivedBytes: 0,
uploadId: (request.arguments as { uploadId: string }).uploadId,
},
} as Awaited<ReturnType<App["callServerTool"]>>;
}
if (request.name === SEQUENCE_APPEND_WORKBENCH_PAYLOAD_CHUNK_TOOL_NAME) {
return {
content: [],
structuredContent: {
maxChunkBytes: SEQUENCE_VIEWER_LIMITS.persistence.maxChunkBytes,
maxWorkspaceArtifactBytes:
SEQUENCE_VIEWER_LIMITS.workspace.maxArtifactBytes,
receivedBytes: Buffer.byteLength(content),
uploadId: (request.arguments as { uploadId: string }).uploadId,
},
} as Awaited<ReturnType<App["callServerTool"]>>;
}
return {
content: [],
structuredContent: {
destination: { base: "opened-source", kind: "workspace" },
format: "fasta",
kind: "artifact",
mediaType: "text/x-fasta",
name: "derived-2.fasta",
outputWorkspacePath: "data/derived-2.fasta",
provenanceWorkspacePath:
"data/derived-2.fasta.provenance.json",
sha256: digest,
size: Buffer.byteLength(content),
version: 1,
},
} as Awaited<ReturnType<App["callServerTool"]>>;
},
);
const input = {
artifact: {
content,
format: "fasta" as const,
mediaType: "text/x-fasta",
name: "derived.fasta",
provenance: {
engine: "sequence-viewer-browser-export-v1",
parameters: {},
sourceRevision: 0,
},
},
relativePath: "derived.fasta",
sessionId: randomUUID(),
};
await expect(
publishPreparedSequenceWorkspaceArtifact(
{ callServerTool } as unknown as Pick<App, "callServerTool">,
{ ...input, collisionPolicy: "next-version" },
),
).resolves.toMatchObject({ name: "derived-2.fasta" });
await expect(
publishPreparedSequenceWorkspaceArtifact(
{ callServerTool } as unknown as Pick<App, "callServerTool">,
input,
),
).rejects.toThrow("workspace artifact binding is inconsistent");
});
it("does not replay a folder creation after a lost mutating response", async () => {
const callServerTool = vi.fn(async () => {
throw new Error("proxy timeout after create");
});
const publisher = createSequenceWorkspaceArtifactPublisher(
{ callServerTool } as unknown as Pick<App, "callServerTool">,
randomUUID(),
);
await expect(
publisher.createDirectory({ name: "analysis", parentDirectory: "." }),
).rejects.toThrow("proxy timeout after create");
expect(callServerTool).toHaveBeenCalledOnce();
});
it("rejects unsafe UI destinations before any server call", async () => {
const callServerTool = vi.fn();
await expect(
publishPreparedSequenceWorkspaceArtifact(
{ callServerTool } as unknown as Pick<App, "callServerTool">,
{
artifact: {
content: ">ui\nACGT\n",
format: "fasta",
mediaType: "text/x-fasta",
name: "ui.fasta",
provenance: {
engine: "sequence-viewer-browser-export-v1",
parameters: {},
sourceRevision: 0,
},
},
relativePath: "/tmp/ui.fasta",
sessionId: randomUUID(),
},
),
).rejects.toThrow();
expect(callServerTool).not.toHaveBeenCalled();
});
it("uses the idempotent one-shot path only below the centralized threshold", async () => {
const { app, requests } = await createAdapter();
const content = `>${"A".repeat(
SEQUENCE_VIEWER_LIMITS.persistence.maxOneShotBytes - 2,
)}`;
const result = await persistSequenceWorkbenchPayload(app, {
commandId: randomUUID(),
content,
format: "fasta",
kind: "artifact",
mediaType: "text/x-fasta",
name: "threshold.fasta",
sessionId: randomUUID(),
});
expect(result).toMatchObject({ kind: "artifact", name: "threshold.fasta" });
expect(requests.map(({ name }) => name)).toEqual([
SEQUENCE_PERSIST_WORKBENCH_PAYLOAD_TOOL_NAME,
]);
expect(
Math.max(...requests.map(encodedSequenceToolRequestBytes)),
).toBeLessThanOrEqual(
SEQUENCE_VIEWER_LIMITS.persistence.proxyEnvelopeBytes,
);
});
it("resumes a multi-megabyte artifact after lost append and finish responses", async () => {
let loseAppendResponse = true;
let loseFinishResponse = true;
const { app, requests, workbench } = await createAdapter({
afterCall: ({ name }, result) => {
if (
name === SEQUENCE_APPEND_WORKBENCH_PAYLOAD_CHUNK_TOOL_NAME &&
loseAppendResponse
) {
loseAppendResponse = false;
throw new Error("proxy timeout after append");
}
if (
name === SEQUENCE_FINISH_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME &&
loseFinishResponse
) {
loseFinishResponse = false;
throw new Error("proxy timeout after finish");
}
return result;
},
});
const content = `>${"A".repeat(2 * 1_024 * 1_024)}`;
const result = await persistSequenceWorkbenchPayload(app, {
commandId: randomUUID(),
content,
format: "fasta",
kind: "artifact",
mediaType: "text/x-fasta",
name: "large.fasta",
sessionId: randomUUID(),
});
expect(result).toMatchObject({
kind: "artifact",
size: Buffer.byteLength(content),
});
expect(
requests.filter(
({ name }) =>
name === SEQUENCE_APPEND_WORKBENCH_PAYLOAD_CHUNK_TOOL_NAME,
).length,
).toBeGreaterThan(
Math.ceil(
Buffer.byteLength(content) /
SEQUENCE_VIEWER_LIMITS.persistence.maxChunkBytes,
),
);
expect(
requests.filter(
({ name }) =>
name === SEQUENCE_FINISH_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME,
),
).toHaveLength(2);
expect(
Math.max(...requests.map(encodedSequenceToolRequestBytes)),
).toBeLessThanOrEqual(
SEQUENCE_VIEWER_LIMITS.persistence.proxyEnvelopeBytes,
);
if (result.kind !== "artifact" || !("id" in result)) {
throw new Error("Expected private artifact result.");
}
await expect(workbench.readArtifact(result.id)).resolves.toMatchObject({
content,
});
});
it("streams beyond 64 MiB, retries the identical lost chunk, and stays inside the host envelope", async () => {
const commandId = randomUUID();
const totalBytes = 64 * 1_024 * 1_024 + 1;
const header = ">large\n";
const slab = "A".repeat(64 * 1_024);
let producerStarts = 0;
const createChunks = async function* () {
producerStarts += 1;
yield header;
let remaining = totalBytes - Buffer.byteLength(header);
while (remaining > 0) {
const length = Math.min(remaining, slab.length);
yield slab.slice(0, length);
remaining -= length;
}
};
const digest = createHash("sha256");
const requests: Array<Parameters<App["callServerTool"]>[0]> = [];
let receivedBytes = 0;
let lostResponse = false;
let lostRequest: { dataBase64: string; offset: number } | undefined;
const callServerTool = vi.fn(
async (request: Parameters<App["callServerTool"]>[0]) => {
requests.push(request);
const args = request.arguments as Record<string, unknown>;
if (request.name === SEQUENCE_PREPARE_WORKSPACE_EXPORT_TOOL_NAME) {
return {
content: [],
structuredContent: {
commandId,
destination: { base: "opened-source", kind: "workspace" },
maxWorkspaceArtifactBytes:
SEQUENCE_VIEWER_LIMITS.workspace.maxArtifactBytes,
},
} as Awaited<ReturnType<App["callServerTool"]>>;
}
if (request.name === SEQUENCE_BEGIN_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME) {
return {
content: [],
structuredContent: {
maxChunkBytes: SEQUENCE_VIEWER_LIMITS.persistence.maxChunkBytes,
maxWorkspaceArtifactBytes:
SEQUENCE_VIEWER_LIMITS.workspace.maxArtifactBytes,
receivedBytes,
uploadId: args.uploadId,
},
} as Awaited<ReturnType<App["callServerTool"]>>;
}
if (request.name === SEQUENCE_APPEND_WORKBENCH_PAYLOAD_CHUNK_TOOL_NAME) {
const offset = args.offset as number;
const dataBase64 = args.dataBase64 as string;
const chunk = Buffer.from(dataBase64, "base64");
if (offset < receivedBytes) {
expect({ dataBase64, offset }).toEqual(lostRequest);
} else {
expect(offset).toBe(receivedBytes);
digest.update(chunk);
receivedBytes += chunk.byteLength;
if (!lostResponse && receivedBytes >= 64 * 1_024 * 1_024) {
lostResponse = true;
lostRequest = { dataBase64, offset };
throw new Error("proxy timeout after accepted append");
}
}
return {
content: [],
structuredContent: {
maxChunkBytes: SEQUENCE_VIEWER_LIMITS.persistence.maxChunkBytes,
maxWorkspaceArtifactBytes:
SEQUENCE_VIEWER_LIMITS.workspace.maxArtifactBytes,
receivedBytes,
uploadId: args.uploadId,
},
} as Awaited<ReturnType<App["callServerTool"]>>;
}
if (request.name === SEQUENCE_FINISH_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME) {
const sha256 = digest.digest("hex");
return {
content: [],
structuredContent: {
destination: { base: "opened-source", kind: "workspace" },
format: "fasta",
kind: "artifact",
mediaType: "text/x-fasta",
name: "large.fasta",
outputWorkspacePath: "data/large.fasta",
provenanceWorkspacePath: "data/large.fasta.provenance.json",
sha256,
size: totalBytes,
version: 1,
},
} as Awaited<ReturnType<App["callServerTool"]>>;
}
throw new Error(`Unexpected tool ${request.name}`);
},
);
const result = await publishPreparedSequenceWorkspaceArtifact(
{ callServerTool } as unknown as Pick<App, "callServerTool">,
{
artifact: {
createChunks,
format: "fasta",
mediaType: "text/x-fasta",
name: "large.fasta",
provenance: {
engine: "sequence-viewer-browser-export-v1",
parameters: { scope: "all" },
sourceRevision: 0,
},
},
relativePath: "large.fasta",
sessionId: randomUUID(),
},
);
expect(result).toMatchObject({ kind: "artifact", size: totalBytes });
expect(lostResponse).toBe(true);
expect(producerStarts).toBe(2);
expect(Math.max(...requests.map(encodedSequenceToolRequestBytes))).toBeLessThanOrEqual(
SEQUENCE_VIEWER_LIMITS.persistence.proxyEnvelopeBytes,
);
}, 30_000);
it("chunks a near-maximum valid session and never includes it in completion", async () => {
const { app, requests } = await createAdapter();
const session = validSession(430 * 1_024);
const commandId = randomUUID();
const result = await persistSequenceCommandResult(app, {
command: {
action: "save_session",
commandId,
name: "large.sequence-session.json",
revision: 1,
},
result: {
applied: true,
message: "Serialized session.",
state: { session },
},
sessionId: randomUUID(),
});
expect(result.state?.session).toMatchObject({
name: "large.sequence-session.json",
savedSessionId: expect.any(String),
size: Buffer.byteLength(session),
});
expect(result.state?.session).not.toEqual(session);
expect(JSON.stringify(result)).not.toContain(session.slice(-1_000));
expect(requests[0]?.name).toBe(
SEQUENCE_BEGIN_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME,
);
expect(
Math.max(...requests.map(encodedSequenceToolRequestBytes)),
).toBeLessThanOrEqual(
SEQUENCE_VIEWER_LIMITS.persistence.proxyEnvelopeBytes,
);
});
it("replaces export bodies with compact artifact metadata", async () => {
const { app } = await createAdapter();
const content = `>${"C".repeat(400 * 1_024)}`;
const result = await persistSequenceCommandResult(app, {
command: {
action: "export_artifact",
commandId: randomUUID(),
destination: { kind: "private" },
format: "fasta",
revision: 1,
scope: "all",
},
result: {
applied: true,
message: "Prepared export.",
state: {
artifact: {
content,
format: "fasta",
mediaType: "text/x-fasta",
name: "export.fasta",
},
provenance: {
engine: "sequence-viewer-export-v1",
parameters: { scope: "all" },
sourceRevision: 2,
},
},
},
sessionId: randomUUID(),
});
expect(result.state?.artifact).toMatchObject({
resourceUri: expect.stringMatching(/^viewer-artifact:/u),
sha256: expect.stringMatching(/^[a-f0-9]{64}$/u),
size: Buffer.byteLength(content),
});
expect(result.state?.artifact).not.toHaveProperty("content");
});
it("aborts and cleans staging when the caller cancels before commit", async () => {
const controller = new AbortController();
const { app, store } = await createAdapter({
beforeCall: ({ name }) => {
if (name === SEQUENCE_APPEND_WORKBENCH_PAYLOAD_CHUNK_TOOL_NAME) {
controller.abort(new DOMException("cancelled", "AbortError"));
}
},
});
const content = `>${"G".repeat(400 * 1_024)}`;
await expect(
persistSequenceWorkbenchPayload(app, {
commandId: randomUUID(),
content,
format: "fasta",
kind: "artifact",
mediaType: "text/x-fasta",
name: "cancelled.fasta",
sessionId: randomUUID(),
signal: controller.signal,
}),
).rejects.toMatchObject({ name: "AbortError" });
expect(store.activeUploads).toBe(0);
});
});
type AdapterOptions = {
afterCall?: (
request: Parameters<App["callServerTool"]>[0],
result: Awaited<ReturnType<App["callServerTool"]>>,
) => Awaited<ReturnType<App["callServerTool"]>>;
beforeCall?: (request: Parameters<App["callServerTool"]>[0]) => void;
};
async function createAdapter(options: AdapterOptions = {}) {
const stateDirectory = await temporaryDirectory();
const stagingDirectory = await temporaryDirectory();
const workbench = createServerWorkbenchStore({ stateDirectory });
const store = new SequenceWorkbenchUploadStore(workbench, {
createStagingDirectory: async () => stagingDirectory,
});
stores.push(store);
const requests: Array<Parameters<App["callServerTool"]>[0]> = [];
const callServerTool = vi.fn(
async (request: Parameters<App["callServerTool"]>[0]) => {
requests.push(request);
options.beforeCall?.(request);
const input = request.arguments ?? {};
const structuredContent =
request.name === SEQUENCE_PERSIST_WORKBENCH_PAYLOAD_TOOL_NAME
? await store.persistOneShot(input)
: request.name === SEQUENCE_BEGIN_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME
? await store.begin(input)
: request.name === SEQUENCE_APPEND_WORKBENCH_PAYLOAD_CHUNK_TOOL_NAME
? await store.append(input)
: request.name === SEQUENCE_FINISH_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME
? await store.finish(input)
: request.name === SEQUENCE_ABORT_WORKBENCH_PAYLOAD_UPLOAD_TOOL_NAME
? await store.abort(input)
: (() => {
throw new Error(`Unexpected tool ${request.name}`);
})();
const response = { content: [], structuredContent } as Awaited<
ReturnType<App["callServerTool"]>
>;
return options.afterCall?.(request, response) ?? response;
},
);
return {
app: { callServerTool } as unknown as Pick<App, "callServerTool">,
requests,
store,
workbench,
};
}
function validSession(artifactBytes: number): string {
return JSON.stringify({
artifacts: [
{
content: `>${"A".repeat(artifactBytes - 1)}`,
createdAt: 1,
format: "fasta",
id: randomUUID(),
mediaType: "text/x-fasta",
name: "large.fasta",
provenance: {
engine: "test",
parameters: {},
sourceRevision: 0,
},
},
],
createdAt: 1,
dirty: false,
jobs: [],
revision: 0,
schemaVersion: 1,
source: { fileName: "demo.fasta", format: "fasta" },
tracks: [],
view: {
mode: "sequence",
sequence: {
geneticCodeId: 1,
layout: "linear",
orientation: "forward",
paletteId: "neutral",
selectedFeatureId: null,
selectedRecordId: "record-1",
selection: null,
showFeatures: true,
showQuality: true,
showTranslation: true,
synchronizedViews: true,
viewport: null,
wrapWidth: 60,
},
},
});
}
async function temporaryDirectory(): Promise<string> {
const directory = await mkdtemp(
path.join(os.tmpdir(), "sequence-persistence-client-test-"),
);
temporaryDirectories.push(directory);
return directory;
}
SHA-256: 4c74da304da2ee2c37c054a091b9a33aeb8833abaf6eb9dfe708fd061b49c7ac