← back to Cli Printing Press
refactor(cli): share generated naming helpers (#327)
52bde6b4336e9af38fd64d27e528f5e1935ca263 · 2026-04-26 20:29:24 -0700 · Trevin Chow
## Change: share generator/tools-manifest naming helpers
### Equivalence contract
- Inputs covered: template snake, envVarPlaceholder, oneline, mcpDescription, mcpDescriptionRich base behavior, and tools-manifest tool names/descriptions/header placeholders.
- Ordering preserved: yes. Call sites remain in the same loops and template execution order.
- Tie-breaking: unchanged; MCP auth annotation minority/tie behavior moved into the shared helper.
- Error semantics: unchanged; helpers are pure and return no errors.
- Laziness: unchanged.
- Short-circuit eval: unchanged for MCP description auth-annotation branches.
- Floating-point: N/A.
- RNG / hash order: unchanged; no maps added.
- Observable side-effects: generated files, tools-manifest JSON, scorecard/golden output are byte-identical.
- Type narrowing: N/A.
- Rerender behavior: N/A.
### Verification
- go fmt ./...
- go test ./internal/naming ./internal/generator ./internal/pipeline
- go test ./...
- go build -o ./printing-press ./cmd/printing-press
- go vet ./...
- golangci-lint run ./...
- scripts/golden.sh verify
No golden fixtures were updated.
LOC: repo Go LOC 84,578 -> 84,454 (-124).
Files touched
M internal/generator/generator.goM internal/generator/generator_test.goM internal/generator/plan_generate.goM internal/naming/naming.goM internal/naming/naming_test.goM internal/pipeline/toolsmanifest.goM internal/pipeline/toolsmanifest_test.go
Diff
commit 52bde6b4336e9af38fd64d27e528f5e1935ca263
Author: Trevin Chow <trevin@trevinchow.com>
Date: Sun Apr 26 20:29:24 2026 -0700
refactor(cli): share generated naming helpers (#327)
## Change: share generator/tools-manifest naming helpers
### Equivalence contract
- Inputs covered: template snake, envVarPlaceholder, oneline, mcpDescription, mcpDescriptionRich base behavior, and tools-manifest tool names/descriptions/header placeholders.
- Ordering preserved: yes. Call sites remain in the same loops and template execution order.
- Tie-breaking: unchanged; MCP auth annotation minority/tie behavior moved into the shared helper.
- Error semantics: unchanged; helpers are pure and return no errors.
- Laziness: unchanged.
- Short-circuit eval: unchanged for MCP description auth-annotation branches.
- Floating-point: N/A.
- RNG / hash order: unchanged; no maps added.
- Observable side-effects: generated files, tools-manifest JSON, scorecard/golden output are byte-identical.
- Type narrowing: N/A.
- Rerender behavior: N/A.
### Verification
- go fmt ./...
- go test ./internal/naming ./internal/generator ./internal/pipeline
- go test ./...
- go build -o ./printing-press ./cmd/printing-press
- go vet ./...
- golangci-lint run ./...
- scripts/golden.sh verify
No golden fixtures were updated.
LOC: repo Go LOC 84,578 -> 84,454 (-124).
---
internal/generator/generator.go | 96 +++--------------------
internal/generator/generator_test.go | 2 +-
internal/generator/plan_generate.go | 2 +-
internal/naming/naming.go | 78 +++++++++++++++++++
internal/naming/naming_test.go | 89 ++++++++++++++++++++-
internal/pipeline/toolsmanifest.go | 91 ++--------------------
internal/pipeline/toolsmanifest_test.go | 132 --------------------------------
7 files changed, 183 insertions(+), 307 deletions(-)
diff --git a/internal/generator/generator.go b/internal/generator/generator.go
index 82cee2e5..ba8ad6a7 100644
--- a/internal/generator/generator.go
+++ b/internal/generator/generator.go
@@ -166,7 +166,7 @@ func New(s *spec.APISpec, outputDir string) *Generator {
"upper": strings.ToUpper,
"join": strings.Join,
"camel": toCamel,
- "snake": toSnake,
+ "snake": naming.Snake,
"pascal": toPascal,
"goType": goType,
"goStructType": goStructType,
@@ -190,13 +190,13 @@ func New(s *spec.APISpec, outputDir string) *Generator {
"columnPlaceholders": columnPlaceholders,
"updateSet": updateSet,
"envVarField": envVarField,
- "envVarPlaceholder": envVarPlaceholder,
+ "envVarPlaceholder": naming.EnvVarPlaceholder,
"envVarIsBuiltinField": envVarIsBuiltinField,
"envVarBuiltinFieldName": envVarBuiltinFieldName,
"resolveEnvVarField": resolveEnvVarField,
"add": func(a, b int) int { return a + b },
- "oneline": oneline,
- "mcpDescription": mcpDescription,
+ "oneline": naming.OneLine,
+ "mcpDescription": naming.MCPDescription,
"mcpDescriptionRich": mcpDescriptionRich,
"flagName": flagName,
"paramIdent": paramIdent,
@@ -776,7 +776,7 @@ func safeDisplayURL(value string) string {
func (g *Generator) buildDomainContext() DomainContext {
ctx := DomainContext{
APIName: g.Spec.Name,
- Description: oneline(g.Spec.Description),
+ Description: naming.OneLine(g.Spec.Description),
Archetype: string(profiler.ArchetypeGeneric),
}
@@ -792,7 +792,7 @@ func (g *Generator) buildDomainContext() DomainContext {
for rName, r := range g.Spec.Resources {
rs := ResourceSummary{
Name: rName,
- Description: oneline(r.Description),
+ Description: naming.OneLine(r.Description),
Syncable: syncSet[rName],
Searchable: len(g.profile.SearchableFields[rName]) > 0,
}
@@ -1809,17 +1809,6 @@ func toCamel(s string) string {
return result
}
-func toSnake(s string) string {
- var result strings.Builder
- for i, r := range s {
- if unicode.IsUpper(r) && i > 0 {
- result.WriteRune('_')
- }
- result.WriteRune(unicode.ToLower(r))
- }
- return result.String()
-}
-
func toPascal(s string) string {
parts := strings.FieldsFunc(s, func(r rune) bool {
return r == '_' || r == '-' || !unicode.IsLetter(r) && !unicode.IsDigit(r)
@@ -2284,7 +2273,7 @@ var builtinConfigTags = map[string]string{
// envVarIsBuiltinField returns true if the env var's placeholder tag would
// collide with a hardcoded Config struct field tag.
func envVarIsBuiltinField(envVar string) bool {
- placeholder := envVarPlaceholder(envVar)
+ placeholder := naming.EnvVarPlaceholder(envVar)
_, ok := builtinConfigTags[placeholder]
return ok
}
@@ -2292,7 +2281,7 @@ func envVarIsBuiltinField(envVar string) bool {
// envVarBuiltinFieldName returns the Go field name of the hardcoded Config
// struct field that matches this env var's placeholder, or empty string if none.
func envVarBuiltinFieldName(envVar string) string {
- placeholder := envVarPlaceholder(envVar)
+ placeholder := naming.EnvVarPlaceholder(envVar)
return builtinConfigTags[placeholder]
}
@@ -2307,65 +2296,12 @@ func resolveEnvVarField(envVar string) string {
return envVarField(envVar)
}
-func oneline(s string) string {
- s = strings.ReplaceAll(s, "\r\n", " ")
- s = strings.ReplaceAll(s, "\n", " ")
- s = strings.ReplaceAll(s, "\r", " ")
- s = strings.ReplaceAll(s, `"`, `'`)
- s = strings.ReplaceAll(s, "\\", "")
- for strings.Contains(s, " ") {
- s = strings.ReplaceAll(s, " ", " ")
- }
- s = strings.TrimSpace(s)
- if len(s) > 120 {
- cut := s[:117]
- if idx := strings.LastIndex(cut, " "); idx > 60 {
- s = cut[:idx] + "..."
- } else {
- s = cut + "..."
- }
- }
- return s
-}
-
-// mcpDescription builds an MCP tool description with optional minority-side
-// auth annotation. Only annotates when the CLI has a mix of public and
-// auth-required tools. The minority side gets annotated:
-// - Public is minority → append "(public)"
-// - Auth-required is minority → append auth-type-specific suffix
-// - All same status or exact tie → no annotation
-func mcpDescription(desc string, noAuth bool, authType string, publicCount, totalCount int) string {
- authCount := totalCount - publicCount
- mixed := publicCount > 0 && authCount > 0
-
- if mixed {
- if noAuth && publicCount < authCount {
- // Public endpoints are the minority — mark them
- desc = desc + " (public)"
- } else if !noAuth && authCount < publicCount {
- // Auth-required endpoints are the minority — mark them
- switch authType {
- case "api_key":
- desc = desc + " (requires API key)"
- case "cookie", "composed":
- desc = desc + " (requires browser login)"
- case "oauth2", "bearer_token":
- desc = desc + " (requires auth)"
- default:
- desc = desc + " (requires auth)"
- }
- }
- }
-
- return oneline(desc)
-}
-
// mcpDescriptionRich builds an enriched MCP tool description that includes
// the base description plus response shape hints and method context.
// This gives agents enough information to choose the right tool without
// trial-and-error. Total length is capped to prevent token bloat.
func mcpDescriptionRich(desc string, noAuth bool, authType string, publicCount, totalCount int, method, respType, respItem string) string {
- base := mcpDescription(desc, noAuth, authType, publicCount, totalCount)
+ base := naming.MCPDescription(desc, noAuth, authType, publicCount, totalCount)
var suffix string
@@ -2670,20 +2606,6 @@ func sortedEndpointNames(endpoints map[string]spec.Endpoint) []string {
return names
}
-func envVarPlaceholder(envVar string) string {
- // STYTCH_PROJECT_ID -> project_id (the placeholder in the format string)
- parts := strings.Split(envVar, "_")
- if len(parts) <= 1 {
- return strings.ToLower(envVar)
- }
- // Skip the first part (tool name prefix) and join the rest
- var lower []string
- for _, p := range parts[1:] {
- lower = append(lower, strings.ToLower(p))
- }
- return strings.Join(lower, "_")
-}
-
// isGraphQLSpec returns true if the spec was produced by a GraphQL SDL parser.
// Detection heuristic: all list endpoints have path "/graphql".
func isGraphQLSpec(s *spec.APISpec) bool {
diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go
index 29e9c2ad..97a5adda 100644
--- a/internal/generator/generator_test.go
+++ b/internal/generator/generator_test.go
@@ -2965,7 +2965,7 @@ func TestMCPDescription(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
- got := mcpDescription(tt.desc, tt.noAuth, tt.authType, tt.publicCount, tt.totalCount)
+ got := naming.MCPDescription(tt.desc, tt.noAuth, tt.authType, tt.publicCount, tt.totalCount)
assert.Equal(t, tt.want, got)
})
}
diff --git a/internal/generator/plan_generate.go b/internal/generator/plan_generate.go
index 7f6764cf..874e440e 100644
--- a/internal/generator/plan_generate.go
+++ b/internal/generator/plan_generate.go
@@ -81,7 +81,7 @@ func GenerateFromPlan(planSpec *PlanSpec, outputDir string) error {
"upper": strings.ToUpper,
"pascal": toPascal,
"camel": toCamel,
- "snake": toSnake,
+ "snake": naming.Snake,
"kebab": toKebab,
"currentYear": func() string { return strconv.Itoa(time.Now().Year()) },
"modulePath": func() string { return naming.CLI(cliName) },
diff --git a/internal/naming/naming.go b/internal/naming/naming.go
index b75ea278..3d3b0524 100644
--- a/internal/naming/naming.go
+++ b/internal/naming/naming.go
@@ -66,6 +66,84 @@ func EnvPrefix(name string) string {
return out
}
+// Snake converts CamelCase to snake_case for generated tool name segments.
+// Hyphens are intentionally preserved to match the historical MCP template
+// helper behavior.
+func Snake(s string) string {
+ var result strings.Builder
+ for i, r := range s {
+ if unicode.IsUpper(r) && i > 0 {
+ result.WriteRune('_')
+ }
+ result.WriteRune(unicode.ToLower(r))
+ }
+ return result.String()
+}
+
+// EnvVarPlaceholder derives the placeholder name from an environment variable.
+// DUB_TOKEN -> token, STYTCH_PROJECT_ID -> project_id.
+func EnvVarPlaceholder(envVar string) string {
+ parts := strings.Split(envVar, "_")
+ if len(parts) <= 1 {
+ return strings.ToLower(envVar)
+ }
+ lower := make([]string, 0, len(parts)-1)
+ for _, p := range parts[1:] {
+ lower = append(lower, strings.ToLower(p))
+ }
+ return strings.Join(lower, "_")
+}
+
+// OneLine normalizes generated descriptions for compact template and manifest
+// output.
+func OneLine(s string) string {
+ s = strings.ReplaceAll(s, "\r\n", " ")
+ s = strings.ReplaceAll(s, "\n", " ")
+ s = strings.ReplaceAll(s, "\r", " ")
+ s = strings.ReplaceAll(s, `"`, `'`)
+ s = strings.ReplaceAll(s, "\\", "")
+ for strings.Contains(s, " ") {
+ s = strings.ReplaceAll(s, " ", " ")
+ }
+ s = strings.TrimSpace(s)
+ if len(s) > 120 {
+ cut := s[:117]
+ if idx := strings.LastIndex(cut, " "); idx > 60 {
+ s = cut[:idx] + "..."
+ } else {
+ s = cut + "..."
+ }
+ }
+ return s
+}
+
+// MCPDescription builds an MCP tool description with optional minority-side
+// auth annotation. It annotates only when an API has a mix of public and
+// auth-required tools, and only the minority side gets annotated.
+func MCPDescription(desc string, noAuth bool, authType string, publicCount, totalCount int) string {
+ authCount := totalCount - publicCount
+ mixed := publicCount > 0 && authCount > 0
+
+ if mixed {
+ if noAuth && publicCount < authCount {
+ desc = desc + " (public)"
+ } else if !noAuth && authCount < publicCount {
+ switch authType {
+ case "api_key":
+ desc = desc + " (requires API key)"
+ case "cookie", "composed":
+ desc = desc + " (requires browser login)"
+ case "oauth2", "bearer_token":
+ desc = desc + " (requires auth)"
+ default:
+ desc = desc + " (requires auth)"
+ }
+ }
+ }
+
+ return OneLine(desc)
+}
+
func DogfoodBinary(name string) string {
return CLI(name) + "-dogfood"
}
diff --git a/internal/naming/naming_test.go b/internal/naming/naming_test.go
index ba838d64..86bfa127 100644
--- a/internal/naming/naming_test.go
+++ b/internal/naming/naming_test.go
@@ -1,6 +1,9 @@
package naming
-import "testing"
+import (
+ "strings"
+ "testing"
+)
func TestTrimCLISuffix(t *testing.T) {
tests := map[string]string{
@@ -67,6 +70,90 @@ func TestEnvPrefix(t *testing.T) {
}
}
+func TestSnake(t *testing.T) {
+ tests := map[string]string{
+ "Pets": "pets",
+ "GetInventory": "get_inventory",
+ "List": "list",
+ "PublicList": "public_list",
+ "APIKeys": "a_p_i_keys",
+ "simple": "simple",
+ "already_snake": "already_snake",
+ "with-hyphen": "with-hyphen",
+ }
+
+ for input, want := range tests {
+ if got := Snake(input); got != want {
+ t.Fatalf("Snake(%q) = %q, want %q", input, got, want)
+ }
+ }
+}
+
+func TestEnvVarPlaceholder(t *testing.T) {
+ tests := map[string]string{
+ "DUB_TOKEN": "token",
+ "STYTCH_PROJECT_ID": "project_id",
+ "STEAM_WEB_API_KEY": "web_api_key",
+ "TOKEN": "token",
+ "GITHUB_TOKEN": "token",
+ }
+
+ for input, want := range tests {
+ if got := EnvVarPlaceholder(input); got != want {
+ t.Fatalf("EnvVarPlaceholder(%q) = %q, want %q", input, got, want)
+ }
+ }
+}
+
+func TestOneLine(t *testing.T) {
+ tests := map[string]string{
+ "line one\nline two": "line one line two",
+ "too many spaces": "too many spaces",
+ `say "hello"`: "say 'hello'",
+ " spaces ": "spaces",
+ }
+
+ for input, want := range tests {
+ if got := OneLine(input); got != want {
+ t.Fatalf("OneLine(%q) = %q, want %q", input, got, want)
+ }
+ }
+
+ if got := OneLine(string(make([]byte, 200))); len(got) > 120 {
+ t.Fatalf("OneLine(long string) length = %d, want <= 120", len(got))
+ }
+}
+
+func TestMCPDescription(t *testing.T) {
+ tests := []struct {
+ name string
+ desc string
+ noAuth bool
+ authType string
+ publicCount int
+ totalCount int
+ wantSuffix string
+ }{
+ {"public minority gets annotated", "List items", true, "api_key", 2, 10, "(public)"},
+ {"auth minority gets api key annotation", "Create item", false, "api_key", 8, 10, "(requires API key)"},
+ {"auth minority gets cookie annotation", "Create item", false, "cookie", 8, 10, "(requires browser login)"},
+ {"all auth has no annotation", "Create item", false, "api_key", 0, 10, ""},
+ {"all public has no annotation", "List items", true, "api_key", 10, 10, ""},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := MCPDescription(tt.desc, tt.noAuth, tt.authType, tt.publicCount, tt.totalCount)
+ if tt.wantSuffix != "" && !strings.Contains(got, tt.wantSuffix) {
+ t.Fatalf("MCPDescription() = %q, want suffix %q", got, tt.wantSuffix)
+ }
+ if tt.wantSuffix == "" && (strings.Contains(got, "(public)") || strings.Contains(got, "(requires")) {
+ t.Fatalf("MCPDescription() = %q, want no auth annotation", got)
+ }
+ })
+ }
+}
+
func TestIsCLIDirName(t *testing.T) {
if !IsCLIDirName("stripe-pp-cli-3") {
t.Fatal("expected suffixed pp-cli directory to be recognized")
diff --git a/internal/pipeline/toolsmanifest.go b/internal/pipeline/toolsmanifest.go
index 0bdbc0c6..9f63685c 100644
--- a/internal/pipeline/toolsmanifest.go
+++ b/internal/pipeline/toolsmanifest.go
@@ -9,8 +9,8 @@ import (
"path/filepath"
"sort"
"strings"
- "unicode"
+ "github.com/mvanhorn/cli-printing-press/v2/internal/naming"
"github.com/mvanhorn/cli-printing-press/v2/internal/spec"
)
@@ -131,8 +131,8 @@ func WriteToolsManifest(dir string, parsed *spec.APISpec) error {
if cookieOrComposed && !endpoint.NoAuth {
continue
}
- toolName := toolSnake(rName) + "_" + toolSnake(eName)
- desc := mcpDescriptionForManifest(endpoint.Description, endpoint.NoAuth, parsed.Auth.Type, public, total)
+ toolName := naming.Snake(rName) + "_" + naming.Snake(eName)
+ desc := naming.MCPDescription(endpoint.Description, endpoint.NoAuth, parsed.Auth.Type, public, total)
tool := buildManifestTool(toolName, desc, endpoint)
manifest.Tools = append(manifest.Tools, tool)
}
@@ -147,8 +147,8 @@ func WriteToolsManifest(dir string, parsed *spec.APISpec) error {
if cookieOrComposed && !endpoint.NoAuth {
continue
}
- toolName := toolSnake(rName) + "_" + toolSnake(subName) + "_" + toolSnake(eName)
- desc := mcpDescriptionForManifest(endpoint.Description, endpoint.NoAuth, parsed.Auth.Type, public, total)
+ toolName := naming.Snake(rName) + "_" + naming.Snake(subName) + "_" + naming.Snake(eName)
+ desc := naming.MCPDescription(endpoint.Description, endpoint.NoAuth, parsed.Auth.Type, public, total)
tool := buildManifestTool(toolName, desc, endpoint)
manifest.Tools = append(manifest.Tools, tool)
}
@@ -230,7 +230,7 @@ func normalizeAuthFormat(format string, envVars []string) string {
}
result := format
for _, envVar := range envVars {
- derived := envVarPlaceholder(envVar)
+ derived := naming.EnvVarPlaceholder(envVar)
if derived != strings.ToLower(envVar) {
// Replace the derived placeholder with the env var name.
result = strings.ReplaceAll(result, "{"+derived+"}", "{"+envVar+"}")
@@ -247,21 +247,6 @@ func normalizeAuthFormat(format string, envVars []string) string {
return result
}
-// envVarPlaceholder derives the placeholder name from an env var.
-// DUB_TOKEN -> token, STYTCH_PROJECT_ID -> project_id.
-// Mirrors the logic in internal/generator/generator.go:1331.
-func envVarPlaceholder(envVar string) string {
- parts := strings.Split(envVar, "_")
- if len(parts) <= 1 {
- return strings.ToLower(envVar)
- }
- lower := make([]string, 0, len(parts)-1)
- for _, p := range parts[1:] {
- lower = append(lower, strings.ToLower(p))
- }
- return strings.Join(lower, "_")
-}
-
// normalizeParamType ensures a consistent type string. Empty types default
// to "string".
func normalizeParamType(t string) string {
@@ -271,70 +256,6 @@ func normalizeParamType(t string) string {
return t
}
-// toolSnake converts CamelCase to snake_case for tool name segments.
-// This is a copy of toSnake from internal/generator/generator.go:794.
-// It does NOT convert hyphens, matching the MCP template's {{snake}} function.
-func toolSnake(s string) string {
- var result strings.Builder
- for i, r := range s {
- if unicode.IsUpper(r) && i > 0 {
- result.WriteRune('_')
- }
- result.WriteRune(unicode.ToLower(r))
- }
- return result.String()
-}
-
-// mcpDescriptionForManifest replicates the mcpDescription template function
-// from internal/generator/generator.go:1078 — adds minority-side annotations
-// for mixed auth/no-auth APIs.
-func mcpDescriptionForManifest(desc string, noAuth bool, authType string, publicCount, totalCount int) string {
- authCount := totalCount - publicCount
- mixed := publicCount > 0 && authCount > 0
-
- if mixed {
- if noAuth && publicCount < authCount {
- desc = desc + " (public)"
- } else if !noAuth && authCount < publicCount {
- switch authType {
- case "api_key":
- desc = desc + " (requires API key)"
- case "cookie", "composed":
- desc = desc + " (requires browser login)"
- case "oauth2", "bearer_token":
- desc = desc + " (requires auth)"
- default:
- desc = desc + " (requires auth)"
- }
- }
- }
-
- return onelineForManifest(desc)
-}
-
-// onelineForManifest replicates the oneline template function from
-// internal/generator/generator.go:1051.
-func onelineForManifest(s string) string {
- s = strings.ReplaceAll(s, "\r\n", " ")
- s = strings.ReplaceAll(s, "\n", " ")
- s = strings.ReplaceAll(s, "\r", " ")
- s = strings.ReplaceAll(s, `"`, `'`)
- s = strings.ReplaceAll(s, "\\", "")
- for strings.Contains(s, " ") {
- s = strings.ReplaceAll(s, " ", " ")
- }
- s = strings.TrimSpace(s)
- if len(s) > 120 {
- cut := s[:117]
- if idx := strings.LastIndex(cut, " "); idx > 60 {
- s = cut[:idx] + "..."
- } else {
- s = cut + "..."
- }
- }
- return s
-}
-
// sortedResourceKeys returns sorted keys from a map[string]spec.Resource.
func sortedResourceKeys(m map[string]spec.Resource) []string {
keys := make([]string, 0, len(m))
diff --git a/internal/pipeline/toolsmanifest_test.go b/internal/pipeline/toolsmanifest_test.go
index 7c9ad30f..7b226509 100644
--- a/internal/pipeline/toolsmanifest_test.go
+++ b/internal/pipeline/toolsmanifest_test.go
@@ -740,96 +740,6 @@ func TestWriteToolsManifest_EmptyParamType(t *testing.T) {
assert.Equal(t, "string", got.Tools[0].Params[0].Type, "empty type should default to string")
}
-func TestToolSnake(t *testing.T) {
- tests := []struct {
- input string
- want string
- }{
- {"Pets", "pets"},
- {"GetInventory", "get_inventory"},
- {"List", "list"},
- {"PublicList", "public_list"},
- {"APIKeys", "a_p_i_keys"}, // mirrors toSnake behavior for consecutive caps
- {"simple", "simple"},
- {"already_snake", "already_snake"},
- {"with-hyphen", "with-hyphen"}, // does NOT convert hyphens
- }
- for _, tt := range tests {
- t.Run(tt.input, func(t *testing.T) {
- assert.Equal(t, tt.want, toolSnake(tt.input))
- })
- }
-}
-
-func TestMcpDescriptionForManifest(t *testing.T) {
- tests := []struct {
- name string
- desc string
- noAuth bool
- authType string
- publicCount int
- totalCount int
- wantSuffix string
- }{
- {
- name: "public minority gets annotated",
- desc: "List items",
- noAuth: true,
- authType: "api_key",
- publicCount: 2,
- totalCount: 10,
- wantSuffix: "(public)",
- },
- {
- name: "auth minority gets annotated with api_key",
- desc: "Create item",
- noAuth: false,
- authType: "api_key",
- publicCount: 8,
- totalCount: 10,
- wantSuffix: "(requires API key)",
- },
- {
- name: "auth minority gets annotated with cookie",
- desc: "Create item",
- noAuth: false,
- authType: "cookie",
- publicCount: 8,
- totalCount: 10,
- wantSuffix: "(requires browser login)",
- },
- {
- name: "no annotation when all auth",
- desc: "Create item",
- noAuth: false,
- authType: "api_key",
- publicCount: 0,
- totalCount: 10,
- wantSuffix: "",
- },
- {
- name: "no annotation when all public",
- desc: "List items",
- noAuth: true,
- authType: "api_key",
- publicCount: 10,
- totalCount: 10,
- wantSuffix: "",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := mcpDescriptionForManifest(tt.desc, tt.noAuth, tt.authType, tt.publicCount, tt.totalCount)
- if tt.wantSuffix != "" {
- assert.Contains(t, result, tt.wantSuffix)
- } else {
- assert.NotContains(t, result, "(public)")
- assert.NotContains(t, result, "(requires")
- }
- })
- }
-}
-
func TestNormalizeAuthFormat(t *testing.T) {
tests := []struct {
name string
@@ -887,45 +797,3 @@ func TestNormalizeAuthFormat(t *testing.T) {
})
}
}
-
-func TestEnvVarPlaceholder(t *testing.T) {
- tests := []struct {
- input string
- want string
- }{
- {"DUB_TOKEN", "token"},
- {"STYTCH_PROJECT_ID", "project_id"},
- {"STEAM_WEB_API_KEY", "web_api_key"},
- {"TOKEN", "token"},
- {"GITHUB_TOKEN", "token"},
- }
- for _, tt := range tests {
- t.Run(tt.input, func(t *testing.T) {
- assert.Equal(t, tt.want, envVarPlaceholder(tt.input))
- })
- }
-}
-
-func TestOnelineForManifest(t *testing.T) {
- tests := []struct {
- name string
- input string
- want string
- }{
- {"newlines collapsed", "line one\nline two", "line one line two"},
- {"double spaces collapsed", "too many spaces", "too many spaces"},
- {"quotes replaced", `say "hello"`, "say 'hello'"},
- {"long string truncated", string(make([]byte, 200)), ""},
- {"trimmed", " spaces ", "spaces"},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := onelineForManifest(tt.input)
- if tt.name == "long string truncated" {
- assert.LessOrEqual(t, len(result), 120)
- } else {
- assert.Equal(t, tt.want, result)
- }
- })
- }
-}
← a2fe1581 refactor(cli): simplify scorecard dimension bookkeeping (#32
·
back to Cli Printing Press
·
fix(ci): include refactors in release notes (#328) df551b37 →