← Files Biological Sequence & Alignment ViewerARCHIVED FILE
src/sequence/chromatogram-query.test.ts
15.3 KB · Sep 30, 2026 · 23:01 UTC
import { describe, expect, it } from "vitest";
import { SEQUENCE_VIEWER_LIMITS, utf8ByteLength } from "../runtime-contract";
import {
makeSyntheticAbif,
makeSyntheticScf,
SYNTHETIC_TRACE_CHANNELS,
} from "./__fixtures__/chromatogram";
import {
queryChromatogram,
type ChromatogramQueryRequest,
} from "./chromatogram-query";
import { parseAbifRecord, parseScfRecord } from "./formats/chromatogram";
import type {
SequenceChromatogram,
SequenceDocument,
SequenceRecord,
} from "./types";
describe("bounded chromatogram queries", () => {
it("returns exact Phred calls and original source-coordinate midpoint spans", () => {
const record = abifRecord();
const original = structuredClone(record);
const response = queryRecord(record, { start: 2, end: 4 });
expect(response).toMatchObject({
coordinateSystem: {
basis: 1,
end: "inclusive",
orientation: "original-forward",
referenceRecordId: record.id,
sampleBasis: 0,
sampleEnd: "inclusive",
sampleSpace: "trace-sample",
space: "sequence",
strand: "+",
},
nextCursor: null,
page: { count: 11, offset: 0, totalCount: 11 },
result: {
baseCalls: [
{ base: "C", coordinate: 2, peakSample: 5, quality: 32 },
{ base: "G", coordinate: 3, peakSample: 8, quality: 33 },
{ base: "T", coordinate: 4, peakSample: 11, quality: 34 },
],
channelNames: ["A", "C", "G", "T"],
downsampled: false,
format: "abif",
qualityEncoding: "phred",
sampleCount: 16,
sampleEnd: 13,
sampleStart: 3,
start: 2,
end: 4,
windowSampleCount: 11,
},
target: "chromatogram",
truncated: false,
});
expect(response.items.map(({ sample }) => sample)).toEqual([
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
]);
expect(response.result.qualitySemantics).toContain(
"Q = -10 log10(error probability)",
);
expect(response.result.spanSemantics).toContain(
"Adjacent base windows may overlap",
);
expect(record).toEqual(original);
});
it("preserves SCF candidate confidence bytes without calling them Phred scores", () => {
const record = parseScfRecord({ bytes: makeSyntheticScf() }).record;
const response = queryRecord(record);
expect(response.result).toMatchObject({
format: "scf",
qualityEncoding: "source-confidence",
sampleStart: 0,
sampleEnd: 15,
});
expect(response.result.baseCalls[1]).toEqual({
base: "C",
baseConfidences: { A: 1, C: 32, G: 3, T: 4 },
coordinate: 2,
peakSample: 5,
quality: 32,
});
expect(response.result.baseCalls[4]).toEqual({
base: "N",
baseConfidences: { A: 1, C: 2, G: 3, T: 4 },
coordinate: 5,
peakSample: 14,
quality: null,
});
expect(response.result.qualitySemantics).toContain(
"not assumed to be calibrated Phred",
);
expect(response.result.baseConfidenceSemantics).toContain(
"per-candidate A/C/G/T",
);
});
it("does not invent quality values or infer an unspecified encoding", () => {
const missing = parseAbifRecord({
bytes: makeSyntheticAbif({ quality: null }),
}).record;
const response = queryRecord(missing);
expect(response.result.qualityEncoding).toBeNull();
expect(
response.result.baseCalls.every(({ quality }) => quality === null),
).toBe(true);
expect(response.result.qualitySemantics).toContain(
"No called-base confidence values",
);
const unlabelled = abifRecord();
delete requireTrace(unlabelled).qualityEncoding;
const unknown = queryRecord(unlabelled);
expect(unknown.result.qualityEncoding).toBeNull();
expect(unknown.result.baseCalls[0].quality).toBe(31);
expect(unknown.result.qualitySemantics).toContain("unspecified encoding");
});
it("keeps signed signal amplitudes unchanged", () => {
const channels = {
...SYNTHETIC_TRACE_CHANNELS,
A: [-3, -1, ...SYNTHETIC_TRACE_CHANNELS.A.slice(2)],
};
const record = parseAbifRecord({
bytes: makeSyntheticAbif({ channels }),
}).record;
expect(queryRecord(record, { end: 1, start: 1 }).items.slice(0, 3)).toEqual(
[
{ sample: 0, A: -3, C: 0, G: 0, T: 0 },
{ sample: 1, A: -1, C: 0, G: 0, T: 0 },
{ sample: 2, A: 40, C: 0, G: 0, T: 0 },
],
);
});
it("pages all four exact channels without gaps, duplicate samples, or downsampling", () => {
const record = longTraceRecord();
const trace = requireTrace(record);
const first = queryRecord(record, { end: 5, limit: 500, start: 1 });
const pages = [first];
while (pages.at(-1)?.nextCursor != null && pages.length < 5) {
pages.push(
queryRecord(record, {
cursor: pages.at(-1)?.nextCursor ?? undefined,
end: 5,
limit: 500,
start: 1,
}),
);
}
expect(pages.map(({ page }) => page)).toEqual([
{ count: 500, offset: 0, totalCount: 1_205 },
{ count: 500, offset: 500, totalCount: 1_205 },
{ count: 205, offset: 1_000, totalCount: 1_205 },
]);
expect(pages.map(({ truncated }) => truncated)).toEqual([
true,
true,
false,
]);
expect(pages.at(-1)?.nextCursor).toBeNull();
const samples = pages.flatMap(({ items }) => items);
expect(samples).toEqual(
trace.channels.A.map((A, sample) => ({
sample,
A,
C: trace.channels.C[sample],
G: trace.channels.G[sample],
T: trace.channels.T[sample],
})),
);
expect(pages.every(({ result }) => result.downsampled === false)).toBe(
true,
);
expect(pages[1].result.baseCalls).toEqual(first.result.baseCalls);
expect(first.result).not.toHaveProperty("channels");
});
it("reads only the requested sample page rather than traversing the full trace", () => {
const record = longTraceRecord();
Object.defineProperty(requireTrace(record).channels.A, 10, {
get: () => {
throw new Error("Unrequested source sample was read.");
},
});
const response = queryRecord(record, { end: 5, limit: 10, start: 1 });
expect(response.page).toEqual({ count: 10, offset: 0, totalCount: 1_205 });
expect(response.items.at(-1)?.sample).toBe(9);
});
it("accepts exactly 100 called bases and rejects a larger source window", () => {
const channels = Array.from({ length: 304 }, () => 0);
const record = parseAbifRecord({
bytes: makeSyntheticAbif({
channels: { A: channels, C: channels, G: channels, T: channels },
peaks: Array.from({ length: 101 }, (_, index) => index * 3),
quality: null,
sequence: "A".repeat(101),
}),
}).record;
expect(
queryRecord(record, { start: 1, end: 100 }).result.baseCalls,
).toHaveLength(100);
expect(() => queryRecord(record, { start: 1, end: 101 })).toThrow(
"limited to 100 called bases",
);
});
it.each([
[0, 1],
[1, 6],
[4, 3],
[1.5, 2],
[Number.NaN, 2],
[1, Number.POSITIVE_INFINITY],
])("rejects invalid source range %s-%s", (start, end) => {
expect(() => queryRecord(abifRecord(), { start, end })).toThrow(
"1-based inclusive integer coordinates",
);
});
it.each([0, 501, 1.5, Number.NaN, Number.POSITIVE_INFINITY])(
"rejects invalid sample limit %s",
(limit) => {
expect(() =>
queryRecord(abifRecord(), { start: 1, end: 5, limit }),
).toThrow("between 1 and 500");
},
);
it("resolves stable IDs and unique aliases, rejecting absent or ambiguous records", () => {
const first = { ...abifRecord(), id: "first", sourceLabel: "shared.ab1" };
const second = {
...abifRecord(),
id: "second",
sourceLabel: "shared.ab1",
description: "Unique trace",
};
const query = (record?: string) =>
queryChromatogram({
document: traceDocument([first, second]),
request: { end: 5, record, start: 1, target: "chromatogram" },
selectedRecordId: second.id,
});
expect(query().result.recordId).toBe("second");
expect(query("first").result.recordId).toBe("first");
expect(query(" unique TRACE ").result.recordId).toBe("second");
expect(() => query("absent")).toThrow("No sequence record matched absent");
expect(() => query("shared.ab1")).toThrow("More than one record matched");
});
it("binds cursors to the record, base range, and source sample span", () => {
const record = {
...abifRecord(),
id: "trace|with.% unicode Ω".repeat(100),
};
const first = queryRecord(record, { end: 5, limit: 2, start: 1 });
const cursor = first.nextCursor ?? "";
expect(cursor.length).toBeLessThan(1_024);
expect(
queryRecord(record, { cursor, end: 5, limit: 2, start: 1 }).items[0]
.sample,
).toBe(2);
expect(() => queryRecord(record, { cursor, end: 5, start: 2 })).toThrow(
"invalid for this record and base window",
);
expect(() =>
queryRecord({ ...record, id: "another" }, { cursor, end: 5, start: 1 }),
).toThrow("invalid for this record and base window");
expect(() =>
queryRecord({ ...record }, { cursor, end: 5, start: 1 }),
).toThrow("invalid for this record and base window");
expect(() =>
queryRecord(record, { cursor: "q1.records.2", end: 5, start: 1 }),
).toThrow("invalid for this record and base window");
expect(() => queryRecord(record, { cursor: "", end: 5, start: 1 })).toThrow(
"invalid for this record and base window",
);
const partial = queryRecord(record, { end: 4, limit: 2, start: 1 });
requireTrace(record).peakLocations[4] = 12;
expect(() =>
queryRecord(record, {
cursor: partial.nextCursor ?? "",
end: 4,
start: 1,
}),
).toThrow("invalid for this record and base window");
});
it.each(["", "-1", "01", "1.5", "16", "999", "9007199254740993"])(
"rejects malformed or exhausted cursor offset %j",
(offset) => {
const record = abifRecord();
const first = queryRecord(record, { end: 5, limit: 2, start: 1 });
const cursor = (first.nextCursor ?? "").replace(/\|\d+$/u, `|${offset}`);
expect(() => queryRecord(record, { cursor, end: 5, start: 1 })).toThrow(
"invalid or out-of-range sample offset",
);
},
);
it("reports absent trace data without synthesizing a chromatogram", () => {
const { chromatogram: _trace, ...record } = abifRecord();
expect(() => queryRecord(record)).toThrow("has no chromatogram trace data");
});
it.each<[string, (trace: SequenceChromatogram) => void]>([
[
"empty samples",
(trace) => {
trace.sampleCount = 0;
},
],
[
"mismatched channel",
(trace) => {
trace.channels.C.pop();
},
],
[
"missing peaks",
(trace) => {
trace.peakLocations.pop();
},
],
[
"decreasing peak",
(trace) => {
trace.peakLocations[2] = 1;
},
],
[
"out-of-range peak",
(trace) => {
trace.peakLocations[4] = 16;
},
],
[
"non-finite signal",
(trace) => {
trace.channels.A[3] = Number.NaN;
},
],
[
"mismatched quality",
(trace) => {
trace.quality = [32];
},
],
[
"invalid quality",
(trace) => {
trace.quality = [256, 32, 33, 34, 0];
},
],
])("rejects malformed source data: %s", (_, mutate) => {
const record = abifRecord();
mutate(requireTrace(record));
expect(() => queryRecord(record)).toThrow("Malformed chromatogram");
});
it("rejects candidate confidence arrays that are missing or have invalid bytes", () => {
const record = parseScfRecord({ bytes: makeSyntheticScf() }).record;
const confidences = requireTrace(record).baseConfidences;
if (confidences == null)
throw new Error("Fixture must contain candidate confidences.");
confidences.A[1] = -1;
expect(() => queryRecord(record)).toThrow(
"invalid candidate confidence for base 2",
);
confidences.A.pop();
expect(() => queryRecord(record)).toThrow(
"confidence values must align with called bases",
);
});
it("shrinks pages to the installed-host completion budget and advances by returned samples", () => {
const record = { ...longTraceRecord(), id: "x".repeat(115_000) };
const first = queryRecord(record, { end: 5, limit: 500, start: 1 });
expect(first.page.count).toBeGreaterThan(0);
expect(first.page.count).toBeLessThan(500);
expect(first.nextCursor).toMatch(
new RegExp(`\\|${first.page.count}$`, "u"),
);
expect(
utf8ByteLength(JSON.stringify({ query: first })),
).toBeLessThanOrEqual(
SEQUENCE_VIEWER_LIMITS.command.maxCompletionStateBytes,
);
const completionRequest = {
arguments: {
applied: true,
commandId: "11111111-1111-4111-8111-111111111111",
message: "Returned chromatogram from the live Sequence viewer.",
sessionId: "22222222-2222-4222-8222-222222222222",
state: { query: first },
},
name: "sequence.complete_viewer_command",
};
expect(
utf8ByteLength(
JSON.stringify({
id: "sequence-viewer-completion",
jsonrpc: "2.0",
method: "tools/call",
params: completionRequest,
}),
),
).toBeLessThanOrEqual(
SEQUENCE_VIEWER_LIMITS.command.maxCompletionRequestBytes,
);
const second = queryRecord(record, {
cursor: first.nextCursor ?? undefined,
end: 5,
limit: 500,
start: 1,
});
expect(second.items[0].sample).toBe(first.page.count);
expect(second.page.offset).toBe(first.page.count);
});
it("rejects oversized metadata rather than returning an empty, stuck page", () => {
const record = { ...abifRecord(), id: "x".repeat(130_000) };
expect(() => queryRecord(record)).toThrow(
"installed-host completion budget",
);
});
});
function abifRecord(): SequenceRecord {
return parseAbifRecord({ bytes: makeSyntheticAbif() }).record;
}
function longTraceRecord(): SequenceRecord {
const A = Array.from({ length: 1_205 }, (_, index) => index);
return parseAbifRecord({
bytes: makeSyntheticAbif({
channels: {
A,
C: A.map((value) => value * 2),
G: A.map((value) => 500 - value),
T: A.map((value) => value % 11),
},
peaks: [2, 202, 602, 902, 1_202],
}),
}).record;
}
function requireTrace(record: SequenceRecord): SequenceChromatogram {
if (record.chromatogram == null)
throw new Error("Fixture must contain a chromatogram.");
return record.chromatogram;
}
function queryRecord(
record: SequenceRecord,
request: Omit<ChromatogramQueryRequest, "target"> = {
end: record.length,
start: 1,
},
) {
return queryChromatogram({
document: traceDocument([record]),
request: { ...request, target: "chromatogram" },
selectedRecordId: record.id,
});
}
function traceDocument(records: Array<SequenceRecord>): SequenceDocument {
return {
classification: {
alignment: null,
confidence: "high",
evidence: [
"Synthetic chromatogram query fixture; not measured biological data.",
],
kind: "chromatogram",
molecule: "dna",
suggestedViewer: "sequence",
},
format: records[0]?.chromatogram?.format ?? "abif",
kind: "chromatogram",
records,
warnings: [],
};
}
SHA-256: 36660201c1e2578a90cfa69c74fd4128f91258a57030bab50cfcf04e27e414f8