iop/packages/go/streamgate/validation_regression_test.go
toki c90bb755a9 feat: streamline plan/code-review/finalize router, add stream gate SDDs, sync dev-test inventory, update roadmap milestones
- Refactor plan, code-review, finalize-task-routing, refine-local-plans, router skills
- Add agent-workflow-loop-orchestration skill and plan agent configs
- Update roadmap: knowledge-tool-optimization milestones, stream-evidence-gate-core SDD
- Add stream-evidence-gate-core task, archive, and Go streamgate package
- Update dev-test inventory (edge/node smoke), agent-contract, edge-local-dev-guide
- Deprecate USER_REVIEW for output-validation-filters SDD
2026-07-24 15:11:00 +09:00

1182 lines
35 KiB
Go

package streamgate
import (
"testing"
"time"
)
// TestNormalizedConstructorsRejectEmptyPayload verifies that text, reasoning,
// and tool call fragment constructors reject empty payloads by running the
// assembled value through Validate.
func TestNormalizedConstructorsRejectEmptyPayload(t *testing.T) {
ts := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
// Empty text delta
if _, err := NewTextDeltaEvent("ch", "", ts); err == nil {
t.Fatal("NewTextDeltaEvent with empty text should return error")
}
// Empty reasoning delta
if _, err := NewReasoningDeltaEvent("ch", "", ts); err == nil {
t.Fatal("NewReasoningDeltaEvent with empty reasoning should return error")
}
// Empty tool call args
if _, err := NewToolCallFragmentEvent("ch", "id", "name", "", ts); err == nil {
t.Fatal("NewToolCallFragmentEvent with empty args should return error")
}
// Non-empty variants should succeed and pass Validate
ev, err := NewTextDeltaEvent("ch", "hello", ts)
if err != nil {
t.Fatalf("NewTextDeltaEvent with non-empty text: %v", err)
}
if err := ev.Validate(); err != nil {
t.Fatalf("valid text delta failed Validate: %v", err)
}
ev, err = NewReasoningDeltaEvent("ch", "thinking...", ts)
if err != nil {
t.Fatalf("NewReasoningDeltaEvent with non-empty reasoning: %v", err)
}
if err := ev.Validate(); err != nil {
t.Fatalf("valid reasoning delta failed Validate: %v", err)
}
ev, err = NewToolCallFragmentEvent("ch", "id", "name", `{"key":"val"}`, ts)
if err != nil {
t.Fatalf("NewToolCallFragmentEvent with non-empty args: %v", err)
}
if err := ev.Validate(); err != nil {
t.Fatalf("valid tool call fragment failed Validate: %v", err)
}
}
// TestFilterValueValidationRejectsMalformedPrivateState verifies that directly
// assembled FilterOutcome, RecoveryDirective, FilterDecision, SanitizedEvidence,
// and RecoveryIntent values with opposite payloads, invalid tokens, or unknown
// enums are rejected by Validate.
func TestFilterValueValidationRejectsMalformedPrivateState(t *testing.T) {
ts := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
// --- FilterOutcome: evaluated with errorCode ---
badEvaluated := FilterOutcome{
kind: FilterOutcomeKindEvaluated,
decision: buildValidDecision(t),
errorCode: mustToken("err.code"),
}
if err := badEvaluated.Validate(); err == nil {
t.Fatal("evaluated outcome with errorCode should be rejected")
}
// --- FilterOutcome: evaluation_error with decision ---
badError := FilterOutcome{
kind: FilterOutcomeKindEvaluationError,
decision: buildValidDecision(t),
errorCode: mustToken("err.code"),
}
if err := badError.Validate(); err == nil {
t.Fatal("evaluation_error outcome with decision should be rejected")
}
// --- FilterOutcome: not_applicable with decision ---
badNA := FilterOutcome{
kind: FilterOutcomeKindNotApplicableForEpoch,
decision: buildValidDecision(t),
}
if err := badNA.Validate(); err == nil {
t.Fatal("not_applicable outcome with decision should be rejected")
}
// --- FilterOutcome: deferred with errorCode ---
badDeferred := FilterOutcome{
kind: FilterOutcomeKindDeferredByRequirement,
errorCode: mustToken("err.code"),
}
if err := badDeferred.Validate(); err == nil {
t.Fatal("deferred outcome with errorCode should be rejected")
}
// --- FilterOutcome: unknown kind ---
badKind := FilterOutcome{
kind: FilterOutcomeKind("unknown"),
}
if err := badKind.Validate(); err == nil {
t.Fatal("unknown outcome kind should be rejected")
}
// --- RecoveryDirective: exact with continuation fields ---
badExact := RecoveryDirective{
kind: RecoveryDirectiveKindExact,
requestRef: mustToken("req.ref"),
cursor: 5,
snapshotRef: mustToken("snap.ref"),
}
if err := badExact.Validate(); err == nil {
t.Fatal("exact directive with cursor/snapshotRef should be rejected")
}
// --- RecoveryDirective: continuation with exact fields ---
badCont := RecoveryDirective{
kind: RecoveryDirectiveKindContinuation,
requestRef: mustToken("req.ref"),
snapshotRef: mustToken("snap.ref"),
cursor: 0,
}
if err := badCont.Validate(); err == nil {
t.Fatal("continuation directive with requestRef should be rejected")
}
// --- RecoveryDirective: schema with exact fields ---
badSchema := RecoveryDirective{
kind: RecoveryDirectiveKindSchema,
requestRef: mustToken("req.ref"),
schemaRef: mustToken("schema.ref"),
patchCode: mustToken("patch.code"),
}
if err := badSchema.Validate(); err == nil {
t.Fatal("schema directive with requestRef should be rejected")
}
// --- RecoveryDirective: unknown kind ---
badDirKind := RecoveryDirective{
kind: RecoveryDirectiveKind("unknown"),
}
if err := badDirKind.Validate(); err == nil {
t.Fatal("unknown directive kind should be rejected")
}
// --- FilterDecision: invalid consumerID token ---
badDecision := FilterDecision{
kind: FilterDecisionKindPass,
consumerID: StableToken{value: "INVALID"},
filterID: mustToken("filter.id"),
ruleID: mustToken("rule.id"),
evidence: buildValidEvidence(t, ts),
}
if err := badDecision.Validate(); err == nil {
t.Fatal("decision with invalid consumerID should be rejected")
}
// --- SanitizedEvidence: unknown event kind ---
badEvidence := SanitizedEvidence{
eventKind: EventKind("unknown_kind"),
channel: "ch",
filterRule: mustToken("filter.rule"),
outcome: FilterOutcomeKindEvaluated,
descriptorCode: mustToken("desc.code"),
fingerprint: [32]byte{1},
count: 0,
offset: 0,
timestamp: ts,
}
if err := badEvidence.Validate(); err == nil {
t.Fatal("evidence with unknown event kind should be rejected")
}
// --- SanitizedEvidence: unknown outcome kind ---
badEvidence2 := SanitizedEvidence{
eventKind: EventKindTextDelta,
channel: "ch",
filterRule: mustToken("filter.rule"),
outcome: FilterOutcomeKind("unknown_outcome"),
descriptorCode: mustToken("desc.code"),
fingerprint: [32]byte{1},
count: 0,
offset: 0,
timestamp: ts,
}
if err := badEvidence2.Validate(); err == nil {
t.Fatal("evidence with unknown outcome kind should be rejected")
}
// --- SanitizedEvidence: invalid filterRule token ---
badEvidence3 := SanitizedEvidence{
eventKind: EventKindTextDelta,
channel: "ch",
filterRule: StableToken{value: "INVALID"},
outcome: FilterOutcomeKindEvaluated,
descriptorCode: mustToken("desc.code"),
fingerprint: [32]byte{1},
count: 0,
offset: 0,
timestamp: ts,
}
if err := badEvidence3.Validate(); err == nil {
t.Fatal("evidence with invalid filterRule token should be rejected")
}
// --- RecoveryIntent: invalid reason token ---
badIntent := RecoveryIntent{
strategy: RecoveryStrategyExactReplay,
directive: mustBuildExactDirective(t),
reason: StableToken{value: "INVALID"},
priority: 0,
}
if err := badIntent.Validate(); err == nil {
t.Fatal("intent with invalid reason token should be rejected")
}
}
// TestTerminalValueValidationRejectsMalformedPrivateState verifies that directly
// assembled FailureCause, ExternalDescriptor, and TerminalResult values with
// invalid required tokens, malformed nested values, or over-long cause chains
// are rejected by Validate.
func TestTerminalValueValidationRejectsMalformedPrivateState(t *testing.T) {
ts := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
// --- FailureCause: invalid stage token ---
badCause := FailureCause{
stage: StableToken{value: "INVALID"},
code: mustToken("code"),
consumer: mustTokenOptional("consumer"),
filter: mustTokenOptional("filter"),
ruleID: mustTokenOptional("rule"),
}
if err := badCause.Validate(); err == nil {
t.Fatal("cause with invalid stage should be rejected")
}
// --- FailureCause: invalid code token ---
badCause2 := FailureCause{
stage: mustToken("stage"),
code: StableToken{value: "INVALID"},
consumer: mustTokenOptional("consumer"),
filter: mustTokenOptional("filter"),
ruleID: mustTokenOptional("rule"),
}
if err := badCause2.Validate(); err == nil {
t.Fatal("cause with invalid code should be rejected")
}
// --- ExternalDescriptor: invalid errorType token ---
badDesc := ExternalDescriptor{
errorType: StableToken{value: "INVALID"},
code: mustTokenOptional("code"),
message: mustToken("message"),
param: mustTokenOptional("param"),
}
if err := badDesc.Validate(); err == nil {
t.Fatal("descriptor with invalid errorType should be rejected")
}
// --- ExternalDescriptor: invalid message token ---
badDesc2 := ExternalDescriptor{
errorType: mustToken("errortype"),
code: mustTokenOptional("code"),
message: StableToken{value: "INVALID"},
param: mustTokenOptional("param"),
}
if err := badDesc2.Validate(); err == nil {
t.Fatal("descriptor with invalid message should be rejected")
}
// --- TerminalResult: error with invalid descriptor ---
badTr := TerminalResult{
channel: "ch",
err: true,
externalDesc: badDescPtr(),
occurredAt: ts,
}
if err := badTr.Validate(); err == nil {
t.Fatal("error terminal with invalid descriptor should be rejected")
}
// --- TerminalResult: error with over-long cause chain ---
overLongCauses := make([]FailureCause, MaxFailureCauses+1)
for i := range overLongCauses {
overLongCauses[i] = FailureCause{
stage: mustToken("stage"),
code: mustToken("code"),
}
}
badTr2 := TerminalResult{
channel: "ch",
err: true,
externalDesc: validDescPtr(),
failureCauses: FailureCauseChain{causes: overLongCauses},
occurredAt: ts,
}
if err := badTr2.Validate(); err == nil {
t.Fatal("error terminal with over-long cause chain should be rejected")
}
// --- TerminalResult: error with malformed cause ---
badCauseChains := []FailureCause{
{
stage: mustToken("stage"),
code: mustToken("code"),
},
{
stage: StableToken{value: "INVALID"},
code: mustToken("code"),
},
}
badTr3 := TerminalResult{
channel: "ch",
err: true,
externalDesc: validDescPtr(),
failureCauses: FailureCauseChain{causes: badCauseChains},
occurredAt: ts,
}
if err := badTr3.Validate(); err == nil {
t.Fatal("error terminal with malformed nested cause should be rejected")
}
// --- Valid 4-entry error chain should pass ---
validCauses := make([]FailureCause, MaxFailureCauses)
for i := range validCauses {
validCauses[i] = FailureCause{
stage: mustToken("stage"),
code: mustToken("code"),
}
}
validTr := TerminalResult{
channel: "ch",
err: true,
externalDesc: validDescPtr(),
failureCauses: FailureCauseChain{causes: validCauses},
occurredAt: ts,
}
if err := validTr.Validate(); err != nil {
t.Fatalf("valid 4-entry error chain failed Validate: %v", err)
}
// --- Valid success terminal without descriptor should pass ---
validSuccess := TerminalResult{
channel: "ch",
success: true,
occurredAt: ts,
}
if err := validSuccess.Validate(); err != nil {
t.Fatalf("valid success terminal failed Validate: %v", err)
}
}
// TestEvidenceBatchValidationRejectsMalformedStagedStart verifies that EvidenceBatch
// rejects invalid HTTP status codes in staged response-start regardless of
// terminal flag, and enforces transport_uncommitted for staged starts.
func TestEvidenceBatchValidationRejectsMalformedStagedStart(t *testing.T) {
ts := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
// --- Private status 99 (below min) ---
badStart99 := ResponseStart{
channel: "ch",
status: 99,
timestamp: ts,
}
if _, err := NewEvidenceBatch(
[]NormalizedEvent{},
nil, nil,
&badStart99, false,
CommitStateTransportUncommitted, ts,
); err == nil {
t.Fatal("evidence batch with status 99 should be rejected")
}
// --- Private status 600 (above max) ---
badStart600 := ResponseStart{
channel: "ch",
status: 600,
timestamp: ts,
}
if _, err := NewEvidenceBatch(
[]NormalizedEvent{},
nil, nil,
&badStart600, false,
CommitStateTransportUncommitted, ts,
); err == nil {
t.Fatal("evidence batch with status 600 should be rejected")
}
// --- Direct Validate on batch with bad staged start ---
badBatch := EvidenceBatch{
capturedAt: ts,
commitState: CommitStateTransportUncommitted,
stagedStart: &badStart99,
terminalFlag: false,
}
if err := badBatch.Validate(); err == nil {
t.Fatal("batch with status 99 staged start should be rejected by Validate")
}
// --- Valid status 100 with transport_uncommitted should pass ---
validStart := ResponseStart{
channel: "ch",
status: 100,
timestamp: ts,
}
if _, err := NewEvidenceBatch(
[]NormalizedEvent{},
nil, nil,
&validStart, false,
CommitStateTransportUncommitted, ts,
); err != nil {
t.Fatalf("valid staged start with transport_uncommitted failed: %v", err)
}
// --- Valid status 599 with transport_uncommitted should pass ---
validStart2 := ResponseStart{
channel: "ch",
status: 599,
timestamp: ts,
}
if _, err := NewEvidenceBatch(
[]NormalizedEvent{},
nil, nil,
&validStart2, false,
CommitStateTransportUncommitted, ts,
); err != nil {
t.Fatalf("valid staged start with status 599 failed: %v", err)
}
// --- Staged start with stream_open should be rejected ---
if _, err := NewEvidenceBatch(
[]NormalizedEvent{},
nil, nil,
&validStart, false,
CommitStateStreamOpen, ts,
); err == nil {
t.Fatal("staged start with stream_open should be rejected")
}
}
// --- Helper functions ---
func buildValidDecision(t *testing.T) *FilterDecision {
t.Helper()
d, err := NewFilterDecision(
FilterDecisionKindPass,
"consumer.id", "filter.id", "rule.id",
buildValidEvidence(t, time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)),
nil,
)
if err != nil {
t.Fatalf("buildValidDecision: %v", err)
}
return &d
}
func buildValidEvidence(t *testing.T, ts time.Time) SanitizedEvidence {
t.Helper()
e, err := NewSanitizedEvidence(
EventKindTextDelta,
"ch", "filter.rule", "desc.code",
[32]byte{1},
0, 0,
FilterOutcomeKindEvaluated,
ts,
)
if err != nil {
t.Fatalf("buildValidEvidence: %v", err)
}
return e
}
func mustToken(v string) StableToken {
tk, err := NewStableTokenRequired("test", v)
if err != nil {
panic(err)
}
return tk
}
func mustTokenOptional(v string) StableToken {
tk, err := NewStableTokenOptional("test", v)
if err != nil {
panic(err)
}
return tk
}
func mustBuildExactDirective(t *testing.T) RecoveryDirective {
t.Helper()
d, err := NewRecoveryDirectiveExact("req.ref")
if err != nil {
t.Fatalf("mustBuildExactDirective: %v", err)
}
return d
}
func badDescPtr() *ExternalDescriptor {
d := ExternalDescriptor{
errorType: mustToken("errortype"),
code: mustTokenOptional("code"),
message: StableToken{value: "INVALID"},
param: mustTokenOptional("param"),
}
return &d
}
func validDescPtr() *ExternalDescriptor {
d, err := NewExternalDescriptor("error.type", "code", "message", "")
if err != nil {
panic(err)
}
return &d
}
// TestNormalizedEventValidationEnforcesCauseBound verifies that the Validate
// method on NormalizedEvent rejects provider_error events whose failure cause
// chain exceeds MaxFailureCauses (4), while accepting chains of exactly 4.
// Invalid nested causes remain rejected as before.
func TestNormalizedEventValidationEnforcesCauseBound(t *testing.T) {
ts := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
desc, err := NewExternalDescriptor("error.type", "code", "message", "")
if err != nil {
t.Fatalf("NewExternalDescriptor: %v", err)
}
// Build 4 valid causes (at the boundary).
causes4 := make([]FailureCause, MaxFailureCauses)
for i := range causes4 {
causes4[i] = FailureCause{
stage: mustToken("stage"),
code: mustToken("code"),
}
}
chain4 := FailureCauseChain{causes: causes4}
// Build 5 causes (exceeds boundary).
causes5 := make([]FailureCause, MaxFailureCauses+1)
for i := range causes5 {
causes5[i] = FailureCause{
stage: mustToken("stage"),
code: mustToken("code"),
}
}
chain5 := FailureCauseChain{causes: causes5}
// 4 causes must pass Validate.
ev4 := NormalizedEvent{
kind: EventKindProviderError,
channel: "ch",
timestamp: ts,
disposition: BaseDispositionTerminalErrorCandidate,
externalDesc: &desc,
failureCauses: chain4,
}
if err := ev4.Validate(); err != nil {
t.Fatalf("4-cause provider error event should pass Validate: %v", err)
}
// 5 causes must fail Validate.
ev5 := NormalizedEvent{
kind: EventKindProviderError,
channel: "ch",
timestamp: ts,
disposition: BaseDispositionTerminalErrorCandidate,
externalDesc: &desc,
failureCauses: chain5,
}
if err := ev5.Validate(); err == nil {
t.Fatal("5-cause provider error event should fail Validate")
}
// Invalid nested cause must still fail.
badCauses := []FailureCause{
{
stage: mustToken("stage"),
code: mustToken("code"),
},
{
stage: StableToken{value: "INVALID"},
code: mustToken("code"),
},
}
badChain := FailureCauseChain{causes: badCauses}
evBad := NormalizedEvent{
kind: EventKindProviderError,
channel: "ch",
timestamp: ts,
disposition: BaseDispositionTerminalErrorCandidate,
externalDesc: &desc,
failureCauses: badChain,
}
if err := evBad.Validate(); err == nil {
t.Fatal("provider error with invalid nested cause should fail Validate")
}
}
// TestFilterDecisionConstructorCopiesRecoveryIntent verifies that NewFilterDecision
// defensively copies the caller-owned *RecoveryIntent so that subsequent
// variable reassignment of the original intent does not affect the returned
// FilterDecision or any previously created evaluated FilterOutcome.
func TestFilterDecisionConstructorCopiesRecoveryIntent(t *testing.T) {
ts := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
evidence := buildValidEvidence(t, ts)
directive, err := NewRecoveryDirectiveExact("req.ref")
if err != nil {
t.Fatalf("NewRecoveryDirectiveExact: %v", err)
}
intent, err := NewRecoveryIntent(RecoveryStrategyExactReplay, directive, "reason", 0)
if err != nil {
t.Fatalf("NewRecoveryIntent: %v", err)
}
decision, err := NewFilterDecision(
FilterDecisionKindViolation,
"consumer.id", "filter.id", "rule.id",
evidence,
&intent,
)
if err != nil {
t.Fatalf("NewFilterDecision: %v", err)
}
// Create an evaluated outcome from the decision before any reassignment.
outcome, err := NewFilterOutcomeEvaluated(decision)
if err != nil {
t.Fatalf("NewFilterOutcomeEvaluated: %v", err)
}
// Reassign the original intent variable to a zero value.
intent = RecoveryIntent{}
// The decision must still validate and retain its original intent values.
if err := decision.Validate(); err != nil {
t.Fatalf("decision should survive caller intent reassignment: %v", err)
}
d := decision.Intent()
if d == nil {
t.Fatal("decision Intent() returned nil")
}
if d.Strategy() != RecoveryStrategyExactReplay {
t.Fatalf("decision Intent strategy should be exact_replay, got %s", d.Strategy())
}
if d.Reason() != "reason" {
t.Fatalf("decision Intent reason should be 'reason', got %s", d.Reason())
}
if d.Priority() != 0 {
t.Fatalf("decision Intent priority should be 0, got %d", d.Priority())
}
// The evaluated outcome must also survive and retain nested intent values.
if err := outcome.Validate(); err != nil {
t.Fatalf("outcome should survive caller intent reassignment: %v", err)
}
oDecision := outcome.Decision()
if oDecision == nil {
t.Fatal("outcome Decision() returned nil")
}
oIntent := oDecision.Intent()
if oIntent == nil {
t.Fatal("outcome decision Intent() returned nil")
}
if oIntent.Strategy() != RecoveryStrategyExactReplay {
t.Fatalf("outcome decision Intent strategy should be exact_replay, got %s", oIntent.Strategy())
}
if oIntent.Reason() != "reason" {
t.Fatalf("outcome decision Intent reason should be 'reason', got %s", oIntent.Reason())
}
if oIntent.Priority() != 0 {
t.Fatalf("outcome decision Intent priority should be 0, got %d", oIntent.Priority())
}
}
// TestFilterOutcomeValidationRejectsEachForbiddenField verifies that each
// FilterOutcome variant rejects exactly the forbidden field set for its kind
// when injected independently.
func TestFilterOutcomeValidationRejectsEachForbiddenField(t *testing.T) {
ts := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
evidence := buildValidEvidence(t, ts)
decision, err := NewFilterDecision(
FilterDecisionKindPass,
"consumer.id", "filter.id", "rule.id",
evidence, nil,
)
if err != nil {
t.Fatalf("NewFilterDecision: %v", err)
}
errCode := mustToken("err.code")
tests := []struct {
name string
make func() FilterOutcome
}{
{
name: "evaluated_with_errorCode",
make: func() FilterOutcome {
return FilterOutcome{
kind: FilterOutcomeKindEvaluated,
decision: &decision,
errorCode: errCode,
}
},
},
{
name: "evaluation_error_with_decision",
make: func() FilterOutcome {
return FilterOutcome{
kind: FilterOutcomeKindEvaluationError,
decision: &decision,
errorCode: errCode,
}
},
},
{
name: "not_applicable_with_decision",
make: func() FilterOutcome {
return FilterOutcome{
kind: FilterOutcomeKindNotApplicableForEpoch,
decision: &decision,
}
},
},
{
name: "not_applicable_with_errorCode",
make: func() FilterOutcome {
return FilterOutcome{
kind: FilterOutcomeKindNotApplicableForEpoch,
errorCode: errCode,
}
},
},
{
name: "deferred_with_decision",
make: func() FilterOutcome {
return FilterOutcome{
kind: FilterOutcomeKindDeferredByRequirement,
decision: &decision,
}
},
},
{
name: "deferred_with_errorCode",
make: func() FilterOutcome {
return FilterOutcome{
kind: FilterOutcomeKindDeferredByRequirement,
errorCode: errCode,
}
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if err := tc.make().Validate(); err == nil {
t.Fatalf("%s should be rejected", tc.name)
}
})
}
}
// TestRecoveryDirectiveValidationRejectsEachForbiddenField verifies that each
// RecoveryDirective variant rejects exactly the forbidden fields for its kind
// when injected independently.
func TestRecoveryDirectiveValidationRejectsEachForbiddenField(t *testing.T) {
exactRef := mustToken("req.ref")
snapRef := mustToken("snap.ref")
schemaRef := mustToken("schema.ref")
patchCode := mustToken("patch.code")
tests := []struct {
name string
make func() RecoveryDirective
}{
{
name: "exact_with_cursor",
make: func() RecoveryDirective {
return RecoveryDirective{
kind: RecoveryDirectiveKindExact,
requestRef: exactRef,
cursor: 5,
}
},
},
{
name: "exact_with_snapshotRef",
make: func() RecoveryDirective {
return RecoveryDirective{
kind: RecoveryDirectiveKindExact,
requestRef: exactRef,
snapshotRef: snapRef,
}
},
},
{
name: "exact_with_schemaRef",
make: func() RecoveryDirective {
return RecoveryDirective{
kind: RecoveryDirectiveKindExact,
requestRef: exactRef,
schemaRef: schemaRef,
}
},
},
{
name: "exact_with_patchCode",
make: func() RecoveryDirective {
return RecoveryDirective{
kind: RecoveryDirectiveKindExact,
requestRef: exactRef,
patchCode: patchCode,
}
},
},
{
name: "continuation_with_requestRef",
make: func() RecoveryDirective {
return RecoveryDirective{
kind: RecoveryDirectiveKindContinuation,
requestRef: exactRef,
snapshotRef: snapRef,
cursor: 0,
}
},
},
{
name: "continuation_with_schemaRef",
make: func() RecoveryDirective {
return RecoveryDirective{
kind: RecoveryDirectiveKindContinuation,
snapshotRef: snapRef,
schemaRef: schemaRef,
cursor: 0,
}
},
},
{
name: "continuation_with_patchCode",
make: func() RecoveryDirective {
return RecoveryDirective{
kind: RecoveryDirectiveKindContinuation,
snapshotRef: snapRef,
patchCode: patchCode,
cursor: 0,
}
},
},
{
name: "schema_with_requestRef",
make: func() RecoveryDirective {
return RecoveryDirective{
kind: RecoveryDirectiveKindSchema,
requestRef: exactRef,
schemaRef: schemaRef,
patchCode: patchCode,
}
},
},
{
name: "schema_with_cursor",
make: func() RecoveryDirective {
return RecoveryDirective{
kind: RecoveryDirectiveKindSchema,
schemaRef: schemaRef,
patchCode: patchCode,
cursor: 5,
}
},
},
{
name: "schema_with_snapshotRef",
make: func() RecoveryDirective {
return RecoveryDirective{
kind: RecoveryDirectiveKindSchema,
snapshotRef: snapRef,
schemaRef: schemaRef,
patchCode: patchCode,
}
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if err := tc.make().Validate(); err == nil {
t.Fatalf("%s should be rejected", tc.name)
}
})
}
}
// TestFilterAndTerminalConstructorsProduceValidValues verifies that all public
// constructors in the filter/terminal domain produce values that pass their
// own Validate, covering every normal variant matrix.
func TestFilterAndTerminalConstructorsProduceValidValues(t *testing.T) {
ts := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
// --- FilterDecision: all kinds with valid evidence ---
evidence := buildValidEvidence(t, ts)
kinds := []FilterDecisionKind{
FilterDecisionKindPass,
FilterDecisionKindObserve,
FilterDecisionKindViolation,
FilterDecisionKindFatal,
FilterDecisionKindReplacement,
}
for _, k := range kinds {
t.Run("FilterDecision_"+string(k), func(t *testing.T) {
d, err := NewFilterDecision(k, "consumer.id", "filter.id", "rule.id", evidence, nil)
if err != nil {
t.Fatalf("NewFilterDecision %s: %v", k, err)
}
if err := d.Validate(); err != nil {
t.Fatalf("FilterDecision %s failed Validate: %v", k, err)
}
})
}
// --- FilterDecision with violation + recovery intent ---
directive, err := NewRecoveryDirectiveExact("req.ref")
if err != nil {
t.Fatalf("NewRecoveryDirectiveExact: %v", err)
}
intent, err := NewRecoveryIntent(RecoveryStrategyExactReplay, directive, "reason", 0)
if err != nil {
t.Fatalf("NewRecoveryIntent: %v", err)
}
d, err := NewFilterDecision(FilterDecisionKindViolation, "consumer.id", "filter.id", "rule.id", evidence, &intent)
if err != nil {
t.Fatalf("NewFilterDecision violation+intent: %v", err)
}
if err := d.Validate(); err != nil {
t.Fatalf("FilterDecision violation+intent failed Validate: %v", err)
}
// --- FilterOutcome: all kinds ---
t.Run("FilterOutcome_evaluated", func(t *testing.T) {
o, err := NewFilterOutcomeEvaluated(d)
if err != nil {
t.Fatalf("NewFilterOutcomeEvaluated: %v", err)
}
if err := o.Validate(); err != nil {
t.Fatalf("FilterOutcome evaluated failed Validate: %v", err)
}
})
t.Run("FilterOutcome_evaluation_error", func(t *testing.T) {
o, err := NewFilterOutcomeEvaluationError("err.code")
if err != nil {
t.Fatalf("NewFilterOutcomeEvaluationError: %v", err)
}
if err := o.Validate(); err != nil {
t.Fatalf("FilterOutcome evaluation_error failed Validate: %v", err)
}
})
t.Run("FilterOutcome_not_applicable", func(t *testing.T) {
o := NewFilterOutcomeNotApplicableForEpoch()
if err := o.Validate(); err != nil {
t.Fatalf("FilterOutcome not_applicable failed Validate: %v", err)
}
})
t.Run("FilterOutcome_deferred", func(t *testing.T) {
o := NewFilterOutcomeDeferredByRequirement()
if err := o.Validate(); err != nil {
t.Fatalf("FilterOutcome deferred failed Validate: %v", err)
}
})
// --- RecoveryDirective: all kinds ---
t.Run("RecoveryDirective_exact", func(t *testing.T) {
dir, err := NewRecoveryDirectiveExact("req.ref")
if err != nil {
t.Fatalf("NewRecoveryDirectiveExact: %v", err)
}
if err := dir.Validate(); err != nil {
t.Fatalf("RecoveryDirective exact failed Validate: %v", err)
}
})
t.Run("RecoveryDirective_continuation", func(t *testing.T) {
dir, err := NewRecoveryDirectiveContinuation(10, "snap.ref")
if err != nil {
t.Fatalf("NewRecoveryDirectiveContinuation: %v", err)
}
if err := dir.Validate(); err != nil {
t.Fatalf("RecoveryDirective continuation failed Validate: %v", err)
}
})
t.Run("RecoveryDirective_schema", func(t *testing.T) {
dir, err := NewRecoveryDirectiveSchema("schema.ref", "patch.code")
if err != nil {
t.Fatalf("NewRecoveryDirectiveSchema: %v", err)
}
if err := dir.Validate(); err != nil {
t.Fatalf("RecoveryDirective schema failed Validate: %v", err)
}
})
// --- RecoveryIntent: all strategies ---
strategies := []struct {
strategy RecoveryStrategy
directive RecoveryDirective
}{
{RecoveryStrategyExactReplay, mustBuildExactDirective(t)},
{RecoveryStrategyContinuationRepair, mustBuildContinuationDirective(t)},
{RecoveryStrategySchemaRepair, mustBuildSchemaDirective(t)},
}
for _, s := range strategies {
t.Run("RecoveryIntent_"+string(s.strategy), func(t *testing.T) {
intent, err := NewRecoveryIntent(s.strategy, s.directive, "reason", 0)
if err != nil {
t.Fatalf("NewRecoveryIntent %s: %v", s.strategy, err)
}
if err := intent.Validate(); err != nil {
t.Fatalf("RecoveryIntent %s failed Validate: %v", s.strategy, err)
}
})
}
// --- SanitizedEvidence: valid variant ---
t.Run("SanitizedEvidence", func(t *testing.T) {
e, err := NewSanitizedEvidence(
EventKindTextDelta, "ch", "filter.rule", "desc.code",
[32]byte{1}, 0, 0,
FilterOutcomeKindEvaluated, ts,
)
if err != nil {
t.Fatalf("NewSanitizedEvidence: %v", err)
}
if err := e.Validate(); err != nil {
t.Fatalf("SanitizedEvidence failed Validate: %v", err)
}
})
// --- FailureCause: required-only (stage + code, optional fields empty) ---
t.Run("FailureCause_required_only", func(t *testing.T) {
fc, err := NewFailureCause("stage", "code", "", "", "")
if err != nil {
t.Fatalf("NewFailureCause required-only: %v", err)
}
if err := fc.Validate(); err != nil {
t.Fatalf("FailureCause required-only failed Validate: %v", err)
}
})
// --- FailureCause: optional-full (all fields provided) ---
t.Run("FailureCause_optional_full", func(t *testing.T) {
fc, err := NewFailureCause("stage", "code", "consumer", "filter", "rule")
if err != nil {
t.Fatalf("NewFailureCause optional-full: %v", err)
}
if err := fc.Validate(); err != nil {
t.Fatalf("FailureCause optional-full failed Validate: %v", err)
}
})
// --- ExternalDescriptor: required-only (errorType + message, optional fields empty) ---
t.Run("ExternalDescriptor_required_only", func(t *testing.T) {
ed, err := NewExternalDescriptor("error.type", "", "message", "")
if err != nil {
t.Fatalf("NewExternalDescriptor required-only: %v", err)
}
if err := ed.Validate(); err != nil {
t.Fatalf("ExternalDescriptor required-only failed Validate: %v", err)
}
})
// --- ExternalDescriptor: optional-full (all fields provided) ---
t.Run("ExternalDescriptor_optional_full", func(t *testing.T) {
ed, err := NewExternalDescriptor("error.type", "code", "message", "param")
if err != nil {
t.Fatalf("NewExternalDescriptor optional-full: %v", err)
}
if err := ed.Validate(); err != nil {
t.Fatalf("ExternalDescriptor optional-full failed Validate: %v", err)
}
})
// --- Invalid inputs must be rejected by constructors ---
t.Run("NewFilterDecision_unknownKind", func(t *testing.T) {
_, err := NewFilterDecision(FilterDecisionKind("bad"), "consumer.id", "filter.id", "rule.id", evidence, nil)
if err == nil {
t.Fatal("NewFilterDecision unknown kind should return error")
}
})
t.Run("NewFilterDecision_nonViolationWithIntent", func(t *testing.T) {
_, err := NewFilterDecision(FilterDecisionKindPass, "consumer.id", "filter.id", "rule.id", evidence, &intent)
if err == nil {
t.Fatal("NewFilterDecision non-violation with intent should return error")
}
})
t.Run("NewFilterOutcomeEvaluated_invalidDecision", func(t *testing.T) {
badDecision := FilterDecision{
kind: FilterDecisionKindPass,
consumerID: StableToken{value: "INVALID"},
filterID: mustToken("filter.id"),
ruleID: mustToken("rule.id"),
evidence: evidence,
}
_, err := NewFilterOutcomeEvaluated(badDecision)
if err == nil {
t.Fatal("NewFilterOutcomeEvaluated with invalid decision should return error")
}
})
t.Run("NewFilterOutcomeEvaluationError_emptyCode", func(t *testing.T) {
_, err := NewFilterOutcomeEvaluationError("")
if err == nil {
t.Fatal("NewFilterOutcomeEvaluationError with empty code should return error")
}
})
t.Run("NewRecoveryDirectiveExact_emptyRequestID", func(t *testing.T) {
_, err := NewRecoveryDirectiveExact("")
if err == nil {
t.Fatal("NewRecoveryDirectiveExact with empty requestID should return error")
}
})
t.Run("NewRecoveryDirectiveContinuation_negativeCursor", func(t *testing.T) {
_, err := NewRecoveryDirectiveContinuation(-1, "snap.ref")
if err == nil {
t.Fatal("NewRecoveryDirectiveContinuation with negative cursor should return error")
}
})
t.Run("NewRecoveryDirectiveSchema_emptySchemaRef", func(t *testing.T) {
_, err := NewRecoveryDirectiveSchema("", "patch.code")
if err == nil {
t.Fatal("NewRecoveryDirectiveSchema with empty schemaRef should return error")
}
})
t.Run("NewRecoveryIntent_emptyReason", func(t *testing.T) {
exactDir, err := NewRecoveryDirectiveExact("req.ref")
if err != nil {
t.Fatalf("NewRecoveryDirectiveExact: %v", err)
}
_, err = NewRecoveryIntent(RecoveryStrategyExactReplay, exactDir, "", 0)
if err == nil {
t.Fatal("NewRecoveryIntent with empty reason should return error")
}
})
t.Run("NewSanitizedEvidence_zeroFingerprint", func(t *testing.T) {
_, err := NewSanitizedEvidence(
EventKindTextDelta, "ch", "filter.rule", "desc.code",
[32]byte{}, 0, 0,
FilterOutcomeKindEvaluated, ts,
)
if err == nil {
t.Fatal("NewSanitizedEvidence with zero fingerprint should return error")
}
})
t.Run("NewFailureCause_invalidStage", func(t *testing.T) {
_, err := NewFailureCause("INVALID", "code", "", "", "")
if err == nil {
t.Fatal("NewFailureCause invalid stage should return error")
}
})
t.Run("NewExternalDescriptor_invalidErrorType", func(t *testing.T) {
_, err := NewExternalDescriptor("INVALID", "code", "message", "")
if err == nil {
t.Fatal("NewExternalDescriptor invalid errorType should return error")
}
})
}
// --- Additional helpers for TestFilterAndTerminalConstructorsProduceValidValues ---
func mustBuildContinuationDirective(t *testing.T) RecoveryDirective {
t.Helper()
d, err := NewRecoveryDirectiveContinuation(10, "snap.ref")
if err != nil {
t.Fatalf("mustBuildContinuationDirective: %v", err)
}
return d
}
func mustBuildSchemaDirective(t *testing.T) RecoveryDirective {
t.Helper()
d, err := NewRecoveryDirectiveSchema("schema.ref", "patch.code")
if err != nil {
t.Fatalf("mustBuildSchemaDirective: %v", err)
}
return d
}