Add contract composition test
This commit is contained in:
150
internal/framework/contracts/composition_test.go
Normal file
150
internal/framework/contracts/composition_test.go
Normal file
@@ -0,0 +1,150 @@
|
||||
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.Candidate{
|
||||
{
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user