Files
marketplace/cli/internal/mapping/labels_test.go
T
naudachu 9480e48312 feat: add the kettle CLI, replacing the plugin's Python scripts
The plugin resolved its issue store from `__file__`, which put it inside a
versioned plugin cache: issues written from one project were invisible from the
next, and `origin: local` files — the only copy of that work by definition —
were stranded a version bump at a time. The walk that answers "which directory
is the project" was written three times over, and in a linked worktree the three
disagreed. Both are runtime failures rather than logic ones, so the fix is a
compiled binary: one walk, imported rather than re-derived, and a layering rule
the build graph enforces instead of a grep.

Seven packages, knowledge flowing one way. `project` answers which directory is
the project and depends on nothing. `issue` is the domain — format, taxonomy,
validation, checkboxes, dependency graph, the store, eviction — offline, with no
tracker in it. `wire` holds the protocol shapes. `gitea` is the transport,
`mapping` the bridge, `config` the credentials, `cmd` the command tree. Four
tests hold the boundaries, each failing on a real mistake rather than a naming
convention.

The marker moves to `.kettle/` and the login pin moves out of the harness's
settings file into `.kettle/config.yaml`, which pins a login by NAME; the tokens
live in one file per machine, mode 0600, outside every working tree. That
retires the PreToolUse guard hook entirely — the binary holds its own
credentials, so a command running under a login nobody chose is not expressible
rather than caught.

`kettle init` migrates an older `tmp/issues` or `.tea/issues` store in, as a
move: a store left behind at an old path is one somebody edits by accident
months later.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 19:05:39 +05:00

96 lines
3.0 KiB
Go

package mapping
import (
"regexp"
"testing"
"git.noodles.cam/claude-skills/marketplace/cli/internal/issue"
)
var hexColor = regexp.MustCompile(`^#[0-9a-f]{6}$`)
// The canonical set is the domain's, and every member of it must have a color
// here. A type added over in the taxonomy that arrived as grey would look like
// a label somebody created by hand.
func TestEveryCanonicalLabelHasAColor(t *testing.T) {
for _, name := range issue.CanonicalLabels() {
color := LabelColor(name)
switch {
case color == DefaultColor:
t.Errorf("%s has no color of its own", name)
case !hexColor.MatchString(color):
t.Errorf("%s = %q, want #rrggbb in lower case", name, color)
}
}
// And the other direction: a color left behind after a label was retired
// paints nothing and is a lie about what the taxonomy holds.
if len(labelColors) != len(issue.CanonicalLabels()) {
t.Errorf("%d colors for %d canonical labels — one of the two lists moved without the other",
len(labelColors), len(issue.CanonicalLabels()))
}
// Anything outside the set is project-specific and nobody here can guess
// what it means.
if got := LabelColor("tech/sql"); got != DefaultColor {
t.Errorf("LabelColor(tech/sql) = %q, want the default", got)
}
}
func TestLabelSpecs(t *testing.T) {
cases := []struct {
name string
description string
exclusive bool
}{
{"type/bug", "Something behaves incorrectly in existing code", true},
{"type/draft", "Idea captured for later; not ready for work", true},
{"severity/critical", "", true},
// Exclusivity is a property of the namespace, not of the members the
// taxonomy happens to know.
{"type/spike", "", true},
{"tech/sql", "", false},
{"comp/appclick", "", false},
}
names := make([]string, len(cases))
for i, c := range cases {
names[i] = c.name
}
specs := LabelSpecs(names)
if len(specs) != len(cases) {
t.Fatalf("%d specs for %d names", len(specs), len(cases))
}
for i, c := range cases {
got := specs[i]
// The order is the taxonomy's: a bootstrap prints its plan in it, and
// two identical runs must not look like different ones.
if got.Name != c.name {
t.Fatalf("spec %d is %s, want %s", i, got.Name, c.name)
}
if got.Description != c.description {
t.Errorf("%s description = %q, want %q", c.name, got.Description, c.description)
}
if got.Exclusive != c.exclusive {
t.Errorf("%s exclusive = %v, want %v", c.name, got.Exclusive, c.exclusive)
}
if got.Color != LabelColor(c.name) {
t.Errorf("%s color = %q", c.name, got.Color)
}
}
}
func TestCanonicalLabelSpecsAreTheDomainsList(t *testing.T) {
specs := CanonicalLabelSpecs()
want := issue.CanonicalLabels()
if len(specs) != len(want) {
t.Fatalf("%d specs, want %d", len(specs), len(want))
}
for i, name := range want {
if specs[i].Name != name {
t.Errorf("spec %d = %s, want %s", i, specs[i].Name, name)
}
if !specs[i].Exclusive {
t.Errorf("%s must be exclusive — the canonical set IS the exclusive namespaces", name)
}
}
}