Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions generator/classic/manifest_path_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2026, Jamf Software LLC

package classic

import (
"fmt"
"sort"
"strings"
"testing"
)

// isCleanPathSegment reports whether s is a well-formed single URL path segment
// for the Classic API: non-empty, no whitespace, no slashes (it is one segment,
// not a path), and no control characters. A typo'd token in resources.yaml
// (stray space, leading slash, embedded slash) produces a broken /JSSResource/
// URL at runtime that nothing else catches — this is the surface that proof guards.
func isCleanPathSegment(s string) bool {
if s == "" {
return false
}
for _, r := range s {
if r <= ' ' || r == '/' || r == '\\' {
return false
}
}
return true
}

// TestProofClassicManifestPathsWellFormed validates the hand-maintained classic
// manifest. The generated modern paths come straight from OpenAPI specs and are
// tautologically valid, but specs/classic/resources.yaml is hand-edited, so its
// path tokens are a real drift surface. We assert every Path/IDPath/GroupPath
// token is a clean single segment and that the constructed /JSSResource/{Path}
// URL is well-formed.
func TestProofClassicManifestPathsWellFormed(t *testing.T) {
resources, err := ParseManifest("../../specs/classic/resources.yaml")
if err != nil {
t.Fatalf("ParseManifest(real manifest): %v", err)
}

// Floor sanity check: an empty parse would make "no violations" silent.
const minResources = 10
if len(resources) < minResources {
t.Fatalf("parsed only %d classic resources (expected >= %d) — manifest or parser regressed", len(resources), minResources)
}

var violations []string
for _, r := range resources {
label := r.CLIName
if label == "" {
label = r.Name
}

// A clean Path token guarantees the constructed "/JSSResource/"+Path URL
// is well-formed (no doubled slash, no whitespace), so the segment check
// is the whole check — no separate URL assertion is needed.
if !isCleanPathSegment(r.Path) {
violations = append(violations, fmt.Sprintf("%s: Path token %q is not a clean URL segment", label, r.Path))
}
// IDPath is always populated by ParseManifest (defaults to "id"); the
// check still validates custom id_path tokens such as "groupid".
if !isCleanPathSegment(r.IDPath) {
violations = append(violations, fmt.Sprintf("%s: IDPath token %q is not a clean URL segment", label, r.IDPath))
}
// GroupPath is only set for resources with group endpoints.
if r.GroupPath != "" && !isCleanPathSegment(r.GroupPath) {
violations = append(violations, fmt.Sprintf("%s: GroupPath token %q is not a clean URL segment", label, r.GroupPath))
}
}

if len(violations) > 0 {
sort.Strings(violations)
t.Errorf("%d malformed classic-manifest path(s) in specs/classic/resources.yaml:\n %s\n\nFix the offending entry's path/id_path/groups_path token in specs/classic/resources.yaml.",
len(violations), strings.Join(violations, "\n "))
}
}

// TestIsCleanPathSegment is a permanent guard on the validator itself: the floor
// check above protects against an empty parse, but only this test catches a
// future edit that weakens isCleanPathSegment (e.g. dropping the slash check),
// which would otherwise let the manifest proof silently pass.
func TestIsCleanPathSegment(t *testing.T) {
for _, s := range []string{"", "policies/extra", "policies ", "/policies", "back\\slash"} {
if isCleanPathSegment(s) {
t.Errorf("%q should be rejected", s)
}
}
for _, s := range []string{"policies", "osxconfigurationprofiles", "groupid"} {
if !isCleanPathSegment(s) {
t.Errorf("%q should be accepted", s)
}
}
}
52 changes: 52 additions & 0 deletions internal/commands/proof_short_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2026, Jamf Software LLC

package commands

import (
"sort"
"strings"
"testing"

"github.com/spf13/cobra"
)

// TestProofAllCommandsHaveShort asserts every command in the tree carries a
// non-empty Short description. Short is what `--help`, the generated site
// (`commands -o json`), and agent tooling surface as the one-line summary; an
// empty Short ships a blank, unusable command. Empirically 0 violations today,
// so this is a regression guard: it fails the moment a new hand-written or
// generated command lands without a Short.
func TestProofAllCommandsHaveShort(t *testing.T) {
root := NewRootCmd("test", "abc123", "2024-01-01", "unknown")

var (
violations []string
inspected int
)
// walkCommands does not invoke fn on hidden commands (though it still
// descends into their children) per its contract in
// destructive_annotations_test.go, so a hidden command with an empty Short
// is intentionally not flagged.
walkCommands(root, func(cmd *cobra.Command) {
inspected++
if strings.TrimSpace(cmd.Short) == "" {
violations = append(violations, commandPath(cmd))
}
})

// Floor sanity check: if the walk inspects almost nothing, the tree wiring
// regressed and a "no violations" pass would be silent. Current tree is
// ~1299 commands; 100 leaves room for churn without masking a major break.
// Fatalf (not Errorf): a broken walk yields an empty violations list too, so
// there is nothing useful to report alongside the floor failure.
const minInspected = 100
if inspected < minInspected {
t.Fatalf("walked only %d commands (expected >= %d) — command tree wiring likely regressed", inspected, minInspected)
}

if len(violations) > 0 {
sort.Strings(violations)
t.Errorf("%d command(s) missing a non-empty Short:\n %s\n\nFix by setting Short on each cobra.Command. For generated commands, edit the generator template (generator/parser/generator.go resourceTemplate, generator/classic/generator.go classicResourceTemplate, or generator/platform/template.go resourceTemplate) and run `make generate` — do not edit files under internal/commands/pro/generated/ or internal/commands/platform/generated/.",
len(violations), strings.Join(violations, "\n "))
}
}