iop/packages/go/streamgate/runtime_contract_test.go
toki d182eafeff feat: stream evidence gate core - full implementation
- Stream evidence gate routing in edge config and runtime
- Ingress snapshot and allocation
- Recovery coordinator and plan
- Runtime contract for gate filters
- OpenAI-compatible request rebuilder and stream gate dispatcher
- Comprehensive tests for all new components
- Updated contracts and roadmap milestones
2026-07-26 20:48:53 +09:00

405 lines
12 KiB
Go

package streamgate
import (
"context"
"errors"
"testing"
"time"
)
func TestNoopFilterContract(t *testing.T) {
t.Run("DefaultConstructor", func(t *testing.T) {
filter, err := NewNoopFilter()
if err != nil {
t.Fatalf("NewNoopFilter failed: %v", err)
}
if filter.ID() != DefaultNoopFilterID {
t.Errorf("ID() = %q, want %q", filter.ID(), DefaultNoopFilterID)
}
builder := NewFilterContextBuilder("gen-1", "att-1")
fctx, err := builder.Build()
if err != nil {
t.Fatalf("Build FilterContext failed: %v", err)
}
if !filter.Applies(fctx) {
t.Errorf("Applies() = false, want true")
}
holdReq := filter.HoldRequirement(fctx)
if holdReq.Mode() != FilterHoldModeNone {
t.Errorf("HoldRequirement.Mode() = %v, want %v", holdReq.Mode(), FilterHoldModeNone)
}
if holdReq.Channel() != DefaultNoopChannel {
t.Errorf("HoldRequirement.Channel() = %q, want %q", holdReq.Channel(), DefaultNoopChannel)
}
})
t.Run("CustomChannelConstructor", func(t *testing.T) {
filter, err := NewNoopFilterWithChannel("custom_noop", "custom_ch")
if err != nil {
t.Fatalf("NewNoopFilterWithChannel failed: %v", err)
}
if filter.ID() != "custom_noop" {
t.Errorf("ID() = %q, want custom_noop", filter.ID())
}
builder := NewFilterContextBuilder("gen-1", "att-1")
fctx, _ := builder.Build()
holdReq := filter.HoldRequirement(fctx)
if holdReq.Channel() != "custom_ch" {
t.Errorf("HoldRequirement.Channel() = %q, want custom_ch", holdReq.Channel())
}
})
t.Run("EvaluatePassDecision", func(t *testing.T) {
filter, _ := NewNoopFilter()
builder := NewFilterContextBuilder("gen-1", "att-1")
fctx, _ := builder.Build()
ev, err := NewTextDeltaEvent("ch", "hello", time.Now())
if err != nil {
t.Fatalf("NewTextDeltaEvent failed: %v", err)
}
batch, err := NewEvidenceBatch(
[]NormalizedEvent{ev},
nil, nil, nil, false,
CommitStateTransportUncommitted,
time.Now(),
)
if err != nil {
t.Fatalf("NewEvidenceBatch failed: %v", err)
}
ctx := context.Background()
decision, err := filter.Evaluate(ctx, fctx, batch)
if err != nil {
t.Fatalf("Evaluate failed: %v", err)
}
if decision.Kind() != FilterDecisionKindPass {
t.Errorf("Decision Kind = %v, want %v", decision.Kind(), FilterDecisionKindPass)
}
if decision.FilterID() != DefaultNoopFilterID {
t.Errorf("Decision FilterID = %q, want %q", decision.FilterID(), DefaultNoopFilterID)
}
if decision.ConsumerID() != DefaultNoopConsumerID {
t.Errorf("Decision ConsumerID = %q, want %q", decision.ConsumerID(), DefaultNoopConsumerID)
}
if decision.RuleID() != DefaultNoopRuleID {
t.Errorf("Decision RuleID = %q, want %q", decision.RuleID(), DefaultNoopRuleID)
}
if decision.Intent() != nil {
t.Errorf("Decision Intent must be nil for pass decision")
}
})
t.Run("EvaluateContextCancelled", func(t *testing.T) {
filter, _ := NewNoopFilter()
builder := NewFilterContextBuilder("gen-1", "att-1")
fctx, _ := builder.Build()
ev, _ := NewTextDeltaEvent("ch", "hello", time.Now())
batch, _ := NewEvidenceBatch(
[]NormalizedEvent{ev},
nil, nil, nil, false,
CommitStateTransportUncommitted,
time.Now(),
)
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := filter.Evaluate(ctx, fctx, batch)
if err == nil {
t.Errorf("Evaluate with cancelled context expected error, got nil")
}
})
}
func TestRuntimeOptionsValidation(t *testing.T) {
t.Run("DefaultOptionsValid", func(t *testing.T) {
opts := DefaultRuntimeOptions()
if err := opts.Validate(); err != nil {
t.Fatalf("DefaultRuntimeOptions validation failed: %v", err)
}
if opts.MaxEvidenceRunes() != DefaultMaxEvidenceRunes {
t.Errorf("MaxEvidenceRunes = %d, want %d", opts.MaxEvidenceRunes(), DefaultMaxEvidenceRunes)
}
if opts.MaxBufferRunes() != DefaultMaxBufferRunes {
t.Errorf("MaxBufferRunes = %d, want %d", opts.MaxBufferRunes(), DefaultMaxBufferRunes)
}
if opts.MaxIngressSnapshotBytes() != DefaultMaxIngressSnapshotBytes {
t.Errorf("MaxIngressSnapshotBytes = %d, want %d", opts.MaxIngressSnapshotBytes(), DefaultMaxIngressSnapshotBytes)
}
if opts.MaxRecoveryAttemptsTotal() != DefaultMaxRecoveryAttemptsTotal {
t.Errorf("MaxRecoveryAttemptsTotal = %d, want %d", opts.MaxRecoveryAttemptsTotal(), DefaultMaxRecoveryAttemptsTotal)
}
})
t.Run("InvalidRunes", func(t *testing.T) {
_, err := NewRuntimeOptions(0, 4096, 1024, 3, GateCoordinatorOptions{}, RecoveryCoordinatorOptions{})
if err == nil {
t.Errorf("Expected error for 0 max evidence runes")
}
_, err = NewRuntimeOptions(500, 100, 1024, 3, GateCoordinatorOptions{}, RecoveryCoordinatorOptions{})
if err == nil {
t.Errorf("Expected error for maxBuffer < maxEvidence")
}
})
t.Run("InvalidIngressBytes", func(t *testing.T) {
_, err := NewRuntimeOptions(500, 4096, 0, 3, GateCoordinatorOptions{}, RecoveryCoordinatorOptions{})
if err == nil {
t.Errorf("Expected error for 0 max ingress bytes")
}
})
t.Run("IngressAbsoluteCapBoundaries", func(t *testing.T) {
limit := int64(DefaultMaxIngressSnapshotBytes)
// limit-1 should succeed
_, err := NewRuntimeOptions(500, 4096, limit-1, 3, GateCoordinatorOptions{}, RecoveryCoordinatorOptions{})
if err != nil {
t.Errorf("NewRuntimeOptions(limit-1) unexpected error: %v", err)
}
// limit should succeed
_, err = NewRuntimeOptions(500, 4096, limit, 3, GateCoordinatorOptions{}, RecoveryCoordinatorOptions{})
if err != nil {
t.Errorf("NewRuntimeOptions(limit) unexpected error: %v", err)
}
// limit+1 should fail
_, err = NewRuntimeOptions(500, 4096, limit+1, 3, GateCoordinatorOptions{}, RecoveryCoordinatorOptions{})
if err == nil {
t.Errorf("Expected error for max ingress bytes > 16 MiB")
}
})
t.Run("DirectValidateRejectsIngressLimitPlusOne", func(t *testing.T) {
opts := DefaultRuntimeOptions()
opts.maxIngressSnapshotBytes = DefaultMaxIngressSnapshotBytes + 1
if err := opts.Validate(); err == nil {
t.Errorf("Direct Validate() with limit+1 bytes should fail")
}
})
t.Run("InvalidRecoveryAttempts", func(t *testing.T) {
_, err := NewRuntimeOptions(500, 4096, 1024, 4, GateCoordinatorOptions{}, RecoveryCoordinatorOptions{})
if err == nil {
t.Errorf("Expected error for max recovery attempts > 3")
}
_, err = NewRuntimeOptions(500, 4096, 1024, -1, GateCoordinatorOptions{}, RecoveryCoordinatorOptions{})
if err == nil {
t.Errorf("Expected error for negative max recovery attempts")
}
})
}
type fakeDispatcher struct{}
func (fakeDispatcher) DispatchAttempt(ctx context.Context, request RebuiltRequest) (AttemptBinding, error) {
return AttemptBinding{}, errors.New("fake dispatch")
}
type fakeRebuilder struct{}
func (fakeRebuilder) RebuildRequest(ctx context.Context, snapshot RecoveryRequestSnapshotRef, plan RecoveryPlan) (RebuiltRequestDraft, error) {
return RebuiltRequestDraft{}, errors.New("fake rebuild")
}
type fakeSink struct{}
func (fakeSink) CommitResponseStart(ctx context.Context, rs ResponseStart) (CommitState, error) {
return CommitStateTransportUncommitted, nil
}
func (fakeSink) Release(ctx context.Context, ev ReleaseEvent) (CommitState, error) {
return CommitStateStreamOpen, nil
}
func (fakeSink) CommitTerminal(ctx context.Context, tr TerminalResult) (CommitState, error) {
return CommitStateTerminalCommitted, nil
}
func TestRequestRuntimeSnapshotContract(t *testing.T) {
regSnap, err := NewFilterRegistrySnapshot("gen-1", nil, nil)
if err != nil {
t.Fatalf("NewFilterRegistrySnapshot failed: %v", err)
}
snapRef, err := NewRecoveryRequestSnapshotRef("snap.ref.1", 100, 200, 1024)
if err != nil {
t.Fatalf("NewRecoveryRequestSnapshotRef failed: %v", err)
}
opts := DefaultRuntimeOptions()
disp := fakeDispatcher{}
rebuilder := fakeRebuilder{}
sink := fakeSink{}
t.Run("ValidConstruction", func(t *testing.T) {
reqSnap, err := NewRequestRuntimeSnapshot(
"req-123",
"gen-1",
"prod",
"v1/chat/completions",
"gpt4",
opts,
regSnap,
nil,
snapRef,
disp,
rebuilder,
nil,
sink,
)
if err != nil {
t.Fatalf("NewRequestRuntimeSnapshot failed: %v", err)
}
if reqSnap.RequestID() != "req-123" {
t.Errorf("RequestID() = %q, want req-123", reqSnap.RequestID())
}
if reqSnap.ConfigGeneration() != "gen-1" {
t.Errorf("ConfigGeneration() = %q, want gen-1", reqSnap.ConfigGeneration())
}
if reqSnap.Environment() != "prod" {
t.Errorf("Environment() = %q, want prod", reqSnap.Environment())
}
if reqSnap.Endpoint() != "v1/chat/completions" {
t.Errorf("Endpoint() = %q, want v1/chat/completions", reqSnap.Endpoint())
}
if reqSnap.Family() != "gpt4" {
t.Errorf("Family() = %q, want gpt4", reqSnap.Family())
}
if reqSnap.Dispatcher() == nil || reqSnap.Rebuilder() == nil || reqSnap.Sink() == nil {
t.Errorf("Seam accessors returned nil")
}
if reqSnap.Preparer() != nil {
t.Errorf("Preparer() expected nil")
}
// Test BuildAttemptFilterContext
target, err := NewAttemptTarget("standard", "gpt-4-turbo", "openai", "primary", nil)
if err != nil {
t.Fatalf("NewAttemptTarget failed: %v", err)
}
fctx, err := reqSnap.BuildAttemptFilterContext("att-1", target, 1, CommitStateTransportUncommitted)
if err != nil {
t.Fatalf("BuildAttemptFilterContext failed: %v", err)
}
if fctx.AttemptID() != "att-1" {
t.Errorf("FilterContext AttemptID() = %q, want att-1", fctx.AttemptID())
}
if fctx.EpochID() != 1 {
t.Errorf("FilterContext EpochID() = %d, want 1", fctx.EpochID())
}
if fctx.StableCorrelation() != "req-123" {
t.Errorf("FilterContext StableCorrelation() = %q, want req-123", fctx.StableCorrelation())
}
if fctx.ActualModel() != "gpt-4-turbo" {
t.Errorf("FilterContext ActualModel() = %q, want gpt-4-turbo", fctx.ActualModel())
}
})
t.Run("GenerationMismatch", func(t *testing.T) {
// matching generation should succeed
_, err := NewRequestRuntimeSnapshot(
"req-123",
"gen-1",
"prod",
"v1/chat/completions",
"gpt4",
opts,
regSnap,
nil,
snapRef,
disp,
rebuilder,
nil,
sink,
)
if err != nil {
t.Fatalf("NewRequestRuntimeSnapshot with matching generation failed: %v", err)
}
// mismatching generation should fail
_, err = NewRequestRuntimeSnapshot(
"req-123",
"gen-2",
"prod",
"v1/chat/completions",
"gpt4",
opts,
regSnap,
nil,
snapRef,
disp,
rebuilder,
nil,
sink,
)
if err == nil {
t.Errorf("Expected error for config/registry generation mismatch")
}
})
t.Run("DirectValidateRejectsGenerationMismatch", func(t *testing.T) {
reqSnap, err := NewRequestRuntimeSnapshot(
"req-123",
"gen-1",
"prod",
"v1/chat/completions",
"gpt4",
opts,
regSnap,
nil,
snapRef,
disp,
rebuilder,
nil,
sink,
)
if err != nil {
t.Fatalf("NewRequestRuntimeSnapshot failed: %v", err)
}
// Tamper configGen to cause mismatch
reqSnap.configGen = StableToken{value: "gen-999"}
if err := reqSnap.Validate(); err == nil {
t.Errorf("Direct Validate() with mismatched generation should fail")
}
})
t.Run("ValidationErrors", func(t *testing.T) {
_, err := NewRequestRuntimeSnapshot(
"", "gen-1", "prod", "ep", "fam", opts, regSnap, nil, snapRef, disp, rebuilder, nil, sink,
)
if err == nil {
t.Errorf("Expected error for empty requestID")
}
_, err = NewRequestRuntimeSnapshot(
"req-1", "gen-1", "prod", "ep", "fam", opts, regSnap, nil, snapRef, nil, rebuilder, nil, sink,
)
if err == nil {
t.Errorf("Expected error for nil dispatcher")
}
_, err = NewRequestRuntimeSnapshot(
"req-1", "gen-1", "prod", "ep", "fam", opts, regSnap, nil, snapRef, disp, nil, nil, sink,
)
if err == nil {
t.Errorf("Expected error for nil rebuilder")
}
_, err = NewRequestRuntimeSnapshot(
"req-1", "gen-1", "prod", "ep", "fam", opts, regSnap, nil, snapRef, disp, rebuilder, nil, nil,
)
if err == nil {
t.Errorf("Expected error for nil sink")
}
})
}