Introduces a static-binary build and Debian package (amd64/arm64) with version/commit/date stamped via -ldflags. Ships section-1 manpages for ctool, ctfetch, and ctail. Adds a `version` subcommand reachable as `ctool version`, `ctool -version`, `ctool --version`, `ctool fetch version`, `ctool tail version`, and via the ctfetch/ctail symlinks. Adds tests covering the dispatcher, fetch/tail argument parsing, and the formatter/helper functions. Adds a retrofit design document modelled on the vpp-maglev one, with FRs and NFRs for each tool and the dispatcher. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
74 lines
2.4 KiB
Go
74 lines
2.4 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestFetchNoArgsShowsUsage(t *testing.T) {
|
|
_, stderr, exit := runCLI(t, "ctool", "fetch")
|
|
if exit == 0 {
|
|
t.Errorf("expected non-zero exit, got 0 (stderr=%q)", stderr)
|
|
}
|
|
if !strings.Contains(stderr, "Usage:") {
|
|
t.Errorf("stderr missing usage header: %q", stderr)
|
|
}
|
|
if !strings.Contains(stderr, "ctool fetch") {
|
|
t.Errorf("stderr missing qualified command name: %q", stderr)
|
|
}
|
|
}
|
|
|
|
// When invoked via the ctfetch symlink, the usage banner must use the
|
|
// symlink name ("ctfetch"), not "ctool fetch". This exercises cmdName's
|
|
// symlink branch through the real CLI.
|
|
func TestFetchUsageUsesSymlinkName(t *testing.T) {
|
|
_, stderr, exit := runCLI(t, "ctfetch")
|
|
if exit == 0 {
|
|
t.Errorf("expected non-zero exit, got 0")
|
|
}
|
|
if !strings.Contains(stderr, "ctfetch") {
|
|
t.Errorf("stderr should mention ctfetch: %q", stderr)
|
|
}
|
|
if strings.Contains(stderr, "ctool fetch") {
|
|
t.Errorf("stderr should NOT say 'ctool fetch' when invoked via symlink: %q", stderr)
|
|
}
|
|
}
|
|
|
|
func TestFetchHelpExitsZero(t *testing.T) {
|
|
_, _, exit := runCLI(t, "ctool", "fetch", "-h")
|
|
if exit != 0 {
|
|
t.Errorf("expected exit 0 for -h, got %d", exit)
|
|
}
|
|
}
|
|
|
|
// An unknown +modifier must be rejected before any network request is made.
|
|
// The fetch dispatcher validates the modifier enum (sct/issuer/ctlog/all)
|
|
// up-front; we pass a junk URL to prove the error happens during parse,
|
|
// not during HTTP.
|
|
func TestFetchRejectsUnknownModifier(t *testing.T) {
|
|
_, stderr, exit := runCLI(t, "ctool", "fetch", "http://invalid.invalid", "1", "+bogus")
|
|
if exit == 0 {
|
|
t.Errorf("expected non-zero exit")
|
|
}
|
|
if !strings.Contains(stderr, "unknown argument") {
|
|
t.Errorf("stderr should mention 'unknown argument', got %q", stderr)
|
|
}
|
|
if !strings.Contains(stderr, "+bogus") {
|
|
t.Errorf("stderr should echo the bad modifier: %q", stderr)
|
|
}
|
|
}
|
|
|
|
// Leaf-index mode is distinguished from tile-dump mode by whether the
|
|
// second positional parses as an integer. The +modifier-rejection path
|
|
// runs through different code for each mode; verify both reach the
|
|
// same validation.
|
|
func TestFetchRejectsUnknownModifierTileMode(t *testing.T) {
|
|
_, stderr, exit := runCLI(t, "ctool", "fetch", "http://invalid.invalid/tile/data/x000/000", "+bogus")
|
|
if exit == 0 {
|
|
t.Errorf("expected non-zero exit")
|
|
}
|
|
if !strings.Contains(stderr, "unknown argument") {
|
|
t.Errorf("stderr should mention 'unknown argument', got %q", stderr)
|
|
}
|
|
}
|