package store import ( "testing" "time" "tower/internal/model" ) func TestStore_Upsert_DedupAndTimestamps(t *testing.T) { now1 := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC) now2 := now1.Add(5 * time.Second) s := New(30 * time.Second) // Same ID twice in one Upsert should dedupe. s.Upsert(now1, []model.Issue{ {ID: "i-1", Title: "first"}, {ID: "i-1", Title: "should be ignored"}, }) snap1 := s.Snapshot(now1) if len(snap1) != 1 { t.Fatalf("expected 1 issue, got %d", len(snap1)) } if snap1[0].ID != "i-1" { t.Fatalf("expected id i-1, got %q", snap1[0].ID) } if !snap1[0].FirstSeen.Equal(now1) { t.Fatalf("expected FirstSeen=%v, got %v", now1, snap1[0].FirstSeen) } if !snap1[0].LastSeen.Equal(now1) { t.Fatalf("expected LastSeen=%v, got %v", now1, snap1[0].LastSeen) } if snap1[0].State != model.StateOpen { t.Fatalf("expected State=Open, got %q", snap1[0].State) } // Subsequent Upsert for same ID should preserve FirstSeen and update LastSeen. s.Upsert(now2, []model.Issue{{ID: "i-1", Title: "updated"}}) snap2 := s.Snapshot(now2) if len(snap2) != 1 { t.Fatalf("expected 1 issue, got %d", len(snap2)) } if !snap2[0].FirstSeen.Equal(now1) { t.Fatalf("expected FirstSeen to remain %v, got %v", now1, snap2[0].FirstSeen) } if !snap2[0].LastSeen.Equal(now2) { t.Fatalf("expected LastSeen=%v, got %v", now2, snap2[0].LastSeen) } } func TestStore_AckPreservedWhilePresent(t *testing.T) { now1 := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC) now2 := now1.Add(1 * time.Second) s := New(30 * time.Second) s.Upsert(now1, []model.Issue{{ID: "i-1", Title: "t"}}) s.Acknowledge("i-1") // Upsert again while present should remain Acked. s.Upsert(now2, []model.Issue{{ID: "i-1", Title: "t2"}}) snap := s.Snapshot(now2) if len(snap) != 1 { t.Fatalf("expected 1 issue, got %d", len(snap)) } if snap[0].State != model.StateAcknowledged { t.Fatalf("expected State=Acknowledged, got %q", snap[0].State) } s.Unacknowledge("i-1") snap2 := s.Snapshot(now2) if snap2[0].State != model.StateOpen { t.Fatalf("expected State=Open after unack, got %q", snap2[0].State) } } func TestStore_ResolvesOnlyAfterAbsenceWindow(t *testing.T) { resolveAfter := 10 * time.Second now0 := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC) s := New(resolveAfter) s.Upsert(now0, []model.Issue{{ID: "i-1", Title: "t"}}) // Miss a tick shortly after; should not resolve due to flap suppression / window. s.Upsert(now0.Add(1*time.Second), nil) snap1 := s.Snapshot(now0.Add(9 * time.Second)) if len(snap1) != 1 { t.Fatalf("expected 1 issue, got %d", len(snap1)) } if snap1[0].State != model.StateOpen { t.Fatalf("expected still Open before resolveAfter, got %q", snap1[0].State) } // Still absent beyond resolveAfter => should resolve. snap2 := s.Snapshot(now0.Add(11 * time.Second)) if snap2[0].State != model.StateResolved { t.Fatalf("expected Resolved after absence > resolveAfter, got %q", snap2[0].State) } }