policy+tui: raise coverage above target (M1)

policy 66% -> 94.7%: TestLoad exercises the previously-untested Load path —
empty/missing path fall back to Default, valid YAML parses onto the Default base
(absent fields keep their default; overrides apply), malformed YAML and a
non-regular file (directory) both error. Documents that a map override entry is
replaced wholesale by yaml.v3 (Evaluate re-merges defaults, so it's harmless).

tui 57% -> 87.8%: cover Init, the "o" open branches (URL vs none), "s" skip and
prev/next boundary guards, non-key messages, enter-at-last-item, esc/ctrl+c
quit, openURL's command, and Run's headless empty-list path. The residual is the
TTY-only tea.Program.Run() launch, not unit-testable headless.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-07-15 11:20:46 -07:00
parent 2a480f3253
commit 96c9aeeb7a
2 changed files with 144 additions and 0 deletions
+46
View File
@@ -1,6 +1,8 @@
package policy
import (
"os"
"path/filepath"
"testing"
"time"
@@ -9,6 +11,50 @@ import (
"incredigo/internal/discover"
)
func TestLoad(t *testing.T) {
// Empty path → built-in default.
if p, err := Load(""); err != nil || p.Defaults.WarnAfter != 60 {
t.Fatalf("Load(\"\") = %+v, %v; want default", p, err)
}
// Missing file → default (not an error).
miss := filepath.Join(t.TempDir(), "nope.yaml")
if p, err := Load(miss); err != nil || p.Defaults.StaleAfter != 90 {
t.Fatalf("Load(missing) = %+v, %v; want default", p, err)
}
// Valid file → parsed onto the Default() base: a struct field absent from the
// file keeps its default (stale_after stays 90), while an override is applied.
f := filepath.Join(t.TempDir(), "policy.yaml")
if err := os.WriteFile(f, []byte("defaults:\n warn_after: 10d\noverrides:\n aws_key:\n warn_after: 5d\n stale_after: 45d\n"), 0o600); err != nil {
t.Fatal(err)
}
p, err := Load(f)
if err != nil {
t.Fatalf("Load(valid): %v", err)
}
if p.Defaults.WarnAfter != 10 || p.Defaults.StaleAfter != 90 {
t.Errorf("defaults = %+v, want {10 90} (stale kept from Default)", p.Defaults)
}
if got := p.Overrides[discover.KindAWSKey]; got.WarnAfter != 5 || got.StaleAfter != 45 {
t.Errorf("aws_key override = %+v, want {5 45}", got)
}
// Malformed YAML → error.
bad := filepath.Join(t.TempDir(), "bad.yaml")
if err := os.WriteFile(bad, []byte("defaults: [this is not a mapping"), 0o600); err != nil {
t.Fatal(err)
}
if _, err := Load(bad); err == nil {
t.Error("Load(malformed) returned no error")
}
// Unreadable (a directory, not a regular file) → a non-IsNotExist error.
if _, err := Load(t.TempDir()); err == nil {
t.Error("Load(directory) returned no error")
}
}
func TestDaysUnmarshal(t *testing.T) {
ok := map[string]Days{
"60": 60,
+98
View File
@@ -95,3 +95,101 @@ func TestEmptyModelView(t *testing.T) {
t.Errorf("empty view = %q", got)
}
}
func TestInitNoCmd(t *testing.T) {
if cmd := sampleModel().Init(); cmd != nil {
t.Errorf("Init returned a command, want nil")
}
}
// TestUpdateIgnoresNonKeyMsg proves a non-keyboard message leaves the model
// unchanged (the walk-through only responds to key presses).
func TestUpdateIgnoresNonKeyMsg(t *testing.T) {
m := sampleModel()
nm, cmd := m.Update(tea.WindowSizeMsg{Width: 80, Height: 24})
if cmd != nil {
t.Error("non-key msg produced a command")
}
if nm.(Model).cur != 0 {
t.Error("non-key msg moved the cursor")
}
}
// TestOpenBranches covers "o": on an item with a URL it emits an open command; on an
// item with no rotation page it does nothing.
func TestOpenBranches(t *testing.T) {
m := sampleModel() // item0 (git) has a URL; item1 (ssh) has none
_, cmd := m.Update(key("o"))
if cmd == nil {
t.Error("o on an item with a URL returned no command")
}
nm, _ := m.Update(key("n")) // advance to the URL-less ssh item
m = nm.(Model)
if _, cmd := m.Update(key("o")); cmd != nil {
t.Error("o on an item with no URL returned a command")
}
}
// TestSkipAndBoundaries covers "s" (skip → next) and the prev/next boundary guards.
func TestSkipAndBoundaries(t *testing.T) {
m := sampleModel()
// prev at the first item is a no-op.
nm, _ := m.Update(key("k"))
if nm.(Model).cur != 0 {
t.Error("k at index 0 moved the cursor")
}
m = nm.(Model)
// skip advances.
nm, _ = m.Update(key("s"))
m = nm.(Model)
if m.cur != 1 {
t.Errorf("s did not advance, cur=%d", m.cur)
}
// skip at the last item is a no-op.
nm, _ = m.Update(key("s"))
if nm.(Model).cur != 1 {
t.Error("s past end moved the cursor")
}
}
// TestEnterAtLastItemStays proves enter marks the last item done without advancing
// past the end of the list.
func TestEnterAtLastItemStays(t *testing.T) {
m := sampleModel()
nm, _ := m.Update(key("n")) // to last item (index 1)
m = nm.(Model)
nm, _ = m.Update(key("enter"))
m = nm.(Model)
if !m.done[1] {
t.Error("enter did not mark the last item done")
}
if m.cur != 1 {
t.Errorf("enter advanced past the last item, cur=%d", m.cur)
}
}
// TestQuitVariants covers the esc and ctrl+c quit keys alongside q.
func TestQuitVariants(t *testing.T) {
for _, k := range []tea.KeyMsg{{Type: tea.KeyEsc}, {Type: tea.KeyCtrlC}} {
nm, cmd := sampleModel().Update(k)
if !nm.(Model).quit || cmd == nil {
t.Errorf("%v did not quit", k)
}
}
}
// TestOpenURLCmd invokes the command returned by openURL: it is best-effort (the
// launcher may be absent on a headless host) and must return a nil message.
func TestOpenURLCmd(t *testing.T) {
if msg := openURL("https://example.com")(); msg != nil {
t.Errorf("openURL cmd returned %v, want nil", msg)
}
}
// TestRunEmptyReturnsNil covers Run's headless-safe path: no items → prints a notice
// and returns without launching the (TTY-requiring) Bubble Tea program.
func TestRunEmptyReturnsNil(t *testing.T) {
if err := Run(nil); err != nil {
t.Errorf("Run(nil) = %v, want nil", err)
}
}