package registryresolver_test import ( "errors" "fmt" "strings" "sync" "sync/atomic" "testing" "gitea.maximumdirect.net/eric/notarius/internal/framework/contracts" "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/registryresolver" ) const ( testSlot = "registry" testMediaType = "application/json" ) type immutableView struct { value string identity string } func TestResolverHandlesConstructionStateFallbackAndOverrides(t *testing.T) { config := resolverConfig(nil) absent, err := registryresolver.New(config, contracts.ReferenceSet{}) if err != nil { t.Fatal(err) } placeholder, err := registryresolver.New(config, referenceSet()) if err != nil { t.Fatal(err) } if absent.Seeded().value != "absent" || placeholder.Seeded().value != "absent" { t.Fatalf("seeded views = %#v / %#v, want absent", absent.Seeded(), placeholder.Seeded()) } seedContent := []byte("seeded") seeded, err := registryresolver.New(config, referenceSet(referenceItem(seedContent))) if err != nil { t.Fatal(err) } fallback, err := seeded.Resolve(contracts.ReferenceSet{}) if err != nil || fallback != seeded.Seeded() { t.Fatalf("fallback = %p, %v; seeded = %p", fallback, err, seeded.Seeded()) } override, err := seeded.Resolve(referenceSet(referenceItem([]byte("override")))) if err != nil || override == seeded.Seeded() || override.value != "override" { t.Fatalf("override = %#v, %v", override, err) } if _, err := seeded.Resolve(referenceSet()); err == nil || !strings.Contains(err.Error(), "exactly one item") { t.Fatalf("operation placeholder error = %v", err) } if _, err := registryresolver.New(config, referenceSet(referenceItem([]byte("one")), referenceItem([]byte("two")))); err == nil || !strings.Contains(err.Error(), "exactly one item") { t.Fatalf("construction cardinality error = %v", err) } } func TestResolveOptionalSingleItemValidatesAndOwnsContent(t *testing.T) { spec := referenceSpec() if item, present, err := registryresolver.ResolveOptionalSingleItem(contracts.ReferenceSet{}, spec); err != nil || present || item.Content != nil { t.Fatalf("absent item = %#v, %t, %v", item, present, err) } content := []byte("approved") set := referenceSet(contracts.ReferenceItem{ SlotName: testSlot, MediaType: "Application/JSON; Charset=UTF-8", Content: content, Digest: "sha256:caller-supplied", Origin: contracts.ReferenceOrigin{Type: "file", URI: "/private/campaign.json"}, }) item, present, err := registryresolver.ResolveOptionalSingleItem(set, spec) if err != nil || !present || item.MediaType != testMediaType || string(item.Content) != "approved" { t.Fatalf("resolved item = %#v, %t, %v", item, present, err) } content[0] = 'X' if string(item.Content) != "approved" { t.Fatalf("resolved item retained caller bytes: %q", item.Content) } secret := "private campaign material" for _, test := range []struct { name string spec registryresolver.ReferenceSpec set contracts.ReferenceSet want string }{ {"blank slot", registryresolver.ReferenceSpec{AcceptedMediaType: testMediaType, MaxBytes: 64}, contracts.ReferenceSet{}, "slot name"}, {"invalid accepted media", registryresolver.ReferenceSpec{SlotName: testSlot, AcceptedMediaType: "not a type", MaxBytes: 64}, contracts.ReferenceSet{}, "accepted media type"}, {"invalid maximum", registryresolver.ReferenceSpec{SlotName: testSlot, AcceptedMediaType: testMediaType}, contracts.ReferenceSet{}, "maximum size"}, {"empty slot", spec, referenceSet(), "exactly one item"}, {"multiple items", spec, referenceSet(referenceItem([]byte("one")), referenceItem([]byte("two"))), "exactly one item"}, {"invalid item media", spec, referenceSet(contracts.ReferenceItem{MediaType: secret, Content: []byte(secret)}), "media type is invalid"}, {"wrong item media", spec, referenceSet(contracts.ReferenceItem{MediaType: "text/plain", Content: []byte(secret)}), "must be application/json"}, {"oversized", spec, referenceSet(referenceItem([]byte(strings.Repeat("x", 65)))), "limit 64"}, } { t.Run(test.name, func(t *testing.T) { _, _, err := registryresolver.ResolveOptionalSingleItem(test.set, test.spec) if err == nil || !strings.Contains(err.Error(), test.want) { t.Fatalf("error = %v, want %q", err, test.want) } if strings.Contains(err.Error(), secret) || strings.Contains(err.Error(), "/private/") { t.Fatalf("error exposed reference content or provenance: %v", err) } }) } } func TestResolverReusesRawSemanticAndSeededViewsByActualContent(t *testing.T) { var loads atomic.Int32 resolver, err := registryresolver.New(resolverConfig(&loads), referenceSet(referenceItem([]byte("alpha")))) if err != nil { t.Fatal(err) } if loads.Load() != 1 { t.Fatalf("construction loads = %d, want 1", loads.Load()) } seedEquivalent := referenceSet(referenceItem([]byte(" alpha "))) resolvedSeed, err := resolver.Resolve(seedEquivalent) if err != nil || resolvedSeed != resolver.Seeded() { t.Fatalf("semantic seeded reuse = %p, %v; want %p", resolvedSeed, err, resolver.Seeded()) } if _, err := resolver.Resolve(seedEquivalent); err != nil || loads.Load() != 2 { t.Fatalf("raw seeded reuse loads = %d, err = %v; want 2", loads.Load(), err) } betaSet := referenceSet(referenceItem([]byte("beta"))) beta, err := resolver.Resolve(betaSet) if err != nil { t.Fatal(err) } betaAgain, err := resolver.Resolve(betaSet) if err != nil || betaAgain != beta { t.Fatalf("raw reuse = %p / %p, %v", beta, betaAgain, err) } betaEquivalent, err := resolver.Resolve(referenceSet(referenceItem([]byte("\n beta \t")))) if err != nil || betaEquivalent != beta { t.Fatalf("semantic reuse = %p / %p, %v", beta, betaEquivalent, err) } mediaVariant := referenceItem([]byte("beta")) mediaVariant.MediaType = "APPLICATION/JSON; charset=utf-8" if got, err := resolver.Resolve(referenceSet(mediaVariant)); err != nil || got != beta { t.Fatalf("normalized media raw reuse = %p / %p, %v", beta, got, err) } if loads.Load() != 4 { t.Fatalf("loads after raw and semantic reuse = %d, want 4", loads.Load()) } first := referenceItem([]byte("gamma")) second := referenceItem([]byte("delta")) first.Digest = "sha256:" + strings.Repeat("0", 64) second.Digest = first.Digest gamma, err := resolver.Resolve(referenceSet(first)) if err != nil { t.Fatal(err) } delta, err := resolver.Resolve(referenceSet(second)) if err != nil || gamma == delta || gamma.value == delta.value { t.Fatalf("caller digest aliased different bytes: %#v / %#v, %v", gamma, delta, err) } } func TestResolverSerializesConcurrentLoads(t *testing.T) { var loads atomic.Int32 resolver, err := registryresolver.New(resolverConfig(&loads), contracts.ReferenceSet{}) if err != nil { t.Fatal(err) } loads.Store(0) references := referenceSet(referenceItem([]byte("shared"))) const callers = 32 results := make(chan *immutableView, callers) errors := make(chan error, callers) var group sync.WaitGroup for range callers { group.Add(1) go func() { defer group.Done() view, err := resolver.Resolve(references) if err != nil { errors <- err return } results <- view }() } group.Wait() close(results) close(errors) for err := range errors { t.Error(err) } var first *immutableView for result := range results { if first == nil { first = result } if result != first { t.Fatalf("concurrent result = %p, want %p", result, first) } } if loads.Load() != 1 { t.Fatalf("concurrent loads = %d, want 1", loads.Load()) } } func TestResolverDoesNotCacheFailuresOrRetainCallerState(t *testing.T) { loaderFailure := errors.New("loader unavailable") var loads atomic.Int32 var loadedBytes []byte config := resolverConfig(&loads) config.Load = func(content []byte) (*immutableView, error) { loads.Add(1) loadedBytes = content if string(content) == "fail" { return nil, loaderFailure } value := strings.TrimSpace(string(content)) return &immutableView{value: value, identity: value}, nil } seedBytes := []byte("seed") seedReferences := referenceSet(referenceItem(seedBytes)) resolver, err := registryresolver.New(config, seedReferences) if err != nil { t.Fatal(err) } seedBytes[0] = 'X' delete(seedReferences.Slots, testSlot) if resolver.Seeded().value != "seed" || string(loadedBytes) != "seed" { t.Fatalf("construction retained caller state: %#v / %q", resolver.Seeded(), loadedBytes) } failing := referenceSet(referenceItem([]byte("fail"))) for range 2 { if _, err := resolver.Resolve(failing); !errors.Is(err, loaderFailure) { t.Fatalf("loader error = %v", err) } } if loads.Load() != 3 { t.Fatalf("loads after repeated failure = %d, want 3", loads.Load()) } operationBytes := []byte("operation") operationReferences := referenceSet(referenceItem(operationBytes)) view, err := resolver.Resolve(operationReferences) if err != nil { t.Fatal(err) } operationBytes[0] = 'X' operationReferences.Slots[testSlot] = contracts.ResolvedReferenceSlot{} if view.value != "operation" || string(loadedBytes) != "operation" { t.Fatalf("operation retained caller state: %#v / %q", view, loadedBytes) } var invalidLoads atomic.Int32 config.Load = func([]byte) (*immutableView, error) { invalidLoads.Add(1) return &immutableView{value: "invalid"}, nil } missingIdentity, err := registryresolver.New(config, contracts.ReferenceSet{}) if err != nil { t.Fatal(err) } for range 2 { if _, err := missingIdentity.Resolve(referenceSet(referenceItem([]byte("invalid")))); err == nil || !strings.Contains(err.Error(), "semantic identity") { t.Fatalf("missing identity error = %v", err) } } if invalidLoads.Load() != 2 { t.Fatalf("invalid view loads = %d, want 2", invalidLoads.Load()) } } func TestNewValidatesCallbacksAndDoesNotCacheAbsentFailure(t *testing.T) { base := resolverConfig(nil) for _, test := range []struct { name string mutate func(*registryresolver.Config[*immutableView]) want string }{ {"absent", func(config *registryresolver.Config[*immutableView]) { config.Absent = nil }, "absent-view"}, {"loader", func(config *registryresolver.Config[*immutableView]) { config.Load = nil }, "loader"}, {"identity", func(config *registryresolver.Config[*immutableView]) { config.SemanticIdentity = nil }, "semantic-identity"}, } { t.Run(test.name, func(t *testing.T) { config := base test.mutate(&config) if _, err := registryresolver.New(config, contracts.ReferenceSet{}); err == nil || !strings.Contains(err.Error(), test.want) { t.Fatalf("New() error = %v, want %q", err, test.want) } }) } absentFailure := errors.New("absent unavailable") base.Absent = func() (*immutableView, error) { return nil, absentFailure } if _, err := registryresolver.New(base, contracts.ReferenceSet{}); !errors.Is(err, absentFailure) { t.Fatalf("absent error = %v", err) } } func resolverConfig(loads *atomic.Int32) registryresolver.Config[*immutableView] { return registryresolver.Config[*immutableView]{ Reference: referenceSpec(), Absent: func() (*immutableView, error) { return &immutableView{value: "absent", identity: "absent"}, nil }, Load: func(content []byte) (*immutableView, error) { if loads != nil { loads.Add(1) } value := strings.TrimSpace(string(content)) return &immutableView{value: value, identity: "identity:" + value}, nil }, SemanticIdentity: func(view *immutableView) string { if view == nil { return "" } return view.identity }, } } func referenceSpec() registryresolver.ReferenceSpec { return registryresolver.ReferenceSpec{SlotName: testSlot, AcceptedMediaType: testMediaType, MaxBytes: 64} } func referenceItem(content []byte) contracts.ReferenceItem { return contracts.ReferenceItem{SlotName: testSlot, MediaType: testMediaType, Content: content} } func referenceSet(items ...contracts.ReferenceItem) contracts.ReferenceSet { return contracts.ReferenceSet{Slots: map[string]contracts.ResolvedReferenceSlot{ testSlot: {Slot: contracts.ReferenceSlot{Name: testSlot}, Items: items}, }} } func TestErrorsDoNotFormatReferenceData(t *testing.T) { secret := "hidden campaign notes" config := resolverConfig(nil) config.Load = func([]byte) (*immutableView, error) { return nil, fmt.Errorf("safe loader failure") } resolver, err := registryresolver.New(config, contracts.ReferenceSet{}) if err != nil { t.Fatal(err) } item := referenceItem([]byte(secret)) item.Origin.URI = "/private/campaign.json" _, err = resolver.Resolve(referenceSet(item)) if err == nil || strings.Contains(err.Error(), secret) || strings.Contains(err.Error(), item.Origin.URI) { t.Fatalf("error exposed reference data: %v", err) } }