package pipeline import ( "context" "reflect" "strings" "testing" "gitea.maximumdirect.net/eric/notarius/internal/framework/contracts" ) func TestValidatorRegistryBehavior(t *testing.T) { registry := NewValidatorRegistry() if err := registry.Register(" generic-validator ", validatorConstructor("generic-validator", contracts.ExecutionClassDeterministic)); err != nil { t.Fatalf("Register() error = %v, want nil", err) } validator, err := registry.Build("generic-validator") if err != nil { t.Fatalf("Build() error = %v, want nil", err) } if validator.Name() != "generic-validator" { t.Fatalf("validator name = %q, want generic-validator", validator.Name()) } spec, ok := registry.Spec(" generic-validator ") if !ok { t.Fatal("Spec() ok = false, want true") } want := ValidatorSpec{Key: "generic-validator", ExecutionClass: contracts.ExecutionClassDeterministic} if !reflect.DeepEqual(spec, want) { t.Fatalf("Spec() = %#v, want %#v", spec, want) } } func TestValidatorRegistryRegistersSpecs(t *testing.T) { registry := NewValidatorRegistry() spec := ValidatorSpec{Key: " llm-validator ", ExecutionClass: contracts.ExecutionClassLLMBacked} if err := registry.RegisterWithSpec(spec, validatorConstructor("llm-validator", contracts.ExecutionClassLLMBacked)); err != nil { t.Fatalf("RegisterWithSpec() error = %v, want nil", err) } got, ok := registry.Spec("llm-validator") if !ok { t.Fatal("Spec() ok = false, want true") } want := ValidatorSpec{Key: "llm-validator", ExecutionClass: contracts.ExecutionClassLLMBacked} if !reflect.DeepEqual(got, want) { t.Fatalf("Spec() = %#v, want %#v", got, want) } } func TestValidatorRegistryRegisteredSpecsAreSorted(t *testing.T) { registry := NewValidatorRegistry() for _, key := range []string{"zeta", "alpha"} { if err := registry.Register(key, validatorConstructor(key, contracts.ExecutionClassDeterministic)); err != nil { t.Fatalf("Register(%q) error = %v", key, err) } } specs := registry.RegisteredSpecs() if len(specs) != 2 || specs[0].Key != "alpha" || specs[1].Key != "zeta" { t.Fatalf("RegisteredSpecs() = %#v, want sorted specs", specs) } } func TestValidatorRegistryRejectsUnsupportedExecutionClass(t *testing.T) { registry := NewValidatorRegistry() err := registry.RegisterWithSpec( ValidatorSpec{Key: "invalid-validator", ExecutionClass: contracts.ExecutionClass("unsupported")}, validatorConstructor("invalid-validator", contracts.ExecutionClass("unsupported")), ) if err == nil { t.Fatal("RegisterWithSpec() error = nil, want unsupported execution class error") } } func TestValidatorRegistryRejectsConstructorExecutionClassMismatch(t *testing.T) { registry := NewValidatorRegistry() if err := registry.RegisterWithSpec( ValidatorSpec{Key: "validator", ExecutionClass: contracts.ExecutionClassDeterministic}, validatorConstructor("validator", contracts.ExecutionClassLLMBacked), ); err != nil { t.Fatalf("RegisterWithSpec() error = %v, want nil", err) } _, err := registry.Build("validator") if err == nil { t.Fatal("Build() error = nil, want execution class mismatch") } if !strings.Contains(err.Error(), "execution class") { t.Fatalf("Build() error = %q, want execution class context", err.Error()) } } type testValidator struct { name string executionClass contracts.ExecutionClass } func validatorConstructor(name string, executionClass contracts.ExecutionClass) ValidatorConstructor { return func() (contracts.Validator, error) { return testValidator{name: name, executionClass: executionClass}, nil } } func (validator testValidator) Name() string { return validator.name } func (validator testValidator) ExecutionClass() contracts.ExecutionClass { return validator.executionClass } func (validator testValidator) Validate(ctx context.Context, req contracts.ValidationRequest) (contracts.ValidationResult, error) { return contracts.ValidationResult{Approved: true}, nil }