package observation import ( "fmt" "testing" "time" "github.com/tegwick/fluid-core/internal/contract" ) var base = time.Date(2026, 9, 4, 10, 0, 0, 0, time.UTC) func req(consumer string, offset time.Duration, method, route string, status int64) contract.FluidTelemetry { cohort := contract.CohortID("coding-agents") s := status return contract.FluidTelemetry{ ID: fmt.Sprintf("tl-%s-%d", consumer, offset), OccurredAt: base.Add(offset), Kind: contract.FluidTelemetryKindRequest, ConsumerRef: consumer, Cohort: &cohort, Request: &contract.FluidTelemetryRequest{Route: route, Method: method, Status: &s}, } } func errEv(consumer string, offset time.Duration, route string, class contract.FluidTelemetryErrorClass) contract.FluidTelemetry { ev := req(consumer, offset, "GET", route, 400) ev.Kind = contract.FluidTelemetryKindError ev.Error = &contract.FluidTelemetryError{Class: class} return ev } // TestDetectsInefficientUsagePattern reproduces the Blueprint section 33 // worked example: consumers listing everything to find one record. func TestDetectsInefficientUsagePattern(t *testing.T) { a := NewTopologyAnalyzer() var events []contract.FluidTelemetry for _, consumer := range []string{"c-1", "c-2", "c-3"} { for chain := 0; chain < 2; chain++ { start := time.Duration(chain) * time.Hour events = append(events, req(consumer, start, "GET", "/customers/{id}/invoices", 200), req(consumer, start+time.Second, "GET", "/customers/{id}/invoices", 200), req(consumer, start+2*time.Second, "GET", "/customers/{id}/invoices", 200), ) } } patterns := a.Patterns(events) if len(patterns) == 0 { t.Fatal("no pattern detected in a clearly repeated shape") } p := patterns[0] if p.Consumers != 3 { t.Errorf("independent consumers = %d, want 3", p.Consumers) } if p.Count != 6 { t.Errorf("occurrences = %d, want 6", p.Count) } // The repeated route is the signal that the consumer is compensating. if p.MaxRepeats != 3 { t.Errorf("max repeats = %d, want 3", p.MaxRepeats) } if p.RepeatedStep != "GET /customers/{id}/invoices" { t.Errorf("repeated step = %q", p.RepeatedStep) } } // TestOneConsumerRepeatingIsNotAPattern: a single client looping is a client // bug; several independent consumers converging is interface pressure. func TestOneConsumerRepeatingIsNotAPattern(t *testing.T) { a := NewTopologyAnalyzer() var events []contract.FluidTelemetry for chain := 0; chain < 10; chain++ { start := time.Duration(chain) * time.Hour events = append(events, req("c-1", start, "GET", "/entries", 200), req("c-1", start+time.Second, "GET", "/entries", 200), ) } if patterns := a.Patterns(events); len(patterns) != 0 { t.Errorf("a single consumer's loop was reported as a pattern: %+v", patterns) } } func TestChainGapSplitsInteractions(t *testing.T) { a := NewTopologyAnalyzer() a.ChainGap = 10 * time.Second events := []contract.FluidTelemetry{ req("c-1", 0, "GET", "/a", 200), req("c-1", 2*time.Second, "GET", "/b", 200), // Well past the gap: a new interaction, not a continuation. req("c-1", time.Minute, "GET", "/c", 200), } interactions := a.Interactions(events) if len(interactions) != 2 { t.Fatalf("got %d interactions, want 2", len(interactions)) } if len(interactions[0].Steps) != 2 || len(interactions[1].Steps) != 1 { t.Errorf("steps split wrongly: %d and %d", len(interactions[0].Steps), len(interactions[1].Steps)) } } func TestExplicitChainIDOverridesTiming(t *testing.T) { a := NewTopologyAnalyzer() a.ChainGap = time.Second withChain := func(ev contract.FluidTelemetry, id string) contract.FluidTelemetry { ev.Sequence = &contract.FluidTelemetrySequence{ChainID: id} return ev } // Two calls an hour apart, but the consumer says they are one task. events := []contract.FluidTelemetry{ withChain(req("c-1", 0, "GET", "/a", 200), "chain-1"), withChain(req("c-1", time.Hour, "GET", "/b", 200), "chain-1"), } interactions := a.Interactions(events) if len(interactions) != 1 { t.Fatalf("an explicit chain id was split by timing: got %d interactions", len(interactions)) } } // TestRecoveredErrorIsDistinguished: invalid request, then a corrected retry, // is a recoverable misunderstanding rather than a hard failure, and the two // deserve different hypotheses. func TestRecoveredErrorIsDistinguished(t *testing.T) { a := NewTopologyAnalyzer() var events []contract.FluidTelemetry for _, consumer := range []string{"c-1", "c-2", "c-3"} { for chain := 0; chain < 2; chain++ { start := time.Duration(chain) * time.Hour events = append(events, errEv(consumer, start, "/entries", contract.FluidTelemetryErrorClassValidation), req(consumer, start+time.Second, "GET", "/entries", 200), ) } } patterns := a.Patterns(events) if len(patterns) == 0 { t.Fatal("no pattern detected") } if patterns[0].RecoveredError != contract.FluidTelemetryErrorClassValidation { t.Errorf("recovered error = %q, want validation", patterns[0].RecoveredError) } } func TestPatternsAreDeterministicallyOrdered(t *testing.T) { a := NewTopologyAnalyzer() var events []contract.FluidTelemetry for _, consumer := range []string{"c-1", "c-2", "c-3"} { for chain := 0; chain < 5; chain++ { start := time.Duration(chain) * time.Hour events = append(events, req(consumer, start, "GET", "/frequent", 200)) } for chain := 0; chain < 2; chain++ { start := time.Duration(chain+10) * time.Hour events = append(events, req(consumer, start, "GET", "/rare", 200)) } } first := a.Patterns(events) if len(first) < 2 { t.Fatalf("expected two patterns, got %d", len(first)) } // Biggest signal first, so an analyst reads the important thing first. if first[0].Count < first[1].Count { t.Error("patterns are not ordered by frequency") } for i := 0; i < 20; i++ { again := a.Patterns(events) for j := range first { if again[j].Signature != first[j].Signature { t.Fatal("pattern ordering varied between runs") } } } } func TestCohortPopulationsRespectMinimumSize(t *testing.T) { policy := testPolicy() e := NewCohortEngine("unclassified", policy) var events []contract.FluidTelemetry // One cohort with a single consumer, one with plenty. small := contract.CohortID("lone-partner") for i := 0; i < 20; i++ { ev := req("only-one", time.Duration(i)*time.Second, "GET", "/a", 200) ev.Cohort = &small events = append(events, ev) } big := contract.CohortID("agents") for i := 0; i < 20; i++ { ev := req(fmt.Sprintf("c-%d", i), time.Duration(i)*time.Second, "GET", "/a", 200) ev.Cohort = &big events = append(events, ev) } pops := e.Populations(events) byCohort := map[contract.CohortID]Population{} for _, p := range pops { byCohort[p.Cohort] = p } if !byCohort[small].Suppressed { t.Error("a cohort of one consumer was reportable") } if byCohort[big].Suppressed { t.Error("a cohort of twenty consumers was suppressed") } }