package web import ( "bytes" "log" "net/http" "net/http/httptest" "os" "os/exec" "path/filepath" "strings" "testing" "time" "bugabinga.net/luci/internal/ciconfig" "bugabinga.net/luci/internal/history" "bugabinga.net/luci/internal/logs" "bugabinga.net/luci/internal/proquint" "bugabinga.net/luci/internal/runstate" ) func TestDashboardPrioritizesActionableState(t *testing.T) { dataDir, inboxDir := webDirs(t) now := time.Now().UTC() store := history.Store{DataDir: dataDir, Now: func() time.Time { return now }} _, _ = store.Append(history.Event{RunID: "failed-run", ChildID: "run", Repo: "broken", Job: "push", Rev: "111111111", Ref: "refs/heads/trunk", Trigger: "push", Status: "failed", Time: now.Add(-time.Minute)}) _, _ = store.Append(history.Event{RunID: "success-run", ChildID: "run", Repo: "smith", Job: "smoke", Rev: "8ae9c890afefe8206a1e9b4e9760e6f40097d9a6", Ref: "refs/heads/trunk", Trigger: "manual", Status: "success", StartedAt: now.Add(-20 * time.Second), Time: now.Add(-2 * time.Second)}) if err := (runstate.Store{DataDir: dataDir}).Write(runstate.Active{RunID: "active-run", ChildID: "001", Repo: "running", Job: "test", Rev: "abcdef123", Ref: "refs/heads/trunk", Trigger: "push", StartedAt: now.Add(-5 * time.Second)}); err != nil { t.Fatal(err) } res := httptest.NewRecorder() Handler(dataDir, inboxDir, "", nil).ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/", nil)) body := res.Body.String() if res.Code != http.StatusOK { t.Fatalf("status=%d body=%s", res.Code, body) } for _, want := range []string{"NEEDS ATTENTION", "IN PROGRESS", "HEALTHY", "Needs attention", "In progress", "Recent successful runs", "broken", "running", "smith", "8ae9", "18s"} { if !strings.Contains(body, want) { t.Fatalf("missing %q: %s", want, body) } } if strings.Contains(body, "alert(1)"), 0o644); err != nil { t.Fatal(err) } stage := filepath.Join(dataDir, "artifacts", "42", "001", ".stage-copy", "private.txt") if err := os.MkdirAll(filepath.Dir(stage), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(stage, []byte("private"), 0o600); err != nil { t.Fatal(err) } handler := Handler(dataDir, inboxDir, "", nil) res := httptest.NewRecorder() handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/runs/42/artifacts/001/build/result.html", nil)) if res.Code != http.StatusOK || res.Body.String() != "" || res.Header().Get("X-Content-Type-Options") != "nosniff" || res.Header().Get("Content-Security-Policy") != "sandbox" || res.Header().Get("Content-Disposition") != "attachment" || res.Header().Get("Cache-Control") != "public, max-age=31536000, immutable" { t.Fatalf("status=%d headers=%v body=%q", res.Code, res.Header(), res.Body.String()) } res = httptest.NewRecorder() handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/runs/42/artifacts/001/.stage-copy/private.txt", nil)) if res.Code != http.StatusNotFound { t.Fatalf("staged=%d", res.Code) } res = httptest.NewRecorder() handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/runs/42/artifacts/%2e%2e/secrets", nil)) if res.Code != http.StatusNotFound { t.Fatalf("traversal=%d", res.Code) } res = httptest.NewRecorder() handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/runs/42/artifacts", nil)) if res.Code != http.StatusOK || !strings.Contains(res.Body.String(), "001/build/result.html\\t25") || strings.Contains(res.Body.String(), "private") { t.Fatalf("list=%d body=%q", res.Code, res.Body.String()) } } func TestRunAndLogRoutesResolveOnlyUniqueProquints(t *testing.T) { dataDir, inboxDir := webDirs(t) now := time.Now().UTC() store := history.Store{DataDir: dataDir, Now: func() time.Time { return now }} for _, id := range []string{"42", "281474976710656"} { if _, err := store.Append(history.Event{RunID: id, ChildID: "run", Repo: "repo", Job: "check", Status: "success", Time: now}); err != nil { t.Fatal(err) } } if _, err := store.Append(history.Event{RunID: "42", ChildID: "001", Repo: "repo", Job: "check", Status: "success", Time: now}); err != nil { t.Fatal(err) } logStore := logs.Store{DataDir: dataDir} file, err := logStore.CreateLive("42-001") if err != nil { t.Fatal(err) } if _, err := file.WriteString("resolved log\n"); err != nil { t.Fatal(err) } if err := file.Close(); err != nil { t.Fatal(err) } if _, err := logStore.Finalize("42-001"); err != nil { t.Fatal(err) } handler := Handler(dataDir, inboxDir, "", nil) full, prefix := proquint.Encode(42), proquint.Encode(42)[:11] for _, path := range []string{"/runs/" + full, "/runs/" + prefix} { res := httptest.NewRecorder() handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, path, nil)) if res.Code != http.StatusOK || !strings.Contains(res.Body.String(), "Run 42") { t.Fatalf("%s status=%d body=%s", path, res.Code, res.Body.String()) } } res := httptest.NewRecorder() handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/runs/"+prefix+"/logs/001", nil)) if res.Code != http.StatusOK || res.Body.String() != "resolved log\n" { t.Fatalf("resolved log status=%d body=%q", res.Code, res.Body.String()) } if _, err := store.Append(history.Event{RunID: "43", ChildID: "run", Repo: "repo", Job: "check", Status: "success", Time: now}); err != nil { t.Fatal(err) } for _, path := range []string{ "/runs/" + proquint.Encode(99), "/runs/" + proquint.Encode(99) + "/logs/001", "/runs/" + full[:5], "/runs/" + full[:5] + "/logs/001", } { res := httptest.NewRecorder() handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, path, nil)) if res.Code != http.StatusNotFound { t.Fatalf("%s status=%d body=%s", path, res.Code, res.Body.String()) } } } func TestRunPageShowsDirectKDLFilesOnly(t *testing.T) { dataDir, inboxDir := webDirs(t) repoRoot := t.TempDir() work := filepath.Join(repoRoot, "work") repos := filepath.Join(repoRoot, "repos") webGit(t, repoRoot, "init", work) webGit(t, work, "config", "user.email", "test@example.invalid") webGit(t, work, "config", "user.name", "Test") for path, content := range map[string]string{ ".ci/ci.kdl": "direct-kdl", ".ci/nested/ci.kdl": "nested-kdl", ".ci/readme.txt": "non-kdl", } { fullPath := filepath.Join(work, path) if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(fullPath, []byte(content), 0o644); err != nil { t.Fatal(err) } } webGit(t, work, "add", ".") webGit(t, work, "commit", "-m", "configuration") rev := strings.TrimSpace(webGit(t, work, "rev-parse", "HEAD")) if err := os.MkdirAll(repos, 0o755); err != nil { t.Fatal(err) } webGit(t, repoRoot, "clone", "--bare", work, filepath.Join(repos, "repo.git")) if _, err := (history.Store{DataDir: dataDir}).Append(history.Event{RunID: "42", ChildID: "run", Repo: "repo", Job: "check", Rev: rev, Status: "success"}); err != nil { t.Fatal(err) } res := httptest.NewRecorder() Handler(dataDir, inboxDir, "", []string{repos}).ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/runs/42", nil)) body := res.Body.String() if res.Code != http.StatusOK || !strings.Contains(body, "direct-kdl") || !strings.Contains(body, "Configuration invalid") || strings.Contains(body, "nested-kdl") || strings.Contains(body, "non-kdl") { t.Fatalf("status=%d body=%s", res.Code, body) } } func TestRunPageShowsSubjectQueueWaitAndSteps(t *testing.T) { dataDir, inboxDir := webDirs(t) now := time.Now().UTC() store := history.Store{DataDir: dataDir, Now: func() time.Time { return now }} _, _ = store.Append(history.Event{RunID: "7", ChildID: "run", Repo: "smith", Job: "push", Rev: "8ae9c890afefe8206a1e9b4e9760e6f40097d9a6", Ref: "refs/heads/trunk", Trigger: "push", Status: "success", Subject: "fix: rebuild cache keys", StartedAt: now.Add(-40 * time.Minute), Time: now.Add(-time.Minute)}) _, _ = store.Append(history.Event{RunID: "7", ChildID: "001", Repo: "smith", Job: "check", Rev: "8ae9c890afefe8206a1e9b4e9760e6f40097d9a6", Ref: "refs/heads/trunk", Trigger: "push", Status: "success", Subject: "fix: rebuild cache keys", StartedAt: now.Add(-31 * time.Minute), Time: now.Add(-2 * time.Minute)}) logStore := logs.Store{DataDir: dataDir} file, err := logStore.CreateLive("7-001") if err != nil { t.Fatal(err) } _, _ = file.WriteString("[luci] cache target: fallback\n[luci] step 1/2: cargo test\n[luci] step 1/2 done in 41s\n[luci] step 2/2: cargo clippy\n") _ = file.Close() if _, err := logStore.Finalize("7-001"); err != nil { t.Fatal(err) } res := httptest.NewRecorder() Handler(dataDir, inboxDir, "", nil).ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/runs/7", nil)) body := res.Body.String() for _, want := range []string{"fix: rebuild cache keys", "queued 9m", "step timing", "cargo clippy", "41s", "running…"} { if !strings.Contains(body, want) { t.Fatalf("missing %q in body", want) } } } func TestRawLogMissingAndCorruptResponses(t *testing.T) { dataDir, inboxDir := webDirs(t) now := time.Now().UTC() _, _ = (history.Store{DataDir: dataDir}).Append(history.Event{RunID: "bad", ChildID: "001", Repo: "repo", Job: "check", Status: "failed", Time: now}) if err := os.WriteFile(filepath.Join(dataDir, "logs", "bad-001.log.zst"), []byte("bad zstd"), 0o644); err != nil { t.Fatal(err) } handler := Handler(dataDir, inboxDir, "", nil) res := httptest.NewRecorder() handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/runs/missing/logs/001", nil)) if res.Code != http.StatusNotFound { t.Fatalf("missing=%d", res.Code) } res = httptest.NewRecorder() handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/runs/bad/logs/001", nil)) if res.Code != http.StatusInternalServerError || res.Body.String() != "internal server error\n" { t.Fatalf("corrupt=%d body=%s", res.Code, res.Body.String()) } res = httptest.NewRecorder() handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/runs/bad", nil)) if res.Code != http.StatusOK || !strings.Contains(res.Body.String(), "Log unavailable") || strings.Contains(res.Body.String(), "invalid input") || !strings.Contains(res.Body.String(), "repo") { t.Fatalf("run=%d body=%s", res.Code, res.Body.String()) } } func TestPageCSSUsesAdaptiveNuguPalette(t *testing.T) { for _, pattern := range []string{ `--raised\s*:\s*var\s*\(\s*--nugu-ui-backdrop\s*,\s*#dbdbdb\s*\)`, `--cyan\s*:\s*var\s*\(\s*--nugu-info\s*,\s*#116477\s*\)`, `@media\s*\(\s*prefers-color-scheme\s*:\s*dark\s*\)`, `--bg\s*:\s*var\s*\(\s*--nugu-content-backdrop\s*,\s*#121212\s*\)`, `--raised\s*:\s*var\s*\(\s*--nugu-ui-backdrop\s*,\s*#242424\s*\)`, `--cyan\s*:\s*var\s*\(\s*--nugu-info\s*,\s*#3ec5e3\s*\)`, } { if !cssMatch(pageCSS, pattern) { t.Fatalf("page CSS missing pattern %q", pattern) } } if strings.Contains(pageCSS, "--nugu-content-link") || cssMatch(pageCSS, `--raised\s*:\s*var\s*\(\s*--nugu-ui-focus`) { t.Fatal("page CSS contains invalid palette role") } } func TestResponsiveAndAccessibilityContracts(t *testing.T) { dataDir, inboxDir := webDirs(t) res := httptest.NewRecorder() Handler(dataDir, inboxDir, "", nil).ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/", nil)) body := res.Body.String() if !strings.Contains(body, `name="viewport"`) { t.Fatal("viewport metadata missing") } css := embeddedPageCSS(t, body) for _, pattern := range []string{ `@media\s*\(\s*max-width\s*:\s*700px\s*\)`, `min-height\s*:\s*44px`, `min-height\s*:\s*24px`, `\.run-row:focus-visible`, `white-space\s*:\s*pre-wrap`, `overflow-wrap\s*:\s*anywhere`, `max-height\s*:\s*60vh`, `(?:^|[{}])\s*\.facts\s+span\s*\{\s*min-width\s*:\s*0\s*;\s*overflow-wrap\s*:\s*anywhere\s*;?\s*\}`, } { if !cssMatch(css, pattern) { t.Fatalf("page CSS missing pattern %q", pattern) } } } func TestRepoDocsThemeAndNotFound(t *testing.T) { dataDir, inboxDir := webDirs(t) _, _ = (history.Store{DataDir: dataDir}).Append(history.Event{RunID: "run1", ChildID: "run", Repo: "repo", Job: "check", Status: "success"}) theme := filepath.Join(dataDir, "theme.css") if err := os.WriteFile(theme, []byte("body{color:red}"), 0o644); err != nil { t.Fatal(err) } handler := Handler(dataDir, inboxDir, theme, nil) for path, want := range map[string]string{"/repos/repo": "repo", "/repos/missing": "No runs", "/runs/missing": "Unknown run", "/docs": "Security model"} { res := httptest.NewRecorder() handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, path, nil)) if res.Code != http.StatusOK || !strings.Contains(res.Body.String(), want) { t.Fatalf("%s status=%d body=%s", path, res.Code, res.Body.String()) } } res := httptest.NewRecorder() handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/theme.css", nil)) if res.Code != http.StatusOK || res.Body.String() != "body{color:red}" { t.Fatalf("theme=%d %q", res.Code, res.Body.String()) } res = httptest.NewRecorder() handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/missing", nil)) if res.Code != http.StatusNotFound { t.Fatalf("missing=%d", res.Code) } res = httptest.NewRecorder() Handler(dataDir, inboxDir, "", nil).ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/", nil)) if strings.Contains(res.Body.String(), `href="/theme.css"`) { t.Fatal("disabled theme endpoint was loaded") } res = httptest.NewRecorder() Handler(dataDir, inboxDir, filepath.Join(dataDir, "missing-theme.css"), nil).ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/", nil)) if strings.Contains(res.Body.String(), `href="/theme.css"`) { t.Fatal("unavailable theme endpoint was loaded") } } func TestLLMsReferenceIsPlainTextAndSetupNeutral(t *testing.T) { res := httptest.NewRecorder() Handler("[", "[", "", nil).ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/llms.txt", nil)) body := res.Body.String() if res.Code != http.StatusOK || res.Header().Get("Content-Type") != "text/plain; charset=utf-8" { t.Fatalf("status=%d type=%q", res.Code, res.Header().Get("Content-Type")) } for _, want := range []string{"# Luci CI", "schedule \"0 3 * * *\"", "include \"src/**\"", "manual-only", "15 minutes", "Git metadata"} { if !strings.Contains(body, want) { t.Fatalf("missing %q", want) } } for _, unwanted := range []string{"ci.bugabinga.net", "root@", "2223", "def ci"} { if strings.Contains(body, unwanted) { t.Fatalf("contains setup-specific %q", unwanted) } } } func TestLLMsSSHCommandsDistinguishLocalDaemon(t *testing.T) { if !strings.Contains(llmsText, "Public SSH commands are restricted") || !strings.Contains(llmsText, "SSH never permits serving") { t.Fatal("llms reference must document restricted SSH commands and local-only serving") } if strings.Contains(llmsText, "Over SSH the same commands") { t.Fatal("llms reference claims unrestricted command parity") } } func TestLLMsKDLExampleParses(t *testing.T) { const marker = "~~~kdl\n" start := strings.Index(llmsText, marker) if start < 0 { t.Fatal("KDL block missing") } start += len(marker) end := strings.Index(llmsText[start:], "\n~~~") if end < 0 { t.Fatal("KDL block not closed") } repo := t.TempDir() path := filepath.Join(repo, ".ci", "ci.kdl") if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(path, []byte(llmsText[start:start+end]), 0o644); err != nil { t.Fatal(err) } if _, err := ciconfig.Load(repo); err != nil { t.Fatalf("documented KDL does not parse: %v", err) } } func TestUnreadableStateReturnsGenericLoggedError(t *testing.T) { var output bytes.Buffer previous := log.Writer() log.SetOutput(&output) t.Cleanup(func() { log.SetOutput(previous) }) res := httptest.NewRecorder() Handler("[", t.TempDir(), "", nil).ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/", nil)) if res.Code != http.StatusInternalServerError || res.Body.String() != "internal server error\n" { t.Fatalf("status=%d body=%s", res.Code, res.Body.String()) } if !strings.Contains(output.String(), "luci web load state:") { t.Fatalf("missing server error log: %s", output.String()) } } func TestRunPageConnectsChildrenToRevisionConfiguration(t *testing.T) { dataDir, inboxDir := webDirs(t) repoRoot := t.TempDir() work, repos := filepath.Join(repoRoot, "work"), filepath.Join(repoRoot, "repos") webGit(t, repoRoot, "init", work) webGit(t, work, "config", "user.email", "test@example.invalid") webGit(t, work, "config", "user.name", "Test") config := `job "other" { image "alpine" run "echo other" } job "test" { image "docker.io/library/golang:${go}" trigger { push { branch "trunk" } } matrix { go "1.26" } run "go test ./... ${go}" }` if err := os.MkdirAll(filepath.Join(work, ".ci"), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(work, ".ci", "ci.kdl"), []byte(config), 0o644); err != nil { t.Fatal(err) } webGit(t, work, "add", ".") webGit(t, work, "commit", "-m", "configuration") rev := strings.TrimSpace(webGit(t, work, "rev-parse", "HEAD")) if err := os.MkdirAll(repos, 0o755); err != nil { t.Fatal(err) } webGit(t, repoRoot, "clone", "--bare", work, filepath.Join(repos, "repo.git")) store := history.Store{DataDir: dataDir} for _, event := range []history.Event{ {RunID: "42", ChildID: "run", Repo: "repo", Job: "push", Rev: rev, Status: "failed", Detail: "parent failed"}, {RunID: "42", ChildID: "001", Repo: "repo", Job: "test[go=1.26]", Rev: rev, Status: "failed", Matrix: map[string]string{"go": "1.26"}}, {RunID: "42", ChildID: "002", Repo: "repo", Job: "test[go=1.26]", Rev: rev, Status: "success", Matrix: map[string]string{"go": "1.26"}}, } { if _, err := store.Append(event); err != nil { t.Fatal(err) } } snapshot, err := (Server{RepoRoots: []string{repos}}).configAt(runDetailView{Known: true, Parent: runView{Repo: "repo", FullRev: rev}}) if err != nil || snapshot.Error != "" || snapshot.Jobs["test[go=1.26]"].Image == "" { t.Fatalf("config snapshot=%#v err=%v", snapshot, err) } res := httptest.NewRecorder() Handler(dataDir, inboxDir, "", []string{repos}).ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/runs/42", nil)) body := res.Body.String() if strings.Contains(body, "Configuration invalid") { t.Fatalf("configuration parse failed") } for _, want := range []string{"Run failed.", "1 failed.", "1 passed.", "Parent failure detail", "Execution configuration", "docker.io/library/golang:1.26", "go test ./... 1.26", "Matrix", "go=1.26", "Trigger", "push", "Job source · .ci/ci.kdl", `job "test"`, "All configuration at this revision"} { if !strings.Contains(body, want) { t.Fatalf("missing %q in %s", want, body) } } if got := strings.Count(body, "Execution configuration"); got != 2 { t.Fatalf("execution configs=%d body=%s", got, body) } } func TestRunPageShowsQueuedRunningAndSkippedChildCounts(t *testing.T) { dataDir, inboxDir := webDirs(t) store := history.Store{DataDir: dataDir} for _, event := range []history.Event{ {RunID: "state", ChildID: "run", Repo: "repo", Job: "push", Status: "queued"}, {RunID: "state", ChildID: "001", Repo: "repo", Job: "queued", Status: "queued"}, {RunID: "state", ChildID: "002", Repo: "repo", Job: "running", Status: "running"}, {RunID: "state", ChildID: "003", Repo: "repo", Job: "skipped", Status: "skipped"}, } { if _, err := store.Append(event); err != nil { t.Fatal(err) } } res := httptest.NewRecorder() Handler(dataDir, inboxDir, "", nil).ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/runs/state", nil)) body := res.Body.String() for _, want := range []string{"QUEUED.", "1 queued.", "1 running.", "1 skipped."} { if !strings.Contains(body, want) { t.Fatalf("missing %q in %s", want, body) } } } func webGit(t *testing.T, dir string, args ...string) string { t.Helper() cmd := exec.Command("git", args...) cmd.Dir = dir output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("git %v: %v: %s", args, err, output) } return string(output) } func webDirs(t *testing.T) (string, string) { t.Helper() root := t.TempDir() dataDir, inboxDir := filepath.Join(root, "data"), filepath.Join(root, "inbox") for _, dir := range []string{filepath.Join(dataDir, "events"), filepath.Join(dataDir, "active"), filepath.Join(dataDir, "live"), filepath.Join(dataDir, "logs"), inboxDir} { if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } } return dataDir, inboxDir } func TestLegacyLogMappingQuotesRunIDAndRequiresHistory(t *testing.T) { dataDir, inboxDir := webDirs(t) runID := "literal[1]" _, _ = (history.Store{DataDir: dataDir}).Append(history.Event{RunID: runID, Repo: "repo", Job: "check", Status: "success"}) store := logs.Store{DataDir: dataDir} file, err := store.CreateLive(runID + "-check") if err != nil { t.Fatal(err) } _, _ = file.WriteString("legacy text") _ = file.Close() if _, err := store.Finalize(runID + "-check"); err != nil { t.Fatal(err) } handler := Handler(dataDir, inboxDir, "", nil) res := httptest.NewRecorder() handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/runs/"+runID, nil)) if res.Code != http.StatusOK || !strings.Contains(res.Body.String(), "legacy text") || !strings.Contains(res.Body.String(), "check") { t.Fatalf("status=%d body=%s", res.Code, res.Body.String()) } res = httptest.NewRecorder() handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/runs/unknown/logs/legacy-001", nil)) if res.Code != http.StatusNotFound { t.Fatalf("unknown legacy=%d", res.Code) } } func TestLegacySanitizationCollisionIsNotMisattributed(t *testing.T) { dataDir, inboxDir := webDirs(t) now := time.Now().UTC() store := history.Store{DataDir: dataDir} _, _ = store.Append(history.Event{RunID: "old", Repo: "repo", Job: "a[b]", Status: "failed", Time: now}) _, _ = store.Append(history.Event{RunID: "old", Repo: "repo", Job: "a_b_", Status: "success", Time: now.Add(time.Second)}) logStore := logs.Store{DataDir: dataDir} file, err := logStore.CreateLive("old-a_b_") if err != nil { t.Fatal(err) } _, _ = file.WriteString("ambiguous") _ = file.Close() _, _ = logStore.Finalize("old-a_b_") handler := Handler(dataDir, inboxDir, "", nil) res := httptest.NewRecorder() handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/runs/old", nil)) if res.Code != http.StatusOK || strings.Count(res.Body.String(), "Ambiguous legacy log identity") != 2 { t.Fatalf("status=%d body=%s", res.Code, res.Body.String()) } res = httptest.NewRecorder() handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/runs/old/logs/legacy-001", nil)) if res.Code != http.StatusNotFound { t.Fatalf("collision raw=%d", res.Code) } } func TestHealthyDashboardDoesNotRenderFailureAlarm(t *testing.T) { dataDir, inboxDir := webDirs(t) _, _ = (history.Store{DataDir: dataDir}).Append(history.Event{RunID: "old", Repo: "smith", Job: "smoke", Status: "success", Time: time.Now().UTC()}) res := httptest.NewRecorder() Handler(dataDir, inboxDir, "", nil).ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/", nil)) body := res.Body.String() if !strings.Contains(body, `class="metrics clear"`) { t.Fatal("healthy dashboard markup missing") } css := embeddedPageCSS(t, body) for _, pattern := range []string{`\.metrics\.clear`, `(?:^|[{}])\s*\.top\s+a\s*\{\s*min-height\s*:\s*44px\s*;?`} { if !cssMatch(css, pattern) { t.Fatalf("page CSS missing pattern %q", pattern) } } if strings.Contains(body, `class="metric bad"`) || strings.Contains(body, "unknown · —") || strings.Contains(body, "— · 0s ago") { t.Fatalf("healthy dashboard is noisy: %s", body) } } func TestRunPagePrioritizesFailureAndCopiesCanonicalValues(t *testing.T) { dataDir, inboxDir := webDirs(t) now := time.Date(2026, 6, 18, 14, 34, 26, 0, time.UTC) fullRev := "0674195565099088373bd53507a1eb55f12a95d0" store := history.Store{DataDir: dataDir, Now: func() time.Time { return now }} _, _ = store.Append(history.Event{RunID: "42", ChildID: "run", Repo: "repo; unsafe", Job: "push", Rev: fullRev, Ref: "refs/heads/trunk", Status: "failed", Detail: "parent failed", StartedAt: now.Add(-2 * time.Minute), Time: now}) _, _ = store.Append(history.Event{RunID: "42", ChildID: "001", Repo: "repo; unsafe", Job: "test", Rev: fullRev, Ref: "refs/heads/trunk", Status: "failed", Detail: "child failed", Matrix: map[string]string{"go": "1.26"}, StartedAt: now.Add(-time.Minute), Time: now}) logStore := logs.Store{DataDir: dataDir} file, err := logStore.CreateLive("42-001") if err != nil { t.Fatal(err) } _, _ = file.WriteString("error: \n") _ = file.Close() if _, err := logStore.Finalize("42-001"); err != nil { t.Fatal(err) } res := httptest.NewRecorder() Handler(dataDir, inboxDir, "", nil).ServeHTTP(res, httptest.NewRequest(http.MethodGet, "/runs/42", nil)) body := res.Body.String() id := proquint.Encode(42) for _, want := range []string{ "Parent failure detail", "Affected execution", "Actual evidence", "go=1.26", `data-raw="` + id + `"`, `data-raw="` + fullRev + `"`, `data-raw="2026-06-18T14:34:26Z"`, `data-raw="120"`, `data-raw="60"`, "18 Jun 2026, 14:34 UTC", "luci log", "luci repo", "'repo; unsafe'", id, "001", "Clipboard unavailable. Exact value selected for manual copy.", "event.key==='Escape'", "details[open]", ".scalar:hover", "<actual evidence>", "log-fail", } { if !strings.Contains(body, want) { t.Fatalf("missing %q in %s", want, body) } } parent := strings.Index(body, "Parent failure detail") variant := strings.Index(body, "Affected execution") evidence := strings.Index(body, "Actual evidence") if !(parent >= 0 && parent < variant && variant < evidence) { t.Fatalf("failure hierarchy is out of order: parent=%d variant=%d evidence=%d", parent, variant, evidence) } short := strings.Split(id, "-")[0] shortButton := `data-raw="` + id + `" data-kind="full run ID" aria-label="Copy full run ID ` + id + `">` + short + `` if got := strings.Count(body, shortButton); got != 2 { t.Fatalf("short run ID displays=%d body=%s", got, body) } if strings.Contains(body, "repo; unsafe\" data-") { t.Fatalf("unquoted injected command attribute: %s", body) } }