Files
diple/health_test.go

212 lines
6.9 KiB
Go

package main
import (
"context"
"errors"
"strings"
"testing"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/x/ansi"
)
type healthTestService struct {
components []HealthComponent
rate RateLimitSnapshot
}
func (s *healthTestService) ListPullRequests(
context.Context, string, string, int, bool,
) ([]PullRequest, error) {
return nil, nil
}
func (s *healthTestService) GetPullRequest(
context.Context, string, string, int,
) (PRDetails, error) {
return PRDetails{}, nil
}
func (s *healthTestService) HealthReport() []HealthComponent {
return append([]HealthComponent(nil), s.components...)
}
func (s *healthTestService) RateLimit() RateLimitSnapshot { return s.rate }
func TestHealthScreenReportsComponentsRateLimitAndEvents(t *testing.T) {
service := &healthTestService{
components: []HealthComponent{{
Name: "GitHub API", Level: healthOK, Summary: "request succeeded",
}},
rate: RateLimitSnapshot{
Limit: 5000, Remaining: 42, UpdatedAt: time.Now(),
ResetAt: time.Now().Add(time.Hour),
},
}
settings := defaultAppSettings()
settings.ReadState = loadReadState(t.TempDir() + "/state.json")
settings.Drafts = loadDraftStore(t.TempDir() + "/drafts.json")
app := NewAppWithSettings(service, "o", "r", false, 50, time.Minute, settings)
app.width, app.height = 64, 30
app.details = PRDetails{
PullRequest: PullRequest{ID: "pr", UpdatedAt: time.Now()},
DataIssues: []DataIssue{{Component: "timeline", Message: "unavailable"}},
}
app.recordHealth("timeline", healthWarning, "a deliberately long warning that must remain readable")
lines := app.healthLines()
plain := ansi.Strip(strings.Join(lines, "\n"))
for _, wanted := range []string{
"configuration", "read state", "draft persistence", "GitHub API",
"rate limit", "42/5000", "PR core data", "timeline",
} {
if !strings.Contains(plain, wanted) {
t.Fatalf("health output missing %q:\n%s", wanted, plain)
}
}
for _, line := range lines {
if ansi.StringWidth(line) > app.width-2 {
t.Fatalf("health line width = %d, want <= %d: %q", ansi.StringWidth(line), app.width-2, line)
}
}
}
func TestHealthScreenOpensAndReturnsToPreviousScreen(t *testing.T) {
app := NewApp(nil, "", "", false, 50, time.Minute)
app.screen = dashboardScreen
app.scroll = 7
app.width, app.height = 100, 30
updated, _ := app.Update(runeKey("H"))
app = updated.(App)
if app.screen != healthScreen || app.healthReturn != dashboardScreen || app.scroll != 7 {
t.Fatalf("health navigation = screen %v return %v", app.screen, app.healthReturn)
}
view := ansi.Strip(app.View())
if !strings.Contains(view, "Application health") ||
!strings.Contains(view, "╭") || !strings.Contains(view, "╰") {
t.Fatalf("health is not rendered as a modal:\n%s", view)
}
updated, _ = app.Update(tea.KeyMsg{Type: tea.KeyEsc})
app = updated.(App)
if app.screen != dashboardScreen || app.scroll != 7 {
t.Fatalf("health back returned to screen %v at scroll %d", app.screen, app.scroll)
}
}
func TestHealthRefreshLineIsStableAndInformational(t *testing.T) {
app := NewApp(nil, "", "", false, 50, time.Minute)
app.width, app.height = 100, 30
app.loading = false
idle := app.healthLines()
app.loading = true
loading := app.healthLines()
findRefresh := func(lines []string) (int, string) {
for index, line := range lines {
plain := ansi.Strip(line)
if strings.Contains(plain, "refresh") {
return index, plain
}
}
return -1, ""
}
idleIndex, idleLine := findRefresh(idle)
loadingIndex, loadingLine := findRefresh(loading)
if idleIndex < 0 || idleIndex != loadingIndex {
t.Fatalf("refresh line moved from %d to %d", idleIndex, loadingIndex)
}
if !strings.Contains(idleLine, "OK") || !strings.Contains(loadingLine, "INFO") ||
strings.Contains(loadingLine, "WARN") {
t.Fatalf("refresh states: idle=%q loading=%q", idleLine, loadingLine)
}
}
func TestAdaptivePollingHonorsRateLimitBackoff(t *testing.T) {
now := time.Unix(1000, 0)
service := &healthTestService{rate: RateLimitSnapshot{
Limit: 5000, Remaining: 100, UpdatedAt: now,
}}
app := NewApp(service, "", "", false, 50, 10*time.Second)
got := app.adaptivePollInterval(now)
if got < 72*time.Second || got > 88*time.Second {
t.Fatalf("low-budget interval = %s, want about 80s with jitter", got)
}
service.rate.RetryAfter = now.Add(2 * time.Minute)
got = app.adaptivePollInterval(now)
if got < 108*time.Second || got > 132*time.Second {
t.Fatalf("retry-after interval = %s, want about 2m with jitter", got)
}
}
func TestRequestCoordinatorCancelsSupersededRequest(t *testing.T) {
var coordinator requestCoordinator
first, cancelFirst, firstID := coordinator.start(time.Minute)
defer cancelFirst()
second, cancelSecond, secondID := coordinator.start(time.Minute)
defer cancelSecond()
select {
case <-first.Done():
default:
t.Fatal("superseded request context was not canceled")
}
if coordinator.current(firstID) || !coordinator.current(secondID) {
t.Fatalf("current request ids: first=%t second=%t",
coordinator.current(firstID), coordinator.current(secondID))
}
if !errors.Is(first.Err(), context.Canceled) {
t.Fatalf("first context error = %v", first.Err())
}
claimedID := coordinator.supersede()
select {
case <-second.Done():
default:
t.Fatal("claimed snapshot did not cancel the active request")
}
if coordinator.current(secondID) || !coordinator.current(claimedID) {
t.Fatalf("claimed request ids: second=%t claimed=%t",
coordinator.current(secondID), coordinator.current(claimedID))
}
}
func TestPartialRefreshPreservesLastCompleteSubsections(t *testing.T) {
previous := PRDetails{
PullRequest: PullRequest{Owner: "o", Repository: "r", Number: 1},
HeadOID: "head",
BaseOID: "base",
Threads: []ReviewThread{{ID: "old-thread"}, {ID: "second-thread"}},
Conversation: []PRComment{{ID: "old-comment"}, {ID: "second-comment"}},
ConflictFiles: []string{"conflicted.go"},
Checks: []Check{{
ID: "check", Annotations: []CheckAnnotation{{Path: "problem.go"}},
}},
}
fresh := PRDetails{
PullRequest: PullRequest{Owner: "o", Repository: "r", Number: 1},
HeadOID: "head",
BaseOID: "base",
Threads: []ReviewThread{{ID: "first-page-only"}},
Conversation: []PRComment{{ID: "first-page-only"}},
Checks: []Check{{ID: "check"}},
DataIssues: []DataIssue{
{Component: "review threads", Message: "page failed"},
{Component: "conversation", Message: "page failed"},
},
}
merged := preservePartialPRData(fresh, previous)
if len(merged.Threads) != 2 || merged.Threads[0].ID != "old-thread" {
t.Fatalf("preserved threads = %#v", merged.Threads)
}
if len(merged.Conversation) != 2 || merged.Conversation[0].ID != "old-comment" {
t.Fatalf("preserved conversation = %#v", merged.Conversation)
}
if len(merged.DataIssues) != 2 {
t.Fatalf("partial markers were lost: %#v", merged.DataIssues)
}
if len(merged.ConflictFiles) != 1 || len(merged.Checks[0].Annotations) != 1 {
t.Fatalf("secondary data flickered during core refresh: %#v", merged)
}
}