package shape import ( "context" "strings" "testing" "unicode/utf8" "gitea.maximumdirect.net/eric/notarius/internal/core/source" "gitea.maximumdirect.net/eric/notarius/internal/framework/contracts" "gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline" "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd" ) func TestValidatorApprovesWellFormedNPCPayload(t *testing.T) { result, err := New(Options{}).Validate(context.Background(), requestWithValue(validNPCRegistry())) if err != nil || !result.Approved { t.Fatalf("Validate() = %#v, %v; want approval", result, err) } } func TestValidatorRejectsRequiredShapeValues(t *testing.T) { value := validNPCRegistry() value.NPCs[0].Name = "" result, err := New(Options{}).Validate(context.Background(), requestWithValue(value)) if err != nil || result.Approved || result.ReasonCode != ReasonCode || !strings.Contains(result.Message, "name must not be empty") { t.Fatalf("Validate() = %#v, %v; want bounded shape rejection", result, err) } missing := dnd.NPCRegistry{} result, err = New(Options{}).Validate(context.Background(), requestWithValue(missing)) if err != nil || result.Approved || result.ReasonCode != ReasonCode { t.Fatalf("missing Validate() = %#v, %v; want shape rejection", result, err) } } func TestValidatorBoundsDiagnosticsAndQuotesUnicode(t *testing.T) { value := dnd.NPCRegistry{NPCs: make([]dnd.NPC, 24)} long := strings.Repeat("火", 220) + "\n\t" for index := range value.NPCs { value.NPCs[index] = dnd.NPC{ID: "", Name: long, SourceRefs: []source.SourceRef{}} } result, err := New(Options{}).Validate(context.Background(), requestWithValue(value)) if err != nil || result.Approved || len([]byte(result.Message)) > diagnosticsMaxMessageBytes || !utf8.ValidString(result.Message) { t.Fatalf("Validate() = %#v, %v; want bounded valid UTF-8 rejection", result, err) } if strings.Count(result.Message, "npcs[") > diagnosticsMaxIssues || !strings.Contains(result.Message, "additional issue(s) omitted") { t.Fatalf("message = %q, want bounded quoted diagnostics", result.Message) } } func TestValidatorSpecCheckpointAndRegistration(t *testing.T) { if got := New(Options{}).CheckpointFingerprints(); len(got) != 1 || got[0].Name != "policy" || got[0].Value != "dnd.npc_registry.validator.shape.v1" { t.Fatalf("CheckpointFingerprints() = %#v, want local policy", got) } if spec := Spec(); spec.Key != Key || spec.ExecutionClass != contracts.ExecutionClassDeterministic { t.Fatalf("Spec() = %#v, want deterministic shape validator", spec) } registry := pipeline.NewValidatorRegistry() if err := Register(registry); err != nil { t.Fatalf("Register() error = %v", err) } if _, err := DecodeOptions(map[string]any{"unexpected": true}); err == nil { t.Fatal("DecodeOptions() accepted unknown option") } } func TestValidatorDoesNotMutateValue(t *testing.T) { value := validNPCRegistry() before := value _, err := New(Options{}).Validate(context.Background(), requestWithValue(value)) if err != nil || value.NPCs[0].Name != before.NPCs[0].Name { t.Fatalf("Validate() mutated value: %#v", value) } } func requestWithValue(value dnd.NPCRegistry) contracts.TypedValidationRequest[dnd.NPCRegistry] { return contracts.TypedValidationRequest[dnd.NPCRegistry]{Value: value} } func validNPCRegistry() dnd.NPCRegistry { return dnd.NPCRegistry{NPCs: []dnd.NPC{{ ID: "candidate", Name: "Mira Thorn", SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 1, EndUnitID: 1}}, }}} } const ( diagnosticsMaxIssues = 20 diagnosticsMaxMessageBytes = 4096 )