Skip to content

Commit 44f6e3f

Browse files
committed
Add validateFixtures checks for turnIndex and hasToolResult
JSON fixtures bypass TypeScript's type system, so invalid values like turnIndex: -1, turnIndex: 1.5, turnIndex: "zero", or hasToolResult: "yes" would silently pass validation but fail to match at runtime. Add runtime type checks for both fields and corresponding unit tests.
1 parent a58cb08 commit 44f6e3f

2 files changed

Lines changed: 84 additions & 0 deletions

File tree

src/__tests__/fixture-loader.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,68 @@ describe("validateFixtures", () => {
800800
expect(validateFixtures(fixtures)).toHaveLength(0);
801801
});
802802

803+
// --- match.turnIndex / match.hasToolResult type checks ---
804+
805+
it("error: turnIndex is negative", () => {
806+
const fixtures = [makeFixture({ match: { userMessage: "test", turnIndex: -1 } })];
807+
const results = validateFixtures(fixtures);
808+
expect(
809+
results.some((r) => r.severity === "error" && r.message.includes("turnIndex")),
810+
).toBe(true);
811+
});
812+
813+
it("error: turnIndex is a float", () => {
814+
const fixtures = [makeFixture({ match: { userMessage: "test", turnIndex: 1.5 } })];
815+
const results = validateFixtures(fixtures);
816+
expect(
817+
results.some((r) => r.severity === "error" && r.message.includes("turnIndex")),
818+
).toBe(true);
819+
});
820+
821+
it("error: turnIndex is a string", () => {
822+
const fixtures = [
823+
makeFixture({ match: { userMessage: "test", turnIndex: "zero" as never } }),
824+
];
825+
const results = validateFixtures(fixtures);
826+
expect(
827+
results.some((r) => r.severity === "error" && r.message.includes("turnIndex")),
828+
).toBe(true);
829+
});
830+
831+
it("no error: turnIndex is 0 (falsy but valid)", () => {
832+
const fixtures = [makeFixture({ match: { userMessage: "test", turnIndex: 0 } })];
833+
const results = validateFixtures(fixtures);
834+
expect(results.filter((r) => r.message.includes("turnIndex"))).toHaveLength(0);
835+
});
836+
837+
it("no error: turnIndex is a positive integer", () => {
838+
const fixtures = [makeFixture({ match: { userMessage: "test", turnIndex: 3 } })];
839+
const results = validateFixtures(fixtures);
840+
expect(results.filter((r) => r.message.includes("turnIndex"))).toHaveLength(0);
841+
});
842+
843+
it("error: hasToolResult is a string", () => {
844+
const fixtures = [
845+
makeFixture({ match: { userMessage: "test", hasToolResult: "yes" as never } }),
846+
];
847+
const results = validateFixtures(fixtures);
848+
expect(
849+
results.some((r) => r.severity === "error" && r.message.includes("hasToolResult")),
850+
).toBe(true);
851+
});
852+
853+
it("no error: hasToolResult is false (falsy but valid)", () => {
854+
const fixtures = [makeFixture({ match: { userMessage: "test", hasToolResult: false } })];
855+
const results = validateFixtures(fixtures);
856+
expect(results.filter((r) => r.message.includes("hasToolResult"))).toHaveLength(0);
857+
});
858+
859+
it("no error: hasToolResult is true", () => {
860+
const fixtures = [makeFixture({ match: { userMessage: "test", hasToolResult: true } })];
861+
const results = validateFixtures(fixtures);
862+
expect(results.filter((r) => r.message.includes("hasToolResult"))).toHaveLength(0);
863+
});
864+
803865
// --- Warning checks ---
804866

805867
it("warning: duplicate userMessage", () => {

src/fixture-loader.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,28 @@ export function validateFixtures(fixtures: Fixture[]): ValidationResult[] {
531531
}
532532
}
533533

534+
// Match field type checks
535+
if (f.match.turnIndex !== undefined) {
536+
if (
537+
typeof f.match.turnIndex !== "number" ||
538+
f.match.turnIndex < 0 ||
539+
!Number.isInteger(f.match.turnIndex)
540+
) {
541+
results.push({
542+
severity: "error",
543+
fixtureIndex: i,
544+
message: "match.turnIndex must be a non-negative integer",
545+
});
546+
}
547+
}
548+
if (f.match.hasToolResult !== undefined && typeof f.match.hasToolResult !== "boolean") {
549+
results.push({
550+
severity: "error",
551+
fixtureIndex: i,
552+
message: `match.hasToolResult must be a boolean, got ${typeof f.match.hasToolResult}`,
553+
});
554+
}
555+
534556
// --- Warning checks ---
535557

536558
// Duplicate userMessage shadowing — include turnIndex, hasToolResult, and

0 commit comments

Comments
 (0)