|
1 | 1 | package guard |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bytes" |
| 5 | + "context" |
4 | 6 | "crypto/sha256" |
5 | 7 | "encoding/hex" |
| 8 | + "encoding/json" |
6 | 9 | "os" |
7 | 10 | "path/filepath" |
8 | 11 | "strings" |
@@ -321,3 +324,91 @@ targets: |
321 | 324 | t.Errorf("all six archives came out the same size, so nothing is being drawn") |
322 | 325 | } |
323 | 326 | } |
| 327 | + |
| 328 | +// A refusal about a size names the key the recipe actually carries. |
| 329 | +// |
| 330 | +// The format is handed a count of bytes and answers about bytes, so every size |
| 331 | +// refusal it produces says "size" - the only size key it knows. A target that |
| 332 | +// draws from a range has no "size" key at all. Measured on 2026-09-06: a recipe |
| 333 | +// carrying "size-range: 143-200" was refused at "targets[1].size", sending |
| 334 | +// somebody to a box that was neither on their screen nor in their file. |
| 335 | +// |
| 336 | +// NOT COVERED BY TestEveryRecipeRefusalSaysWhichSettingItIsAbout, and that was |
| 337 | +// checked rather than assumed. Its table has a "size-range" case already, but |
| 338 | +// that refusal comes from the recipe READER, which knows which key it was |
| 339 | +// reading. This one comes from underneath, and the table's runner never reaches |
| 340 | +// it - the two cases were tried there first and the recipe came back valid, |
| 341 | +// because nothing in that path plans a file. |
| 342 | +// |
| 343 | +// The pair matters more than either half. A build that answered "size-range" |
| 344 | +// for every size refusal would pass the first case and misaddress every |
| 345 | +// ordinary target in the tree. |
| 346 | +func TestASizeRefusalIsAddressedToTheKeyTheRecipeCarries(t *testing.T) { |
| 347 | + cases := []struct { |
| 348 | + name string |
| 349 | + src string |
| 350 | + want string |
| 351 | + }{ |
| 352 | + { |
| 353 | + name: "a range the format cannot deliver", |
| 354 | + src: `version: 1 |
| 355 | +targets: |
| 356 | + - id: a |
| 357 | + format: pdf |
| 358 | + count: 40 |
| 359 | + size-range: 10-8kb |
| 360 | +`, |
| 361 | + want: "targets[1].size-range", |
| 362 | + }, |
| 363 | + { |
| 364 | + name: "a plain size the format cannot deliver", |
| 365 | + src: `version: 1 |
| 366 | +targets: |
| 367 | + - id: a |
| 368 | + format: pdf |
| 369 | + count: 1 |
| 370 | + size: 10 |
| 371 | +`, |
| 372 | + want: "targets[1].size", |
| 373 | + }, |
| 374 | + } |
| 375 | + |
| 376 | + for _, c := range cases { |
| 377 | + t.Run(c.name, func(t *testing.T) { |
| 378 | + dir := t.TempDir() |
| 379 | + path := writeRecipe(t, dir, c.src) |
| 380 | + |
| 381 | + var out, errOut bytes.Buffer |
| 382 | + if code := cli.Run(context.Background(), |
| 383 | + []string{"validate", path, "--json"}, &out, &errOut); code == cli.ExitOK { |
| 384 | + t.Fatalf("this recipe was meant to be refused and validate was happy with it:\n%s", |
| 385 | + out.String()) |
| 386 | + } |
| 387 | + |
| 388 | + var report struct { |
| 389 | + Problems []struct { |
| 390 | + What string `json:"what"` |
| 391 | + At string `json:"at"` |
| 392 | + } `json:"problems"` |
| 393 | + } |
| 394 | + if err := json.Unmarshal(errOut.Bytes(), &report); err != nil { |
| 395 | + t.Fatalf("the report is not readable as JSON: %v\n%s", err, errOut.String()) |
| 396 | + } |
| 397 | + // Asserted rather than assumed. A report with no problems in it |
| 398 | + // would pass the loop below by never entering it. |
| 399 | + if len(report.Problems) == 0 { |
| 400 | + t.Fatalf("the report carries no problems at all:\n%s", errOut.String()) |
| 401 | + } |
| 402 | + |
| 403 | + for _, pr := range report.Problems { |
| 404 | + if pr.At == c.want { |
| 405 | + continue |
| 406 | + } |
| 407 | + t.Errorf("the refusal %q is addressed to %q and belongs at %q.\n"+ |
| 408 | + "What to do: a refusal about a size carries the key that held the number, "+ |
| 409 | + "so a window can mark the box somebody can actually change.", |
| 410 | + pr.What, pr.At, c.want) |
| 411 | + } |
| 412 | + }) |
| 413 | + } |
| 414 | +} |
0 commit comments