Fix high prio recommendations, add error / health screen
This commit is contained in:
201
health_test.go
Normal file
201
health_test.go
Normal file
@@ -0,0 +1,201 @@
|
||||
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()
|
||||
_, 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())
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user