← Files NGS Analysis WorkbenchARCHIVED FILE
tests_js/workflow-catalog.test.ts
10.4 KB · Sep 30, 2026 · 23:20 UTC
import assert from "node:assert/strict";
import { after, before, test } from "node:test";
import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { createServer, type ViteDevServer } from "vite";
let server: ViteDevServer;
let listPipelines: (client: {
call: (name: string, parameters: Record<string, unknown>) => Promise<unknown>;
}) => Promise<Array<Record<string, unknown>>>;
let listComputeTargets: (client: {
call: (name: string, parameters: Record<string, unknown>) => Promise<unknown>;
}) => Promise<Array<Record<string, unknown>>>;
let PipelineCatalog: (properties: Record<string, unknown>) => unknown;
let ComputeTargetCatalog: (properties: Record<string, unknown>) => unknown;
let NgsWorkbenchSurface: (properties: Record<string, unknown>) => unknown;
let useNgsWorkbench: typeof import("../src/hooks/useNgsWorkbench.ts").useNgsWorkbench;
let groupRunLineages: (runs: Array<Record<string, unknown>>) => Array<{
id: string;
latest: Record<string, unknown>;
runs: Array<Record<string, unknown>>;
}>;
const payload = {
workflows: [
{
workflow_id: "rnaseq",
name: "Included Nextflow RNA-seq",
engine: "nextflow",
description: "Curated RNA-seq alignment and quantification.",
catalog: "bundled",
collection: "nf-core",
source: { kind: "remote", workflow: "nf-core/rnaseq", revision: "3.26.0" },
},
{
workflow_id: "oai_bulk_rnaseq_counts_qc",
name: "Included Snakemake RNA-seq",
engine: "snakemake",
description: "Bundled RNA-seq quantification.",
catalog: "bundled",
source: {
kind: "local",
root: "/private/plugin/workflows/rnaseq",
entrypoint: "workflow/Snakefile",
source_sha256: `sha256:${"c".repeat(64)}`,
},
},
{
workflow_id: "lab_rna_quant",
name: "Saved laboratory RNA-seq",
engine: "snakemake",
description: null,
catalog: "user",
source: {
kind: "local",
root: "/Users/private/workflows/lab_rna_quant",
entrypoint: "Snakefile",
source_sha256: `sha256:${"a".repeat(64)}`,
},
},
],
};
const targetPayload = {
count: 2,
targets: [
{
target_id: "local",
title: "This computer",
provider: "ngs-analysis-workbench",
controller_transport: "local_process",
executor: "local_process",
workspace_access: "local_filesystem",
description: "Run the workflow controller on this computer.",
},
{
target_id: "research-slurm",
title: "Research Slurm",
provider: "ngs-compute",
controller_transport: "ssh",
executor: "slurm",
workspace_access: "remote_filesystem",
description: "Run a workflow controller through SSH.",
workspace_root: "/shared/rosalind",
executor_configuration: { partition: "genomics" },
},
],
};
before(async () => {
server = await createServer({
appType: "custom",
configFile: false,
logLevel: "silent",
server: { middlewareMode: true },
});
({ listComputeTargets, listPipelines } = await server.ssrLoadModule("/src/mcp/ngs.ts"));
({ PipelineCatalog } = await server.ssrLoadModule("/src/components/PipelineCatalog.tsx"));
({ ComputeTargetCatalog } = await server.ssrLoadModule("/src/components/ComputeTargetCatalog.tsx"));
({ NgsWorkbenchSurface } = await server.ssrLoadModule("/src/App.tsx"));
({ useNgsWorkbench } = await server.ssrLoadModule("/src/hooks/useNgsWorkbench.ts"));
({ groupRunLineages } = await server.ssrLoadModule("/src/components/RunHistory.tsx"));
});
after(async () => {
await server?.close();
});
async function workflows() {
return listPipelines({
async call(name, parameters) {
assert.equal(name, "list_workflows");
assert.deepEqual(parameters, {});
return payload;
},
});
}
async function computeTargets() {
return listComputeTargets({
async call(name, parameters) {
assert.equal(name, "list_compute_target_summaries");
assert.deepEqual(parameters, {});
return targetPayload;
},
});
}
test("maps public workflow identity and nested source only at the UI boundary", async () => {
const entries = await workflows();
const saved = entries.find((workflow) => workflow.id === "lab_rna_quant");
assert.equal(saved?.catalog, "saved");
assert.equal(saved?.sourceKind, "local");
assert.equal(saved?.description, "");
assert.equal(saved?.entrypoint, "Snakefile");
assert.equal(saved?.workflow, "/Users/private/workflows/lab_rna_quant");
assert.equal(saved?.sourceSha256, payload.workflows[2].source.source_sha256);
assert.equal(entries[0]?.revision, "3.26.0");
});
test("groups executions by first run and selects the greatest workflow attempt", () => {
const runs = [
{
registry_run_id: "latest", run_id: "run-b", first_run_id: "run-a",
attempt_number: 2, binding: "nextflow", created_at_ms: 20,
updated_at_ms: 30,
},
{
registry_run_id: "older", run_id: "run-a", first_run_id: "run-a",
attempt_number: 1, binding: "nextflow", created_at_ms: 10,
updated_at_ms: 100,
},
{ registry_run_id: "unlinked", run_id: "run-c", first_run_id: "run-c",
attempt_number: 1, binding: "nextflow", created_at_ms: 15, updated_at_ms: 40 },
];
const grouped = groupRunLineages(runs);
assert.equal(grouped.length, 2);
assert.deepEqual(grouped[0].runs.map((run) => run.registry_run_id), ["older", "latest"]);
assert.equal(grouped[0].latest.registry_run_id, "latest");
assert.equal(grouped[1].id, "run-c");
});
test("loads drawer history using only the first run ID", async () => {
const attempts = [{ registry_run_id: "first" }, { registry_run_id: "latest" }];
const client = {
async call(name: string, parameters: Record<string, unknown>) {
assert.equal(name, "list_ngs_runs");
assert.deepEqual(parameters, { first_run_id: "run-a", limit: 200 });
return { ok: true, runs: attempts };
},
} as Parameters<typeof useNgsWorkbench>[0];
let loadRunLineage: ReturnType<typeof useNgsWorkbench>["loadRunLineage"];
function Harness() {
({ loadRunLineage } = useNgsWorkbench(client));
return null;
}
renderToStaticMarkup(createElement(Harness));
const selected = {
registry_run_id: "latest", run_id: "run-b", first_run_id: "run-a", binding: "nextflow",
} as Parameters<typeof loadRunLineage>[0];
assert.deepEqual(await loadRunLineage!(selected), attempts);
});
test("shows user workflows first and keeps engine metadata reviewable", async () => {
const html = renderToStaticMarkup(createElement(PipelineCatalog, {
pipelines: await workflows(),
loading: false,
}));
assert.ok(html.indexOf("Saved laboratory RNA-seq") < html.indexOf("Included Nextflow RNA-seq"));
assert.ok(html.includes("Included Nextflow RNA-seq"));
assert.ok(html.includes("Included Snakemake RNA-seq"));
assert.ok(!html.includes("/private/plugin"));
assert.ok(!html.includes("/Users/private"));
});
test("distinguishes the loading catalog from an actually empty catalog", () => {
const loading = renderToStaticMarkup(createElement(PipelineCatalog, {
pipelines: [],
loading: true,
}));
const empty = renderToStaticMarkup(createElement(PipelineCatalog, {
pipelines: [],
loading: false,
}));
assert.ok(loading.includes('role="status"'));
assert.ok(loading.includes("Loading pipelines"));
assert.ok(!loading.includes("No pipelines are available"));
assert.ok(empty.includes("No pipelines are available"));
assert.ok(!empty.includes('role="status"'));
});
test("makes Pipelines a primary page while keeping the Runs page available", async () => {
const workbench = {
connected: true,
displayMode: "inline",
catalogLoading: false,
pipelines: await workflows(),
loadCatalog: async () => undefined,
dismissError: () => undefined,
};
const html = renderToStaticMarkup(createElement(NgsWorkbenchSurface, {
initialPage: "workflows",
workbench,
}));
assert.match(html, /<button[^>]*>Runs<\/button>/);
assert.match(html, /<button[^>]*aria-current="page"[^>]*>Pipelines<\/button>/);
assert.ok(html.includes("Saved laboratory RNA-seq"));
assert.ok(html.includes("Included Nextflow RNA-seq"));
});
test("makes Compute a primary page and lists configuration without readiness claims", async () => {
const targets = await computeTargets();
const html = renderToStaticMarkup(createElement(NgsWorkbenchSurface, {
initialPage: "compute",
workbench: {
connected: true,
displayMode: "inline",
computeTargets: targets,
targetsError: undefined,
targetsLoading: false,
loadComputeTargets: async () => undefined,
dismissError: () => undefined,
},
}));
assert.match(html, /<button[^>]*>Runs<\/button>/);
assert.match(html, /<button[^>]*>Pipelines<\/button>/);
assert.match(html, /<button[^>]*aria-current="page"[^>]*>Compute<\/button>/);
assert.ok(html.includes("This computer"));
assert.ok(html.includes("Research Slurm"));
assert.ok(html.includes("Configured does not mean workflow-ready"));
assert.ok(html.includes('aria-haspopup="dialog"'));
assert.ok(!html.includes("Ready"));
});
test("does not present an unavailable target inventory as empty", () => {
const html = renderToStaticMarkup(createElement(ComputeTargetCatalog, {
targets: undefined,
loading: false,
error: "Could not load compute targets.",
}));
assert.ok(html.includes('role="alert"'));
assert.ok(html.includes("Could not load compute targets."));
assert.ok(!html.includes("No compute targets are available"));
});
test("keeps target details configuration-only", async () => {
const html = renderToStaticMarkup(createElement(ComputeTargetCatalog, {
targets: await computeTargets(),
loading: false,
}));
assert.ok(html.includes("Local process"));
assert.equal(html.match(/Local process/g)?.length, 1);
assert.ok(html.includes("Slurm"));
assert.ok(!html.includes("config_hash"));
assert.ok(!html.includes("host_access"));
});
test("does not present a disconnected catalog as an empty pipeline library", () => {
const html = renderToStaticMarkup(createElement(NgsWorkbenchSurface, {
initialPage: "workflows",
workbench: {
connected: false,
displayMode: "inline",
catalogLoading: false,
pipelines: [],
error: "The MCP host is unavailable.",
loadCatalog: async () => undefined,
dismissError: () => undefined,
},
}));
assert.ok(html.includes("pipeline library is unavailable"));
assert.ok(!html.includes("No pipelines are available"));
});
SHA-256: 261cd55607c097df3469fb6287b53ef76919ad7086b15bfb2608533c7e7099e8