Files
mats 21f04d696b
Build and Publish / Metadata (push) Successful in 2s
Build and Publish / BuildAndDeployAmd64 (push) Successful in 58s
more otel
2026-09-18 20:51:37 +00:00

230 lines
8.5 KiB
Go

package issues
import "testing"
func TestTrackerDedupesByFingerprint(t *testing.T) {
tracker := NewTracker()
tracker.Record("span_error", "checkout", "charge-card: card declined", Sample{Timestamp: 100, TraceID: "t1"})
tracker.Record("span_error", "checkout", "charge-card: card declined", Sample{Timestamp: 200, TraceID: "t2"})
tracker.Record("span_error", "checkout", "different error", Sample{Timestamp: 150})
list := tracker.List(0)
if len(list) != 2 {
t.Fatalf("expected 2 distinct issues, got %d", len(list))
}
// Most recently seen first.
if list[0].Title != "charge-card: card declined" {
t.Errorf("expected most recent issue first, got %q", list[0].Title)
}
if list[0].Count != 2 {
t.Errorf("expected duplicate occurrences to merge into count 2, got %d", list[0].Count)
}
if list[0].FirstSeen != 100 || list[0].LastSeen != 200 {
t.Errorf("expected firstSeen=100 lastSeen=200, got firstSeen=%d lastSeen=%d", list[0].FirstSeen, list[0].LastSeen)
}
if len(list[0].Samples) != 2 {
t.Errorf("expected 2 samples, got %d", len(list[0].Samples))
}
}
func TestTrackerGet(t *testing.T) {
tracker := NewTracker()
tracker.Record("log_error", "api", "panic: nil pointer", Sample{Timestamp: 100})
list := tracker.List(0)
if len(list) != 1 {
t.Fatalf("expected 1 issue, got %d", len(list))
}
issue, ok := tracker.Get(list[0].ID)
if !ok {
t.Fatalf("expected to find issue %s", list[0].ID)
}
if issue.Service != "api" {
t.Errorf("expected service 'api', got %s", issue.Service)
}
if _, ok := tracker.Get("does-not-exist"); ok {
t.Errorf("expected lookup of unknown id to fail")
}
}
func TestTrackerDifferentServicesDoNotMerge(t *testing.T) {
tracker := NewTracker()
tracker.Record("span_error", "checkout", "timeout", Sample{Timestamp: 100})
tracker.Record("span_error", "billing", "timeout", Sample{Timestamp: 100})
list := tracker.List(0)
if len(list) != 2 {
t.Fatalf("expected same title on different services to stay distinct, got %d issues", len(list))
}
}
func TestTrackerListRespectsLimit(t *testing.T) {
tracker := NewTracker()
for i := 0; i < 5; i++ {
tracker.Record("log_error", "api", string(rune('a'+i)), Sample{Timestamp: int64(i)})
}
list := tracker.List(2)
if len(list) != 2 {
t.Fatalf("expected limit to cap results at 2, got %d", len(list))
}
}
func TestTrackerFilteredCursorPagination(t *testing.T) {
tracker := NewTracker()
tracker.Record("span_error", "nginx", "GET /.env", Sample{Timestamp: 300, SourceIP: "203.0.113.10", HTTPRoute: "/.env"})
tracker.Record("span_error", "nginx", "GET /.git/HEAD", Sample{Timestamp: 200, SourceIP: "203.0.113.11", HTTPRoute: "/.git/HEAD"})
tracker.Record("log_error", "api", "database down", Sample{Timestamp: 100})
page := tracker.ListFiltered(ListOptions{Service: "nginx", Kind: "span_error", Route: "/.env", Limit: 1})
if len(page.Issues) != 1 || page.Issues[0].Title != "GET /.env" {
t.Fatalf("unexpected filtered page: %+v", page)
}
if page.HasMore {
t.Error("single matching issue should not have another page")
}
all := tracker.ListFiltered(ListOptions{Service: "nginx", Limit: 1})
if len(all.Issues) != 1 || !all.HasMore {
t.Fatalf("expected first paginated page with more results, got %+v", all)
}
next := tracker.ListFiltered(ListOptions{
Service: "nginx",
AfterLastSeen: all.Issues[0].LastSeen,
AfterID: all.Issues[0].ID,
Limit: 1,
})
if len(next.Issues) != 1 || next.Issues[0].Title != "GET /.git/HEAD" || next.HasMore {
t.Fatalf("unexpected cursor page: %+v", next)
}
}
func TestNormalizeTitleGroupsVariableMessages(t *testing.T) {
cases := [][3]string{
// same, same, different: [a, b, expectedEqual(a,b)]
{"User 123 not found", "User 456 not found", "user <num> not found"},
{"order 550e8400-e29b-41d4-a716-446655440000 failed", "order 550e8400-e29b-41d4-a716-446655440001 failed", "order <id> failed"},
{"request 0a1b2c3d4e5f6a7b timed out", "request ff11aa22bb33cc44 timed out", "request <id> timed out"},
}
for _, c := range cases {
if got := NormalizeTitle(c[0]); got != c[2] {
t.Errorf("NormalizeTitle(%q) = %q, want %q", c[0], got, c[2])
}
if NormalizeTitle(c[0]) != NormalizeTitle(c[1]) {
t.Errorf("expected %q and %q to normalize identically", c[0], c[1])
}
}
// Words spelled from hex letters without digits must survive.
if got := NormalizeTitle("bad failed deadbeef badge"); got != "bad failed deadbeef badge" {
t.Errorf("word-like hex incorrectly replaced: %q", got)
}
}
func TestTrackerNormalizedFingerprinting(t *testing.T) {
tracker := NewTracker()
tracker.Record("log_error", "api", "User 123 not found", Sample{Timestamp: 100, Message: "User 123 not found"})
tracker.Record("log_error", "api", "User 98765432 not found", Sample{Timestamp: 200, Message: "User 98765432 not found"})
list := tracker.List(0)
if len(list) != 1 {
t.Fatalf("expected variable messages to merge into 1 issue, got %d", len(list))
}
if list[0].Count != 2 {
t.Errorf("expected count 2, got %d", list[0].Count)
}
// The display title is the first-seen raw message.
if list[0].Title != "User 123 not found" {
t.Errorf("expected first-seen raw title kept, got %q", list[0].Title)
}
}
func TestTrackerTrendBuckets(t *testing.T) {
tracker := NewTracker()
// Two occurrences in the same hour.
tracker.Record("log_error", "api", "boom", Sample{Timestamp: 100})
tracker.Record("log_error", "api", "boom", Sample{Timestamp: 200})
issue, ok := tracker.Get(tracker.List(1)[0].ID)
if !ok {
t.Fatal("expected issue to exist")
}
bucket := HourBucket(100)
if issue.Trend[bucket] != 2 {
t.Errorf("expected trend[%s]=2, got %d", bucket, issue.Trend[bucket])
}
}
func TestTrackerStatusLifecycle(t *testing.T) {
tracker := NewTracker()
tracker.Record("log_error", "api", "boom", Sample{Timestamp: 100})
id := tracker.List(1)[0].ID
// Invalid status rejected.
if _, ok := tracker.SetStatus(id, "bogus"); ok {
t.Error("expected invalid status to be rejected")
}
// Unknown id rejected.
if _, ok := tracker.SetStatus("nope", StatusResolved); ok {
t.Error("expected unknown id to be rejected")
}
// Resolve.
resolved, ok := tracker.SetStatus(id, StatusResolved)
if !ok || resolved.Status != StatusResolved || resolved.ResolvedAt == 0 {
t.Fatalf("expected resolved issue, got %+v ok=%v", resolved, ok)
}
// A new occurrence reopens it (regression).
tracker.Record("log_error", "api", "boom", Sample{Timestamp: 200})
issue, _ := tracker.Get(id)
if issue.Status != StatusOpen || issue.ResolvedAt != 0 {
t.Errorf("expected regression to reopen issue, got status=%s resolvedAt=%d", issue.Status, issue.ResolvedAt)
}
// Ignored survives new occurrences.
if _, ok := tracker.SetStatus(id, StatusIgnored); !ok {
t.Fatal("expected ignore to succeed")
}
tracker.Record("log_error", "api", "boom", Sample{Timestamp: 300})
issue, _ = tracker.Get(id)
if issue.Status != StatusIgnored {
t.Errorf("expected ignored state to survive new occurrence, got %s", issue.Status)
}
}
func TestTrackerPatternsAndSimilarSuggestions(t *testing.T) {
tracker := NewTracker()
tracker.Record("span_error", "checkout", "HTTP 504: database timeout for order 123", Sample{
Timestamp: 100, HTTPRoute: "/orders/123", HTTPStatus: 504, Stacktrace: "db.go:42\nhandler.go:10",
})
tracker.Record("span_error", "checkout", "HTTP 502: database timeout for order 456", Sample{
Timestamp: 200, HTTPRoute: "/orders/456", HTTPStatus: 502, Stacktrace: "db.go:42\nhandler.go:10",
})
issuesList := tracker.List(0)
if len(issuesList) != 2 {
t.Fatalf("expected separate exact issues, got %d", len(issuesList))
}
if issuesList[0].PatternID == "" || issuesList[0].PatternID != issuesList[1].PatternID {
t.Fatalf("expected related HTTP variants to share a pattern: %+v", issuesList)
}
if issuesList[0].Samples[0].NormalizedRoute != "/orders/{id}" {
t.Fatalf("expected normalized route, got %q", issuesList[0].Samples[0].NormalizedRoute)
}
related, ok := tracker.Similar(issuesList[0].ID, 5)
if !ok || len(related) != 1 || related[0].Score < .45 {
t.Fatalf("expected deterministic related suggestion, got ok=%v related=%+v", ok, related)
}
if !tracker.SetSimilarityFeedback(issuesList[0].ID, related[0].Issue.ID, "same") {
t.Fatal("expected feedback to be accepted")
}
related, _ = tracker.Similar(issuesList[0].ID, 5)
if related[0].Decision != "same" {
t.Fatalf("expected feedback decision, got %q", related[0].Decision)
}
patterns := tracker.Patterns()
if len(patterns) != 1 || patterns[0].IssueCount != 2 || patterns[0].OccurrenceCount != 2 {
t.Fatalf("expected one two-issue pattern, got %+v", patterns)
}
}