package observation import ( "context" "errors" "fmt" "path/filepath" "testing" "time" _ "modernc.org/sqlite" "github.com/tegwick/fluid-core/internal/contract" "github.com/tegwick/fluid-core/internal/evidence" ) func newRegistry(t *testing.T) (*PressureRegistry, *evidence.SQLStore) { t.Helper() store, err := evidence.OpenSQLite(context.Background(), filepath.Join(t.TempDir(), "e.db")) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = store.Close() }) return NewPressureRegistry(store, "hall-publishing"), store } // inefficientTraffic reproduces the Blueprint section 33 shape: several // consumers listing a collection repeatedly to find one record. func inefficientTraffic() []contract.FluidTelemetry { var events []contract.FluidTelemetry for _, consumer := range []string{"c-1", "c-2", "c-3", "c-4"} { for chain := 0; chain < 3; chain++ { start := time.Duration(chain) * time.Hour for i := 0; i < 4; i++ { events = append(events, req(consumer, start+time.Duration(i)*time.Second, "GET", "/customers/{id}/invoices", 200)) } } } return events } func TestClassifierFindsInefficientUsage(t *testing.T) { c := NewClassifier(DefaultClassifierOptions(), NewTopologyAnalyzer()) findings := c.Classify(inefficientTraffic()) if len(findings) == 0 { t.Fatal("no findings from clearly inefficient traffic") } var found *Finding for i := range findings { if findings[i].Class == contract.PressureClassSuccessfulButInefficientUsage { found = &findings[i] } } if found == nil { t.Fatalf("inefficient usage not classified; got %v", classesOf(findings)) } if found.Consumers != 4 { t.Errorf("consumers = %d, want 4", found.Consumers) } if len(found.Evidence) == 0 { t.Error("a finding with no evidence references is not auditable") } if found.Confidence <= 0 || found.Confidence > 1 { t.Errorf("confidence out of range: %v", found.Confidence) } } func TestClassifierMapsErrorsToPressureClasses(t *testing.T) { c := NewClassifier(DefaultClassifierOptions(), NewTopologyAnalyzer()) var events []contract.FluidTelemetry for i, consumer := range []string{"c-1", "c-2", "c-3"} { for n := 0; n < 5; n++ { ev := errEv(consumer, time.Duration(i*10+n)*time.Minute, "/v1/latest", contract.FluidTelemetryErrorClassUnknownPath) events = append(events, ev) } } findings := c.Classify(events) if !hasClass(findings, contract.PressureClassMissingInterfaceCapability) { t.Errorf("repeated unknown-path errors did not yield missing capability; got %v", classesOf(findings)) } } // TestAuthorizationFailureIsNotPressure: the interface working as designed is // not a defect, and dressing it up as one would send the Daimon chasing a // change that must not happen. func TestAuthorizationFailureIsNotPressure(t *testing.T) { c := NewClassifier(DefaultClassifierOptions(), NewTopologyAnalyzer()) var events []contract.FluidTelemetry for i, consumer := range []string{"c-1", "c-2", "c-3"} { for n := 0; n < 10; n++ { events = append(events, errEv(consumer, time.Duration(i*10+n)*time.Minute, "/v1/admin", contract.FluidTelemetryErrorClassAuthorization)) } } for _, f := range c.Classify(events) { if len(f.Evidence) > 0 && f.Class == contract.PressureClassMissingInterfaceCapability { t.Errorf("authorization failures were classified as pressure: %+v", f) } } } func TestClassifierNeedsIndependentConsumers(t *testing.T) { c := NewClassifier(DefaultClassifierOptions(), NewTopologyAnalyzer()) var events []contract.FluidTelemetry for n := 0; n < 50; n++ { events = append(events, errEv("only-one", time.Duration(n)*time.Minute, "/v1/x", contract.FluidTelemetryErrorClassUnknownPath)) } if findings := c.Classify(events); len(findings) != 0 { t.Errorf("one consumer's behaviour became interface pressure: %v", classesOf(findings)) } } // TestRecordIsIdempotent is what stops re-analysing a window from minting a new // pressure record on every pass. func TestRecordIsIdempotent(t *testing.T) { ctx := context.Background() reg, _ := newRegistry(t) c := NewClassifier(DefaultClassifierOptions(), NewTopologyAnalyzer()) findings := c.Classify(inefficientTraffic()) if len(findings) == 0 { t.Fatal("no findings") } first, err := reg.RecordAll(ctx, findings) if err != nil { t.Fatal(err) } second, err := reg.RecordAll(ctx, findings) if err != nil { t.Fatal(err) } if len(first) != len(second) { t.Fatalf("record counts differ between runs: %d then %d", len(first), len(second)) } for i := range first { if first[i].ID != second[i].ID { t.Errorf("re-recording minted a new id: %s then %s", first[i].ID, second[i].ID) } } all, err := reg.List(ctx, "") if err != nil { t.Fatal(err) } if len(all) != len(first) { t.Errorf("registry holds %d records after two identical runs, want %d", len(all), len(first)) } } // TestFirstSeenIsNotOverwritten: how long the interface has had a problem is // evidence, and it is what makes an old pressure worth prioritizing. func TestFirstSeenIsNotOverwritten(t *testing.T) { ctx := context.Background() reg, _ := newRegistry(t) old := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) f := Finding{ Class: contract.PressureClassPoorDiscoverability, Summary: "consumers repeatedly send an unsupported filter", Consumers: 3, Occurrences: 30, Severity: 0.4, Confidence: 0.6, Evidence: []contract.EvidenceRef{"route:GET /entries"}, FirstSeen: old, LastSeen: old.Add(time.Hour), Fingerprint: "poor:GET /entries", } if _, err := reg.Record(ctx, f); err != nil { t.Fatal(err) } later := f later.FirstSeen = old.AddDate(0, 6, 0) later.LastSeen = old.AddDate(0, 6, 1) got, err := reg.Record(ctx, later) if err != nil { t.Fatal(err) } if !got.FirstSeen.Equal(old) { t.Errorf("first_seen was overwritten: %s, want %s", got.FirstSeen, old) } if !got.LastSeen.Equal(later.LastSeen) { t.Errorf("last_seen was not extended: %s", got.LastSeen) } } // TestDismissedPressureStaysDismissed: an operator's decision that something is // not worth acting on must survive the next analysis run. func TestDismissedPressureStaysDismissed(t *testing.T) { ctx := context.Background() reg, _ := newRegistry(t) f := Finding{ Class: contract.PressureClassOutOfScopeDemand, Summary: "consumers ask for a capability outside the interface boundary", Consumers: 5, Occurrences: 40, Severity: 0.5, Confidence: 0.7, Evidence: []contract.EvidenceRef{"route:GET /billing"}, FirstSeen: base, LastSeen: base.Add(time.Hour), Fingerprint: "out-of-scope:GET /billing", } p, err := reg.Record(ctx, f) if err != nil { t.Fatal(err) } actor := contract.Actor{Type: contract.ActorTypeHuman, ID: "worsch"} if err := reg.SetStatus(ctx, p.ID, contract.FluidPressureStatusDISMISSED, actor, "billing belongs to the payments interface"); err != nil { t.Fatal(err) } if _, err := reg.Record(ctx, f); !errors.Is(err, ErrDismissed) { t.Errorf("a dismissed pressure was silently reopened: %v", err) } // A batch run must skip it rather than fail. if _, err := reg.RecordAll(ctx, []Finding{f}); err != nil { t.Errorf("RecordAll failed on a dismissed pressure: %v", err) } got, err := reg.Get(ctx, p.ID) if err != nil { t.Fatal(err) } if got.Status != contract.FluidPressureStatusDISMISSED { t.Errorf("status = %s, want DISMISSED", got.Status) } } func TestStatusChangeRequiresAReason(t *testing.T) { ctx := context.Background() reg, _ := newRegistry(t) p, err := reg.Record(ctx, Finding{ Class: contract.PressureClassPoorDiscoverability, Summary: "s", Consumers: 2, Occurrences: 5, Evidence: []contract.EvidenceRef{"route:x"}, FirstSeen: base, LastSeen: base, Fingerprint: "fp", }) if err != nil { t.Fatal(err) } actor := contract.Actor{Type: contract.ActorTypeHuman, ID: "worsch"} if err := reg.SetStatus(ctx, p.ID, contract.FluidPressureStatusDISMISSED, actor, ""); err == nil { t.Error("a dismissal with no reason was accepted") } } func TestLinkHypothesisMovesToAnalyzing(t *testing.T) { ctx := context.Background() reg, store := newRegistry(t) p, err := reg.Record(ctx, Finding{ Class: contract.PressureClassSuccessfulButInefficientUsage, Summary: "s", Consumers: 3, Occurrences: 12, Evidence: []contract.EvidenceRef{"route:x"}, FirstSeen: base, LastSeen: base, Fingerprint: "fp", }) if err != nil { t.Fatal(err) } if err := reg.LinkHypothesis(ctx, p.ID, "H-1"); err != nil { t.Fatal(err) } // Linking must reject a mis-prefixed id rather than store nonsense. if err := reg.LinkHypothesis(ctx, p.ID, "R-1"); err == nil { t.Error("a revision id was accepted as a hypothesis link") } got, err := reg.Get(ctx, p.ID) if err != nil { t.Fatal(err) } if got.Status != contract.FluidPressureStatusANALYZING { t.Errorf("status = %s, want ANALYZING", got.Status) } if len(got.LinkedHypotheses) != 1 { t.Errorf("linked hypotheses = %v", got.LinkedHypotheses) } // Linking twice must not duplicate. if err := reg.LinkHypothesis(ctx, p.ID, "H-1"); err != nil { t.Fatal(err) } got, _ = reg.Get(ctx, p.ID) if len(got.LinkedHypotheses) != 1 { t.Errorf("duplicate link recorded: %v", got.LinkedHypotheses) } events, err := store.Events(ctx, evidence.EventFilter{EntityID: string(p.ID)}) if err != nil { t.Fatal(err) } if len(events) < 2 { t.Errorf("lifecycle transitions left too few events: %d", len(events)) } } func TestListOrdersBySeverity(t *testing.T) { ctx := context.Background() reg, _ := newRegistry(t) for i, sev := range []float64{0.2, 0.9, 0.5} { _, err := reg.Record(ctx, Finding{ Class: contract.PressureClassPoorDiscoverability, Summary: fmt.Sprintf("finding %d", i), Consumers: 3, Occurrences: 10, Severity: sev, Confidence: 0.5, Evidence: []contract.EvidenceRef{contract.EvidenceRef(fmt.Sprintf("route:%d", i))}, FirstSeen: base, LastSeen: base, Fingerprint: fmt.Sprintf("fp-%d", i), }) if err != nil { t.Fatal(err) } } got, err := reg.List(ctx, "") if err != nil { t.Fatal(err) } if len(got) != 3 { t.Fatalf("got %d records", len(got)) } for i := 1; i < len(got); i++ { if unit(got[i-1].Severity) < unit(got[i].Severity) { t.Error("records are not ordered by severity") } } } func classesOf(fs []Finding) []contract.PressureClass { out := make([]contract.PressureClass, len(fs)) for i, f := range fs { out[i] = f.Class } return out } func hasClass(fs []Finding, want contract.PressureClass) bool { for _, f := range fs { if f.Class == want { return true } } return false }