package contracts_test import ( "context" "encoding/json" "errors" "testing" "gitea.maximumdirect.net/eric/notarius/internal/core/artifacts" "gitea.maximumdirect.net/eric/notarius/internal/core/source" "gitea.maximumdirect.net/eric/notarius/internal/framework/contracts" ) var _ contracts.InputAdapter = compositionAdapter{} var _ contracts.Extractor = compositionExtractor{} var _ contracts.Validator = compositionValidator{} func TestContractsComposeAcrossPackages(t *testing.T) { ctx := context.Background() adapter := compositionAdapter{} extractor := compositionExtractor{} validator := compositionValidator{} doc, err := adapter.Parse(ctx, contracts.ParseRequest{SourceID: "source-1"}) if err != nil { t.Fatalf("Parse() error = %v, want nil", err) } if err := source.ValidateDocument(doc); err != nil { t.Fatalf("ValidateDocument() error = %v, want nil", err) } extraction, err := extractor.Extract(ctx, contracts.ExtractionRequest{Source: doc}) if err != nil { t.Fatalf("Extract() error = %v, want nil", err) } if len(extraction.Candidates) != 1 { t.Fatalf("len(Candidates) = %d, want 1", len(extraction.Candidates)) } candidate := extraction.Candidates[0] for _, ref := range candidate.SourceRefs { if err := source.ValidateRef(doc, ref); err != nil { t.Fatalf("ValidateRef() error = %v, want nil", err) } } validation, err := validator.Validate(ctx, contracts.ValidationRequest{ Source: doc, Candidates: extraction.Candidates, }) if err != nil { t.Fatalf("Validate() error = %v, want nil", err) } if len(validation.Decisions) != 1 { t.Fatalf("len(Decisions) = %d, want 1", len(validation.Decisions)) } decision := validation.Decisions[0] if !decision.Approved { t.Fatal("Approved = false, want true") } if decision.CandidateIndex != candidate.Index { t.Fatalf("CandidateIndex = %d, want %d", decision.CandidateIndex, candidate.Index) } } type compositionAdapter struct{} func (adapter compositionAdapter) Key() string { return "generic-input" } func (adapter compositionAdapter) Parse(ctx context.Context, req contracts.ParseRequest) (*source.SourceDocument, error) { return &source.SourceDocument{ ID: req.SourceID, Kind: "document", Format: "text/plain", Digest: "sha256:abc123", Units: []source.SourceUnit{ {ID: "u1", Kind: "unit", Text: "First source unit."}, {ID: "u2", Kind: "unit", Text: "Second source unit."}, }, }, nil } type compositionExtractor struct{} func (extractor compositionExtractor) Key() string { return "generic-extractor" } func (extractor compositionExtractor) ArtifactType() string { return "generic-artifact" } func (extractor compositionExtractor) SchemaVersion() string { return "v1" } func (extractor compositionExtractor) Validators() []contracts.Validator { return []contracts.Validator{compositionValidator{}} } func (extractor compositionExtractor) Extract(ctx context.Context, req contracts.ExtractionRequest) (contracts.ExtractionResult, error) { if req.Source == nil { return contracts.ExtractionResult{}, errors.New("source document is required") } return contracts.ExtractionResult{ Candidates: []artifacts.ArtifactCandidate{ { Index: 0, ExtractorKey: extractor.Key(), ArtifactType: extractor.ArtifactType(), SchemaVersion: extractor.SchemaVersion(), Payload: json.RawMessage(`{"value":"example"}`), SourceRefs: []source.SourceRef{ { SourceID: req.Source.ID, StartUnitID: req.Source.Units[0].ID, EndUnitID: req.Source.Units[1].ID, }, }, }, }, }, nil } type compositionValidator struct{} func (validator compositionValidator) Name() string { return "generic-validator" } func (validator compositionValidator) Validate(ctx context.Context, req contracts.ValidationRequest) (contracts.ValidationResult, error) { decisions := make([]contracts.ValidationDecision, 0, len(req.Candidates)) for _, candidate := range req.Candidates { decisions = append(decisions, contracts.ValidationDecision{ CandidateIndex: candidate.Index, Approved: true, ReasonCode: "accepted", Message: "candidate accepted", }) } return contracts.ValidationResult{ ValidatorName: validator.Name(), Decisions: decisions, }, nil }