Files
notarius/internal/framework/contracts/diagnostics_test.go

140 lines
6.8 KiB
Go

package contracts
import (
"strings"
"testing"
)
func TestProducerDiagnosticValidationAcceptsClassificationMatrix(t *testing.T) {
for _, test := range []struct {
name string
disposition DiagnosticDisposition
category DiagnosticCategory
}{
{name: "configuration warning", disposition: DiagnosticDispositionWarning, category: DiagnosticCategoryConfiguration},
{name: "degradation warning", disposition: DiagnosticDispositionWarning, category: DiagnosticCategoryDegradation},
{name: "incomplete validation warning", disposition: DiagnosticDispositionWarning, category: DiagnosticCategoryValidationIncomplete},
{name: "fallback warning", disposition: DiagnosticDispositionWarning, category: DiagnosticCategoryFallback},
{name: "quality advisory", disposition: DiagnosticDispositionAdvisory, category: DiagnosticCategoryDataQuality},
{name: "normalization observation", disposition: DiagnosticDispositionObservation, category: DiagnosticCategoryNormalization},
} {
t.Run(test.name, func(t *testing.T) {
diagnostic := validProducerDiagnostic()
diagnostic.Disposition = test.disposition
diagnostic.Category = test.category
if err := diagnostic.Validate(); err != nil {
t.Fatalf("Validate() error = %v", err)
}
})
}
}
func TestProducerDiagnosticValidationRejectsInvalidFieldsAndCounts(t *testing.T) {
tooLongReason := strings.Repeat("r", MaxDiagnosticReasonCodeBytes+1)
tooLongScope := strings.Repeat("s", MaxDiagnosticScopeBytes+1)
tooLongMessage := strings.Repeat("m", MaxDiagnosticMessageBytes+1)
for _, test := range []struct {
name string
mutate func(*ProducerDiagnostic)
}{
{name: "invalid classification", mutate: func(diagnostic *ProducerDiagnostic) { diagnostic.Category = DiagnosticCategoryDataQuality }},
{name: "blank reason", mutate: func(diagnostic *ProducerDiagnostic) { diagnostic.ReasonCode = " \t" }},
{name: "invalid reason UTF-8", mutate: func(diagnostic *ProducerDiagnostic) { diagnostic.ReasonCode = string([]byte{0xff}) }},
{name: "oversized reason", mutate: func(diagnostic *ProducerDiagnostic) { diagnostic.ReasonCode = tooLongReason }},
{name: "blank scope", mutate: func(diagnostic *ProducerDiagnostic) { diagnostic.Samples[0].Scope = "\n" }},
{name: "invalid scope UTF-8", mutate: func(diagnostic *ProducerDiagnostic) { diagnostic.Samples[0].Scope = string([]byte{0xff}) }},
{name: "oversized scope", mutate: func(diagnostic *ProducerDiagnostic) { diagnostic.Samples[0].Scope = tooLongScope }},
{name: "blank message", mutate: func(diagnostic *ProducerDiagnostic) { diagnostic.Samples[0].Message = " " }},
{name: "invalid message UTF-8", mutate: func(diagnostic *ProducerDiagnostic) { diagnostic.Samples[0].Message = string([]byte{0xff}) }},
{name: "oversized message", mutate: func(diagnostic *ProducerDiagnostic) { diagnostic.Samples[0].Message = tooLongMessage }},
{name: "zero occurrences", mutate: func(diagnostic *ProducerDiagnostic) { diagnostic.OccurrenceCount = 0 }},
{name: "missing samples", mutate: func(diagnostic *ProducerDiagnostic) {
diagnostic.Samples = nil
diagnostic.OmittedSampleCount = diagnostic.OccurrenceCount
}},
{name: "too many samples", mutate: func(diagnostic *ProducerDiagnostic) {
diagnostic.OccurrenceCount = 4
diagnostic.Samples = []DiagnosticSample{{Scope: "one", Message: "one"}, {Scope: "two", Message: "two"}, {Scope: "three", Message: "three"}, {Scope: "four", Message: "four"}}
diagnostic.OmittedSampleCount = 0
}},
{name: "duplicate samples", mutate: func(diagnostic *ProducerDiagnostic) {
diagnostic.OccurrenceCount = 2
diagnostic.Samples = []DiagnosticSample{{Scope: "scope", Message: "message"}, {Scope: "scope", Message: "message"}}
diagnostic.OmittedSampleCount = 0
}},
{name: "inconsistent omission", mutate: func(diagnostic *ProducerDiagnostic) { diagnostic.OmittedSampleCount = 1 }},
{name: "producer chunk context", mutate: func(diagnostic *ProducerDiagnostic) { chunkIndex := 0; diagnostic.Samples[0].ChunkIndex = &chunkIndex }},
} {
t.Run(test.name, func(t *testing.T) {
diagnostic := validProducerDiagnostic()
test.mutate(&diagnostic)
if err := diagnostic.Validate(); err == nil {
t.Fatal("Validate() error = nil, want invalid diagnostic error")
}
})
}
}
func TestValidateProducerDiagnosticsEnforcesLocalGroupBound(t *testing.T) {
diagnostics := make([]ProducerDiagnostic, MaxProducerDiagnosticGroups)
for index := range diagnostics {
diagnostics[index] = validProducerDiagnostic()
diagnostics[index].ReasonCode = "reason-" + string(rune('a'+index))
}
if err := ValidateProducerDiagnostics(diagnostics); err != nil {
t.Fatalf("ValidateProducerDiagnostics() error = %v", err)
}
diagnostics = append(diagnostics, validProducerDiagnostic())
if err := ValidateProducerDiagnostics(diagnostics); err == nil {
t.Fatal("ValidateProducerDiagnostics() error = nil, want excessive-group error")
}
}
func TestDiagnosticGroupValidationPreservesChunkIndexZero(t *testing.T) {
chunkIndex := 0
group := DiagnosticGroup{
Disposition: DiagnosticDispositionAdvisory,
Category: DiagnosticCategoryDataQuality,
ReasonCode: "unresolved",
Origin: DiagnosticOrigin{Stage: DiagnosticOriginStageExtract, StepID: "extract", LaneID: "spells", ModuleKey: "dnd/spells", ValidatorKey: "dnd/spells/source-relatedness"},
OccurrenceCount: 1,
Samples: []DiagnosticSample{{Scope: "spells[0]", Message: "Spell was not found", ChunkID: "chunk-1", ChunkIndex: &chunkIndex}},
}
if err := group.Validate(); err != nil {
t.Fatalf("Validate() error = %v", err)
}
collection := DiagnosticCollection{Groups: []DiagnosticGroup{group}}
if err := collection.Validate(); err != nil {
t.Fatalf("DiagnosticCollection.Validate() error = %v", err)
}
}
func TestDiagnosticGroupRejectsRepeatedSampleWithEqualChunkIndex(t *testing.T) {
firstIndex := 0
secondIndex := 0
group := DiagnosticGroup{
Disposition: DiagnosticDispositionAdvisory,
Category: DiagnosticCategoryDataQuality,
ReasonCode: "unresolved",
Origin: DiagnosticOrigin{Stage: DiagnosticOriginStageExtract, StepID: "extract", LaneID: "spells", ModuleKey: "dnd/spells"},
OccurrenceCount: 2,
Samples: []DiagnosticSample{
{Scope: "spells[0]", Message: "Spell was not found", ChunkID: "chunk-1", ChunkIndex: &firstIndex},
{Scope: "spells[0]", Message: "Spell was not found", ChunkID: "chunk-1", ChunkIndex: &secondIndex},
},
}
if err := group.Validate(); err == nil {
t.Fatal("Validate() error = nil, want duplicate sample error")
}
}
func validProducerDiagnostic() ProducerDiagnostic {
return ProducerDiagnostic{
Disposition: DiagnosticDispositionWarning,
Category: DiagnosticCategoryConfiguration,
ReasonCode: "empty_reference",
OccurrenceCount: 1,
Samples: []DiagnosticSample{{Scope: "references.glossary", Message: "Reference is empty"}},
}
}