diff --git a/lagoon/fill_fuzz_test.go b/lagoon/fill_fuzz_test.go new file mode 100644 index 0000000..151ff14 --- /dev/null +++ b/lagoon/fill_fuzz_test.go @@ -0,0 +1,68 @@ +package lagoon + +import ( + "encoding/json" + "testing" +) + +// fuzzFillFixture is the D-07 framework-level target: only name/notes are +// allow-listed. collection_id, secret, and id must stay at their pre-Fill values. +type fuzzFillFixture struct { + ID uint `gorm:"column:id"` + Name string `gorm:"column:name"` + Notes *string `gorm:"column:notes"` + CollectionID uint `gorm:"column:collection_id"` + Secret string `gorm:"column:secret"` +} + +func FuzzFill(f *testing.F) { + f.Add(`{"name":"ok","notes":"n"}`) + f.Add(`{"name":"ok","collection_id":9,"secret":"leak","id":99}`) + f.Add(`{"name":null,"notes":null}`) + f.Add(`{"collection_id":1,"unknown":true}`) + f.Add(`{"name":{"nested":true},"notes":[1,2]}`) + f.Add(`{"name":1,"notes":false}`) + f.Add(`[]`) + f.Add(``) + f.Add(`null`) + f.Add(`{"name":"x","extra":{"deep":{"x":1}}}`) + + f.Fuzz(func(t *testing.T, raw string) { + var requested map[string]any + _ = json.Unmarshal([]byte(raw), &requested) + if requested == nil { + requested = map[string]any{} + } + requested["collection_id"] = float64(99) + requested["secret"] = "pwned" + requested["id"] = float64(7) + + row := fuzzFillFixture{ID: 1, CollectionID: 3, Secret: "keep"} + if err := Fill(&row, []string{"name", "notes"}, requested, true); err != nil { + // Type mismatches on allow-listed keys are errors, not panics. + // Non-allow-listed fields must still be untouched. + } + if row.CollectionID != 3 { + t.Fatalf("collection_id mutated to %d", row.CollectionID) + } + if row.Secret != "keep" { + t.Fatalf("secret mutated to %q", row.Secret) + } + if row.ID != 1 { + t.Fatalf("id mutated to %d", row.ID) + } + }) +} + +func TestFuzzFillNilAndEmptyNeverPanic(t *testing.T) { + row := fuzzFillFixture{CollectionID: 3, Secret: "keep"} + if err := Fill(&row, []string{"name"}, nil, true); err != nil { + t.Fatalf("nil map: %v", err) + } + if err := Fill(&row, []string{"name"}, map[string]any{}, true); err != nil { + t.Fatalf("empty map: %v", err) + } + if row.CollectionID != 3 || row.Secret != "keep" { + t.Fatalf("zero-input fill mutated %+v", row) + } +}