repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/ultra/__tests__/interp.test.ts
Rawimport { describe, expect, it } from "vitest";
import {
type InterpContext,
resolveOver,
resolveOverWithSources,
resolveSelector,
resolveTemplate,
resolveTemplateWithSources,
SpecValidationError,
} from "../interp.ts";
// One shared null-containing fixture drives the "line-197" property:
// the same `null` is RETAINED positionally for plain `{r.results}` but
// DROPPED under flatten `{r.results[].findings[]}` and `{... | where real}`.
// It also carries a missing-`findings` row and a missing-`real` row so the
// "missing FIELD" skip is exercised for both flatten and where.
function fixture(): InterpContext {
return {
args: { topic: "security", count: 3, tags: ["a", "b"] },
item: "performance",
results: {
r: [
{ real: true, findings: ["a1", "a2"] }, // index 0: kept, 2 findings
null, // index 1: the retained/dropped null
{ real: false, findings: ["b1"] }, // index 2: real falsy
{ real: true }, // index 3: truthy but MISSING findings
{ findings: ["c1"] }, // index 4: has findings but MISSING real
],
},
failures: {
r: [
{ code: "transport", retryable: true, message: "rate limited" },
{ code: "auth", retryable: false, message: "missing key" },
],
},
};
}
describe("resolveTemplate", () => {
it("substitutes {args.NAME}", () => {
expect(resolveTemplate("topic is {args.topic}", fixture())).toBe(
"topic is security",
);
});
it("renders {item} string verbatim", () => {
expect(resolveTemplate("do {item}", fixture())).toBe("do performance");
});
it("renders {item} object as compact JSON", () => {
const ctx: InterpContext = { item: { file: "a.ts", line: 3 } };
expect(resolveTemplate("Finding: {item}", ctx)).toBe(
'Finding: {"file":"a.ts","line":3}',
);
});
it("renders a non-string arg (number) as JSON", () => {
expect(resolveTemplate("n={args.count}", fixture())).toBe("n=3");
});
it("substitutes multiple tokens in one template", () => {
expect(resolveTemplate("{args.topic}/{item}", fixture())).toBe(
"security/performance",
);
});
it("renders prior phase failures", () => {
expect(resolveTemplate("repair {r.failures}", fixture())).toContain(
'"code":"transport"',
);
});
it("throws SpecValidationError on an unknown token", () => {
expect(() => resolveTemplate("x {foo.bar | map x} y", fixture())).toThrow(
SpecValidationError,
);
});
});
describe("source attribution", () => {
it("preserves producer positions through flattening and filtering", () => {
const flattened = resolveOverWithSources(
"{r.results[].findings[]}",
fixture(),
);
expect(flattened).toEqual([
{ value: "a1", sources: [{ phase: "r", index: 0 }] },
{ value: "a2", sources: [{ phase: "r", index: 0 }] },
{ value: "b1", sources: [{ phase: "r", index: 2 }] },
{ value: "c1", sources: [{ phase: "r", index: 4 }] },
]);
const filtered = resolveOverWithSources(
"{r.results | where real}",
fixture(),
);
expect(filtered.map((item) => item.sources)).toEqual([
[{ phase: "r", index: 0 }],
[{ phase: "r", index: 3 }],
]);
});
it("unions member producers for grouped fanout items", () => {
const grouped = resolveOverWithSources("{r.results | groupBy source}", {
results: {
r: [
{ source: "sessions", id: 1 },
{ source: "repository", id: 2 },
{ source: "sessions", id: 3 },
],
},
});
expect(grouped[0].sources).toEqual([
{ phase: "r", index: 0 },
{ phase: "r", index: 2 },
]);
expect(grouped[1].sources).toEqual([{ phase: "r", index: 1 }]);
});
it("reports sources to prompt rendering without changing ordinary values", () => {
const rendered = resolveTemplateWithSources(
"topic={args.topic}; prior={r.results}; item={item}",
{
...fixture(),
itemSources: [{ phase: "r", index: 4 }],
},
(content, sources) =>
`${content}<${sources.map(({ phase, index }) => `${phase}#${index}`).join(",")}>`,
);
expect(rendered).toContain("topic=security<>");
expect(rendered).toContain("<r#0,r#2,r#3,r#4>");
expect(rendered).toContain("item=performance<r#4>");
});
});
describe("resolveSelector", () => {
it("retains nulls positionally for plain {PHASE.results}", () => {
const out = resolveSelector("{r.results}", fixture());
expect(Array.isArray(out)).toBe(true);
expect(out).toHaveLength(5);
expect((out as unknown[])[1]).toBeNull();
expect((out as unknown[])[0]).toEqual({
real: true,
findings: ["a1", "a2"],
});
});
it("flatten {PHASE.results[].FIELD[]} collects arrays and skips null elements + missing FIELDs", () => {
// null (idx1) skipped; idx3 missing findings skipped; rest flattened.
const out = resolveSelector("{r.results[].findings[]}", fixture());
expect(out).toEqual(["a1", "a2", "b1", "c1"]);
});
it("{EXPR | where FIELD} keeps truthy FIELD, skips null elements + falsy + missing FIELD", () => {
// idx0 real:true kept; idx1 null skipped; idx2 real:false skipped;
// idx3 real:true kept; idx4 missing real skipped.
const out = resolveSelector("{r.results | where real}", fixture());
expect(out).toEqual([
{ real: true, findings: ["a1", "a2"] },
{ real: true },
]);
});
it("where supports exact string equality", () => {
const ctx: InterpContext = {
results: {
r: [
{ source: "sessions", id: 1 },
{ source: "repository", id: 2 },
{ source: "sessions", id: 3 },
{ source: "a|b", id: 4 },
{ source: 7, id: 5 },
{ id: 6 },
],
},
};
expect(
resolveSelector('{r.results | where source == "sessions"}', ctx),
).toEqual([
{ source: "sessions", id: 1 },
{ source: "sessions", id: 3 },
]);
expect(resolveSelector('{r.results | where source == "a|b"}', ctx)).toEqual(
[{ source: "a|b", id: 4 }],
);
});
it("groupBy groups string fields in first-seen order", () => {
const ctx: InterpContext = {
results: {
r: [
{ source: "sessions", id: 1 },
{ source: "repository", id: 2 },
{ source: "sessions", id: 3 },
{ source: 7, id: 4 },
{ id: 5 },
],
},
};
expect(resolveSelector("{r.results | groupBy source}", ctx)).toEqual([
{
key: "sessions",
items: [
{ source: "sessions", id: 1 },
{ source: "sessions", id: 3 },
],
},
{ key: "repository", items: [{ source: "repository", id: 2 }] },
]);
});
it("where equality is strict, case-sensitive, and supports empty values", () => {
const ctx: InterpContext = {
results: {
r: [
{ source: "sessions", id: 1 },
{ source: "Sessions", id: 2 },
{ source: "", id: 3 },
{ source: false, id: 4 },
{ source: 0, id: 5 },
{ source: null, id: 6 },
{ id: 7 },
],
},
};
expect(
resolveSelector('{r.results | where source == "Sessions"}', ctx),
).toEqual([{ source: "Sessions", id: 2 }]);
expect(resolveSelector('{r.results | where source == ""}', ctx)).toEqual([
{ source: "", id: 3 },
]);
expect(resolveSelector('{r.results | where source == "0"}', ctx)).toEqual(
[],
);
});
it("where equality accepts surrounding whitespace", () => {
const ctx: InterpContext = {
results: { r: [{ source: "sessions" }, { source: "repository" }] },
};
expect(
resolveSelector('{r.results | where source == "sessions"}', ctx),
).toEqual([{ source: "sessions" }]);
});
it("where equality supports escaped strings and pipes", () => {
const ctx: InterpContext = {
results: {
r: [
{ source: 'a"b', id: 1 },
{ source: "a\\b", id: 2 },
{ source: "a|b", id: 3 },
],
},
};
expect(
resolveSelector(String.raw`{r.results | where source == "a\"b"}`, ctx),
).toEqual([{ source: 'a"b', id: 1 }]);
expect(
resolveSelector(String.raw`{r.results | where source == "a\\b"}`, ctx),
).toEqual([{ source: "a\\b", id: 2 }]);
expect(resolveSelector('{r.results | where source == "a|b"}', ctx)).toEqual(
[{ source: "a|b", id: 3 }],
);
});
it("where equality rejects non-string and malformed operands", () => {
const invalid = [
"{r.results | where source == sessions}",
`{r.results | where source != "sessions"}`,
`{r.results | where source == 'sessions'}`,
String.raw`{r.results | where source == "bad\q"}`,
`{r.results | where source == "unterminated}`,
];
for (const selector of invalid)
expect(() => resolveSelector(selector, fixture())).toThrow(
SpecValidationError,
);
});
it("where and groupBy reject non-array sources and unknown phases", () => {
for (const selector of [
"{item | where real}",
'{item | where real == "yes"}',
"{item | groupBy source}",
"{missing.results | groupBy source}",
])
expect(() => resolveSelector(selector, fixture())).toThrow(
SpecValidationError,
);
});
it("groupBy handles empty keys and skips invalid keys", () => {
const ctx: InterpContext = {
results: {
r: [
{ source: "", id: 1 },
{ source: "__proto__", id: 2 },
{ source: "", id: 3 },
{ source: "__proto__", id: 4 },
{ source: null, id: 5 },
{ source: false, id: 6 },
{ source: ["sessions"], id: 7 },
{ id: 8 },
],
},
};
expect(resolveSelector("{r.results | groupBy source}", ctx)).toEqual([
{
key: "",
items: [
{ source: "", id: 1 },
{ source: "", id: 3 },
],
},
{
key: "__proto__",
items: [
{ source: "__proto__", id: 2 },
{ source: "__proto__", id: 4 },
],
},
]);
});
it("groupBy returns an empty array for empty or entirely invalid input", () => {
expect(
resolveSelector("{r.results | groupBy source}", { results: { r: [] } }),
).toEqual([]);
expect(
resolveSelector("{r.results | groupBy source}", {
results: { r: [null, { source: 1 }, { id: 2 }] },
}),
).toEqual([]);
});
it("groupBy renders as structured JSON in templates", () => {
const output = resolveTemplate("groups={r.results | groupBy source}", {
results: {
r: [
{ source: "sessions", id: 1 },
{ source: "web", id: 2 },
],
},
});
expect(output).toBe(
'groups=[{"key":"sessions","items":[{"source":"sessions","id":1}]},{"key":"web","items":[{"source":"web","id":2}]}]',
);
});
it("rejects chained and repeated selector operators", () => {
for (const selector of [
"{r.results | groupBy source | where key}",
"{r.results | where source | groupBy source}",
])
expect(() => resolveSelector(selector, fixture())).toThrow(
SpecValidationError,
);
});
it("resolves and filters {PHASE.failures}", () => {
expect(resolveSelector("{r.failures}", fixture())).toHaveLength(2);
expect(
resolveSelector("{r.failures | where retryable}", fixture()),
).toEqual([
{ code: "transport", retryable: true, message: "rate limited" },
]);
});
it("rejects an unknown phase failure reference", () => {
expect(() => resolveSelector("{missing.failures}", fixture())).toThrow(
SpecValidationError,
);
});
it("resolves {args.NAME}", () => {
expect(resolveSelector("{args.tags}", fixture())).toEqual(["a", "b"]);
});
it("resolves {item}", () => {
expect(resolveSelector("{item}", fixture())).toBe("performance");
});
it("throws SpecValidationError on an unknown token {foo.bar | map x}", () => {
expect(() => resolveSelector("{foo.bar | map x}", fixture())).toThrow(
SpecValidationError,
);
});
it("throws SpecValidationError on an unknown filter", () => {
expect(() =>
resolveSelector("{r.results | filter real}", fixture()),
).toThrow(SpecValidationError);
});
it("throws SpecValidationError on a non-brace-wrapped selector", () => {
expect(() => resolveSelector("r.results", fixture())).toThrow(
SpecValidationError,
);
});
it("throws SpecValidationError on a phase reference that is not present", () => {
expect(() => resolveSelector("{missing.results}", fixture())).toThrow(
SpecValidationError,
);
});
it("projects scalar, object, and array fields in result order", () => {
const ctx: InterpContext = {
results: {
r: [
{ report: { summary: "first" } },
{ report: ["nested", "array"] },
{ report: false },
{ report: 0 },
{ report: "" },
],
},
};
expect(resolveSelector("{r.results[].report}", ctx)).toEqual([
{ summary: "first" },
["nested", "array"],
false,
0,
"",
]);
});
it("skips null, missing, and non-record values under projection", () => {
const ctx: InterpContext = {
results: {
r: [null, "not-a-record", { report: null }, {}, { report: "ok" }],
},
};
expect(resolveSelector("{r.results[].report}", ctx)).toEqual(["ok"]);
});
it("skips a non-array FIELD under flatten", () => {
const ctx: InterpContext = {
results: { r: [{ findings: "not-an-array" }, { findings: ["ok"] }] },
};
expect(resolveSelector("{r.results[].findings[]}", ctx)).toEqual(["ok"]);
});
});
describe("line-197 shared-fixture property", () => {
it("the same null is retained for plain results but dropped under flatten and where", () => {
const ctx = fixture();
const plain = resolveSelector("{r.results}", ctx) as unknown[];
const flat = resolveSelector("{r.results[].findings[]}", ctx) as unknown[];
const filtered = resolveSelector(
"{r.results | where real}",
ctx,
) as unknown[];
// plain: null present positionally
expect(plain).toHaveLength(5);
expect(plain[1]).toBeNull();
// flatten + where: null never appears
expect(flat.some((x) => x === null)).toBe(false);
expect(filtered.some((x) => x === null)).toBe(false);
expect(flat).toEqual(["a1", "a2", "b1", "c1"]);
expect(filtered).toEqual([
{ real: true, findings: ["a1", "a2"] },
{ real: true },
]);
});
});
describe("resolveOver", () => {
it("returns an array literal unchanged", () => {
const lit = ["correctness", "security", "performance"];
expect(resolveOver(lit, {})).toEqual(lit);
});
it("resolves grouped results as a fanout input", () => {
expect(
resolveOver("{r.results | groupBy source}", {
results: {
r: [
{ source: "sessions", id: 1 },
{ source: "repository", id: 2 },
],
},
}),
).toEqual([
{ key: "sessions", items: [{ source: "sessions", id: 1 }] },
{ key: "repository", items: [{ source: "repository", id: 2 }] },
]);
});
it("resolves failures as a recovery fanout", () => {
expect(resolveOver("{r.failures}", fixture())).toEqual(
fixture().failures?.r,
);
});
it("resolves a field projection as a fanout input", () => {
expect(
resolveOver("{r.results[].findings}", {
results: { r: [{ findings: ["a"] }, { findings: ["b"] }] },
}),
).toEqual([["a"], ["b"]]);
});
it("resolves an interpolation expression to its array", () => {
expect(resolveOver("{r.results[].findings[]}", fixture())).toEqual([
"a1",
"a2",
"b1",
"c1",
]);
});
it("throws SpecValidationError when an interpolation does not resolve to an array", () => {
// {item} resolves to a string, not an array
expect(() => resolveOver("{item}", fixture())).toThrow(SpecValidationError);
});
it("throws SpecValidationError when over is neither an array nor a string", () => {
expect(() => resolveOver(42, fixture())).toThrow(SpecValidationError);
});
});