129 lines
4.0 KiB
TypeScript
129 lines
4.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { minimizeInput } from "../../src/minimize/engine";
|
|
import {
|
|
createPredicate,
|
|
type RegexEvaluator,
|
|
} from "../../src/minimize/predicates";
|
|
|
|
const unusedRegex: RegexEvaluator = async () => ({
|
|
matched: false,
|
|
elapsedMs: 0,
|
|
timedOut: false,
|
|
});
|
|
|
|
describe("minimization engine", () => {
|
|
it("removes XML structure while preserving a literal marker", async () => {
|
|
const input = `<case><noise a="1">discard</noise><payload><message>BOOM</message><also>discard</also></payload></case>`;
|
|
const predicate = createPredicate(
|
|
{ kind: "contains", needle: "BOOM" },
|
|
unusedRegex,
|
|
);
|
|
const result = await minimizeInput(
|
|
input,
|
|
{ structure: "xml", maxTests: 300, maxSeconds: 5 },
|
|
predicate,
|
|
new AbortController().signal,
|
|
);
|
|
expect(result.minimized).toContain("BOOM");
|
|
expect(result.minimized.length).toBeLessThan(input.length);
|
|
expect(
|
|
new DOMParser()
|
|
.parseFromString(result.minimized, "application/xml")
|
|
.querySelector("parsererror"),
|
|
).toBeNull();
|
|
expect(result.steps.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("keeps the original JSON Schema failure signature", async () => {
|
|
const input = JSON.stringify({
|
|
request: { id: 0, label: "noise" },
|
|
unrelated: [1, 2, 3],
|
|
});
|
|
const predicate = createPredicate(
|
|
{
|
|
kind: "json-schema-fails",
|
|
preserveFailureSignature: true,
|
|
schema: JSON.stringify({
|
|
type: "object",
|
|
required: ["request"],
|
|
properties: {
|
|
request: {
|
|
type: "object",
|
|
required: ["id"],
|
|
properties: { id: { type: "integer", minimum: 1 } },
|
|
},
|
|
},
|
|
}),
|
|
},
|
|
unusedRegex,
|
|
);
|
|
const result = await minimizeInput(
|
|
input,
|
|
{ structure: "json", maxTests: 500, maxSeconds: 5 },
|
|
predicate,
|
|
new AbortController().signal,
|
|
);
|
|
expect(JSON.parse(result.minimized)).toEqual({ request: { id: 0 } });
|
|
});
|
|
|
|
it("minimizes malformed syntax and reports budget exhaustion", async () => {
|
|
const predicate = createPredicate({ kind: "invalid-json" }, unusedRegex);
|
|
const result = await minimizeInput(
|
|
'{"large": [1,2,}',
|
|
{ structure: "json", maxTests: 1, maxSeconds: 5 },
|
|
predicate,
|
|
new AbortController().signal,
|
|
);
|
|
expect(result.exhausted).toBe(true);
|
|
expect(result.tests).toBe(1);
|
|
});
|
|
|
|
it("rejects a baseline that does not reproduce and honours cancellation", async () => {
|
|
await expect(
|
|
minimizeInput(
|
|
"safe",
|
|
{ structure: "text", maxTests: 20, maxSeconds: 2 },
|
|
createPredicate({ kind: "contains", needle: "BOOM" }, unusedRegex),
|
|
new AbortController().signal,
|
|
),
|
|
).rejects.toThrow(/does not satisfy/u);
|
|
const controller = new AbortController();
|
|
controller.abort();
|
|
await expect(
|
|
minimizeInput(
|
|
"BOOM",
|
|
{ structure: "text", maxTests: 20, maxSeconds: 2 },
|
|
createPredicate({ kind: "contains", needle: "BOOM" }, unusedRegex),
|
|
controller.signal,
|
|
),
|
|
).rejects.toMatchObject({ name: "AbortError" });
|
|
});
|
|
|
|
it("uses isolated regex outcomes and fails closed on unsafe schemas/XSLT", async () => {
|
|
const slow = createPredicate(
|
|
{ kind: "regex-slow", pattern: "a+", flags: "u", thresholdMs: 20 },
|
|
async () => ({ matched: false, elapsedMs: 20, timedOut: false }),
|
|
);
|
|
expect(await slow("aaaa", new AbortController().signal)).toBe(true);
|
|
expect(() =>
|
|
createPredicate(
|
|
{
|
|
kind: "json-schema-fails",
|
|
schema: '{"$ref":"https://invalid/schema.json"}',
|
|
},
|
|
unusedRegex,
|
|
),
|
|
).toThrow(/unsupported.*\$ref/iu);
|
|
expect(() =>
|
|
createPredicate(
|
|
{
|
|
kind: "xslt-throws",
|
|
stylesheet:
|
|
'<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:include href="https://invalid/x.xsl"/></xsl:stylesheet>',
|
|
},
|
|
unusedRegex,
|
|
),
|
|
).toThrow(/include/u);
|
|
});
|
|
});
|