From c95c89dea14b5349ffc15453f00973023c1f972e Mon Sep 17 00:00:00 2001 From: Jordan Coin Jackson Date: Fri, 3 Apr 2026 09:05:04 -0400 Subject: [PATCH] test: improve cmd coverage from 63.4% to 67.6% --- .github/workflows/ci.yml | 2 +- cmd/drift_test.go | 43 +++++++++++++ cmd/serve_test.go | 90 +++++++++++++++++++++++++++ cmd/skill_test.go | 130 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 cmd/serve_test.go create mode 100644 cmd/skill_test.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 386ba0b..e804e70 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: run: | total=$(go tool cover -func=coverage.out | awk '/^total:/ {gsub("%","",$3); print $3}') # Current enforced coverage floor. Codex PRs raise this incrementally toward 90%. - min=50.0 + min=55.0 awk -v t="$total" -v m="$min" 'BEGIN { if (t+0 < m+0) { printf "Coverage %.1f%% is below floor %.1f%%\n", t, m diff --git a/cmd/drift_test.go b/cmd/drift_test.go index 811b23d..544457a 100644 --- a/cmd/drift_test.go +++ b/cmd/drift_test.go @@ -88,3 +88,46 @@ func TestDriftWarning_Fields(t *testing.T) { t.Error("unexpected commits behind") } } + +func TestResolveCodePaths(t *testing.T) { + tests := []struct { + name string + subsystem string + paths map[string][]string + wantPrefix string + }{ + { + name: "configured path strips globs", + subsystem: "watching", + paths: map[string][]string{ + "watching": {"watch/**", "cmd/hooks.go"}, + }, + wantPrefix: "watch/", + }, + { + name: "fallback to guessed paths", + subsystem: "scanning", + paths: map[string][]string{}, + wantPrefix: "scanner/", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := resolveCodePaths(tt.subsystem, tt.paths) + if len(got) == 0 { + t.Fatal("expected at least one path") + } + found := false + for _, p := range got { + if p == tt.wantPrefix { + found = true + break + } + } + if !found { + t.Fatalf("expected %q in paths %v", tt.wantPrefix, got) + } + }) + } +} diff --git a/cmd/serve_test.go b/cmd/serve_test.go new file mode 100644 index 0000000..6305f13 --- /dev/null +++ b/cmd/serve_test.go @@ -0,0 +1,90 @@ +package cmd + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestWriteJSON_WritesIndentedJSONAndContentType(t *testing.T) { + tests := []struct { + name string + payload map[string]interface{} + }{ + { + name: "simple map", + payload: map[string]interface{}{ + "status": "ok", + "count": 2, + }, + }, + { + name: "nested payload", + payload: map[string]interface{}{ + "outer": map[string]interface{}{"inner": true}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rr := httptest.NewRecorder() + writeJSON(rr, tt.payload) + + if got := rr.Header().Get("Content-Type"); got != "application/json" { + t.Fatalf("expected content-type application/json, got %q", got) + } + if rr.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d", rr.Code) + } + if !strings.Contains(rr.Body.String(), "\n ") { + t.Fatalf("expected indented JSON output, got %q", rr.Body.String()) + } + + var decoded map[string]interface{} + if err := json.Unmarshal(rr.Body.Bytes(), &decoded); err != nil { + t.Fatalf("expected valid JSON body: %v", err) + } + }) + } +} + +func TestWriteError_WritesStatusJSONAndContentType(t *testing.T) { + tests := []struct { + name string + code int + msg string + wantCode int + wantSubstr string + }{ + {name: "bad request", code: http.StatusBadRequest, msg: "bad input", wantCode: http.StatusBadRequest, wantSubstr: "bad input"}, + {name: "internal server error", code: http.StatusInternalServerError, msg: "boom", wantCode: http.StatusInternalServerError, wantSubstr: "boom"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rr := httptest.NewRecorder() + writeError(rr, tt.code, tt.msg) + + if rr.Code != tt.wantCode { + t.Fatalf("expected status %d, got %d", tt.wantCode, rr.Code) + } + if got := rr.Header().Get("Content-Type"); got != "application/json" { + t.Fatalf("expected content-type application/json, got %q", got) + } + + var decoded map[string]string + if err := json.Unmarshal(rr.Body.Bytes(), &decoded); err != nil { + t.Fatalf("expected valid JSON body: %v", err) + } + if decoded["error"] != tt.msg { + t.Fatalf("expected error message %q, got %q", tt.msg, decoded["error"]) + } + if !strings.Contains(rr.Body.String(), tt.wantSubstr) { + t.Fatalf("expected body to contain %q, got %q", tt.wantSubstr, rr.Body.String()) + } + }) + } +} diff --git a/cmd/skill_test.go b/cmd/skill_test.go new file mode 100644 index 0000000..b36acc9 --- /dev/null +++ b/cmd/skill_test.go @@ -0,0 +1,130 @@ +package cmd + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestRunSkill_PrintsUsageForUnknownOrMissingSubcommand(t *testing.T) { + tests := []struct { + name string + args []string + }{ + {name: "no arguments", args: nil}, + {name: "unknown subcommand", args: []string{"bogus"}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + out := captureOutput(func() { + RunSkill(tt.args, t.TempDir()) + }) + + checks := []string{ + "Usage: codemap skill ", + "Commands:", + "list", + "show ", + "init", + } + for _, check := range checks { + if !strings.Contains(out, check) { + t.Fatalf("expected output to contain %q, got:\n%s", check, out) + } + } + }) + } +} + +func TestRunSkillList_PrintsBuiltinSkills(t *testing.T) { + root := t.TempDir() + + out := captureOutput(func() { + runSkillList(root) + }) + + checks := []string{ + "Available skills", + "[builtin]", + } + for _, check := range checks { + if !strings.Contains(out, check) { + t.Fatalf("expected output to contain %q, got:\n%s", check, out) + } + } +} + +func TestRunSkillShow_PrintsBuiltinSkillDetails(t *testing.T) { + root := t.TempDir() + + out := captureOutput(func() { + runSkillShow(root, "explore") + }) + + checks := []string{ + "# explore", + "Source: builtin", + "Description:", + } + for _, check := range checks { + if !strings.Contains(out, check) { + t.Fatalf("expected output to contain %q, got:\n%s", check, out) + } + } +} + +func TestRunSkillInit_CreatesTemplateAndIsIdempotent(t *testing.T) { + root := t.TempDir() + path := filepath.Join(root, ".codemap", "skills", "my-skill.md") + + tests := []struct { + name string + run func() string + wantContains []string + }{ + { + name: "first run creates template", + run: func() string { + return captureOutput(func() { + runSkillInit(root) + }) + }, + wantContains: []string{ + "Created skill template", + "Run 'codemap skill list'", + }, + }, + { + name: "second run reports already exists", + run: func() string { + return captureOutput(func() { + runSkillInit(root) + }) + }, + wantContains: []string{ + "Skill template already exists", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + out := tt.run() + for _, check := range tt.wantContains { + if !strings.Contains(out, check) { + t.Fatalf("expected output to contain %q, got:\n%s", check, out) + } + } + }) + } + + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("expected template file at %s: %v", path, err) + } + if !strings.Contains(string(data), "name: my-skill") { + t.Fatalf("expected template content in %s, got:\n%s", path, string(data)) + } +}