(fixtures: Fixture[])
| 428 | } |
| 429 | |
| 430 | export function validateFixtures(fixtures: Fixture[]): ValidationResult[] { |
| 431 | const results: ValidationResult[] = []; |
| 432 | |
| 433 | const seenUserMessages = new Map<string, number>(); |
| 434 | |
| 435 | for (let i = 0; i < fixtures.length; i++) { |
| 436 | const f = fixtures[i]; |
| 437 | const response = f.response; |
| 438 | |
| 439 | // Skip response-shape validation for function responses — they are |
| 440 | // evaluated at runtime so we cannot statically inspect them. |
| 441 | if (typeof response === "function") { |
| 442 | // Still validate match fields and numeric options below. |
| 443 | } else { |
| 444 | // --- Error checks --- |
| 445 | |
| 446 | // Response type recognition |
| 447 | // Note: isContentWithToolCallsResponse must be checked before isTextResponse |
| 448 | // and isToolCallResponse since it is a structural superset of both. |
| 449 | if ( |
| 450 | !isContentWithToolCallsResponse(response) && |
| 451 | !isTextResponse(response) && |
| 452 | !isToolCallResponse(response) && |
| 453 | !isErrorResponse(response) && |
| 454 | !isEmbeddingResponse(response) && |
| 455 | !isImageResponse(response) && |
| 456 | !isAudioResponse(response) && |
| 457 | !isTranscriptionResponse(response) && |
| 458 | !isVideoResponse(response) && |
| 459 | !isJSONResponse(response) |
| 460 | ) { |
| 461 | results.push({ |
| 462 | severity: "error", |
| 463 | fixtureIndex: i, |
| 464 | message: |
| 465 | "response is not a recognized type (must have content, toolCalls, error, embedding, image, audio, transcription, video, or json)", |
| 466 | }); |
| 467 | } |
| 468 | |
| 469 | // When a non-empty ordered `blocks` array is present, the builders stream |
| 470 | // `blocks` and IGNORE the legacy `content` mirror (see validateBlocks's |
| 471 | // divergence note + isContentWithToolCallsResponse's BLOCKS-ONLY clause). |
| 472 | // So an empty-string `content` is harmless in that case and must NOT raise |
| 473 | // the "content is empty string" hard error. Fixtures WITHOUT blocks keep |
| 474 | // the error (an empty content with no blocks produces no output). |
| 475 | const hasNonEmptyBlocks = |
| 476 | Array.isArray((response as { blocks?: unknown }).blocks) && |
| 477 | (response as { blocks: unknown[] }).blocks.length > 0; |
| 478 | |
| 479 | // Text response checks |
| 480 | if (isTextResponse(response)) { |
| 481 | if (response.content === "" && !hasNonEmptyBlocks) { |
| 482 | results.push({ |
| 483 | severity: "error", |
| 484 | fixtureIndex: i, |
| 485 | message: "content is empty string", |
| 486 | }); |
| 487 | } |
no test coverage detected
searching dependent graphs…