package cmd // The seam between the registry and the documents written from it. Both halves // are in this package's reach, so it is asserted here rather than inferred from // a generated file downstream. import ( "sort" "strings" "testing" "git.noodles.cam/claude-skills/marketplace/cli/internal/scaffold" ) // A group with no document is a group whose flag table is written nowhere, and // nothing would say so: gen walks the documents, not the registry, so the // commands would simply be absent. This is the test that makes adding a group a // two-step change rather than a silent one-step mistake. func TestEveryGroupHasSomewhereToBeWritten(t *testing.T) { inRegistry := map[string]bool{} for _, c := range Commands() { if c.Group == "" { t.Errorf("command %q has no group, so it is in no document at all", c.Name) continue } inRegistry[c.Group] = true } for _, g := range scaffold.Groups() { if !inRegistry[g] { t.Errorf("scaffold ships a document for group %q, which no command is in", g) } } for g := range inRegistry { if scaffold.PathFor(g) == "" { t.Errorf("group %q has commands and no document — add one to internal/scaffold/assets", g) } } } // Every document renders, and the flag table inside it is this binary's. A // command whose Long text spelled a region marker out in full would end the // generated block inside itself, and renderAll is where that is caught. func TestEveryDocumentRendersWithItsOwnFlags(t *testing.T) { files, err := renderAll() if err != nil { t.Fatalf("rendering the tree failed: %v", err) } if len(files) != len(scaffold.Files()) { t.Fatalf("rendered %d of %d documents", len(files), len(scaffold.Files())) } for _, f := range files { if f.Group == "" { continue } for _, c := range commandsIn(f.Group) { if !strings.Contains(f.Body, "## `"+c.Usage()+"`") { t.Errorf("%s does not document `%s`", f.Path, c.Usage()) } for _, flag := range c.Flags() { if !strings.Contains(f.Body, "| `--"+flag.Name+"` |") { t.Errorf("%s documents `%s` without --%s", f.Path, c.Name, flag.Name) } } } if strings.Contains(f.Body, genOpen+"\n"+genOpen) { t.Errorf("%s has a doubled marker", f.Path) } } } // Rendering twice produces the same bytes. Everything downstream — --check, the // idempotence of init, a diff in somebody's repository — is built on it. func TestRenderingIsDeterministic(t *testing.T) { a, err := renderAll() if err != nil { t.Fatal(err) } b, err := renderAll() if err != nil { t.Fatal(err) } if len(a) != len(b) { t.Fatalf("two renders produced %d and %d documents", len(a), len(b)) } for i := range a { if a[i].Path != b[i].Path { t.Fatalf("document %d is %s then %s", i, a[i].Path, b[i].Path) } if a[i].Body != b[i].Body { t.Errorf("%s differs between two renders", a[i].Path) } } } // The guess the wizard offers for --repo. It is worth exactly what it costs — // a file read — so anything it cannot make sense of must answer "" and let the // operator type it, rather than proposing half an address. func TestOwnerNameReadsTheSpellingsGitActuallyWrites(t *testing.T) { cases := []struct{ url, want string }{ {"git@git.example.com:claude-skills/marketplace.git", "claude-skills/marketplace"}, {"git@git.example.com:claude-skills/marketplace", "claude-skills/marketplace"}, {"https://git.example.com/claude-skills/marketplace.git", "claude-skills/marketplace"}, {"https://git.example.com/claude-skills/marketplace", "claude-skills/marketplace"}, {"ssh://git@git.example.com:2222/claude-skills/marketplace.git", "claude-skills/marketplace"}, {" https://git.example.com/owner/name.git ", "owner/name"}, // Deeper paths: a Gitea instance served under a prefix still ends in // owner/name, and the last two elements are the address. {"https://example.com/git/owner/name.git", "owner/name"}, // Nothing that can be read as an address. {"", ""}, {"https://git.example.com/", ""}, {"https://git.example.com/lonely", ""}, {"/a/local/path", "local/path"}, } for _, tc := range cases { if got := ownerName(tc.url); got != tc.want { t.Errorf("ownerName(%q) = %q, want %q", tc.url, got, tc.want) } } } // The document list is stable and sorted, which is what makes a receipt from one // machine comparable with a receipt from another. func TestScaffoldFilesAreSorted(t *testing.T) { files := scaffold.Files() paths := make([]string, len(files)) for i, f := range files { paths[i] = f.Path } if !sort.StringsAreSorted(paths) { t.Errorf("the document list is not sorted: %v", paths) } }