← Files Biological Sequence & Alignment ViewerARCHIVED FILE
e2e/installed-host-fault-injection.mjs
2.97 KB · Sep 30, 2026 · 23:01 UTC
import fs from "node:fs";
import { syncBuiltinESMExports } from "node:module";
const scenario = process.env.SEQUENCE_VIEWER_E2E_FAULT_SCENARIO;
const allowedScenarios = new Set([
"accession-missing",
"deterministic-subset-failure",
"malformed-database-response",
"network-unavailable",
"output-quota-failure",
"oversized-source-or-analysis-budget",
"payload-format-mismatch",
"rate-limit",
]);
if (!allowedScenarios.has(scenario)) {
throw new Error("Invalid Sequence Viewer qualification fault scenario.");
}
const originalFetch = globalThis.fetch.bind(globalThis);
globalThis.fetch = async (input, init) => {
const url = new URL(
typeof input === "string" || input instanceof URL ? input : input.url,
);
if (scenario === "network-unavailable") {
throw new TypeError("Injected qualification network outage.");
}
if (scenario === "rate-limit") {
return response("rate limited\n", {
headers: { "retry-after": "60 seconds" },
status: 429,
});
}
if (scenario === "malformed-database-response") {
return response("<!doctype html><title>Error</title>database unavailable\n", {
headers: { "content-type": "text/html; charset=utf-8" },
});
}
if (scenario === "accession-missing") {
return response("NC_001416.1 was not found\n", { status: 404 });
}
if (
scenario === "payload-format-mismatch" &&
url.hostname === "www.ebi.ac.uk"
) {
return response(
[
"run_accession\tfastq_ftp\tfastq_md5\tfastq_bytes",
"DRR000000\tftp.sra.ebi.ac.uk/vol1/fastq/DRR037/DRR037765/DRR037765.fastq.gz\t81735432a6f578b332aae58cdbd95231\t127526",
"",
].join("\n"),
{ headers: { "content-type": "text/tab-separated-values" } },
);
}
if (scenario === "oversized-source-or-analysis-budget") {
return response("oversized\n", {
headers: { "content-length": String(2 * 1_024 * 1_024 + 1) },
});
}
return await originalFetch(input, init);
};
if (scenario === "deterministic-subset-failure") {
const OriginalTextDecoder = globalThis.TextDecoder;
globalThis.TextDecoder = class QualificationTextDecoder extends OriginalTextDecoder {
decode(input, options) {
const decoded = super.decode(input, options);
if (!decoded.startsWith("@DRR037765.")) return decoded;
const lines = decoded.trimEnd().split(/\r?\n/u);
return `${lines.slice(0, 499 * 4).join("\n")}\n`;
}
};
}
if (scenario === "output-quota-failure") {
const originalStatfs = fs.promises.statfs.bind(fs.promises);
fs.promises.statfs = async (...args) => {
const fileSystem = await originalStatfs(...args);
return {
...fileSystem,
bavail: typeof fileSystem.bavail === "bigint" ? 0n : 0,
bfree: typeof fileSystem.bfree === "bigint" ? 0n : 0,
};
};
syncBuiltinESMExports();
}
function response(body, { headers = {}, status = 200 } = {}) {
return new Response(body, {
headers: {
"content-type": "text/plain; charset=utf-8",
...headers,
},
status,
});
}
SHA-256: 41db44cc0f821acb2ebacfc5b2f7995e4a89ec31aac95c3a9eda6047fca91a84