polish(v0.5.4): invocation-name audit + targeted regression tests
Audit walked every cmd/ and internal/ file that produces user-facing
output. All command-form hints (text the user is meant to type back)
were already routed through invocationName() in v0.5.3 — the audit is
a verification pass, not a code rewrite.
Additions:
- Extract formatResumeRestoreHint and formatDirectModeTmuxHint as
testable string-only helpers. Production paths are unchanged
behaviorally; the helpers exist purely so the audit can pin down
the format strings without simulating tmux or stderr capture.
- Two new tests pinning the invocation name to a non-canonical value
("my-bin"). The pre-existing tests already protect these surfaces
but pin the name to "ctask", so they cannot detect a regression
that hard-codes "ctask" inside the format string. The new tests
flush that out for the resume restore hint and the Layer-1
active-session attach hint.
- Drop the duplicated withInvocationName helper accidentally added in
the info-session tests; reuse the canonical helper from
persistent_test.go.
Product-identity references ("ctask persistent mode requires tmux",
the SSH-remote `ssh -t <host> ctask <subcmd>` hint, doctor's "[ctask]"
diagnostic prefix, root-command Use:/Long:) deliberately remain
literal per spec §2.
This commit is contained in:
+9
-1
@@ -153,9 +153,17 @@ func directModeTmuxHint(opts WorkspaceEntryOptions) string {
|
||||
if !shell.HasSession(tmuxPath, sessionName) {
|
||||
return ""
|
||||
}
|
||||
return formatDirectModeTmuxHint(opts.WsMeta.Slug)
|
||||
}
|
||||
|
||||
// formatDirectModeTmuxHint builds the hint string itself, with no tmux
|
||||
// or filesystem checks. Split out so unit tests can verify that the
|
||||
// command-form line uses invocationName() without needing a live tmux
|
||||
// session set up against a real workspace.
|
||||
func formatDirectModeTmuxHint(slug string) string {
|
||||
return fmt.Sprintf(
|
||||
"Tip: a tmux session exists for this workspace.\nTo reattach instead of starting a second direct-mode session, run:\n %s attach %s",
|
||||
invocationName(), opts.WsMeta.Slug)
|
||||
invocationName(), slug)
|
||||
}
|
||||
|
||||
func invokePersistentRun(opts WorkspaceEntryOptions, tmuxPath, sessionName string) error {
|
||||
|
||||
@@ -12,16 +12,7 @@ import (
|
||||
"github.com/warrenronsiek/ctask/internal/workspace"
|
||||
)
|
||||
|
||||
// withInvocationNameInfo pins invocationName() to "ctask" for the duration
|
||||
// of the test so attach-hint substring assertions are stable across hosts.
|
||||
// Mirrors the helper in persistent_test.go but kept local to avoid
|
||||
// cross-test coupling.
|
||||
func withInvocationNameInfo(t *testing.T, name string) {
|
||||
t.Helper()
|
||||
prev := invocationNameOverride
|
||||
invocationNameOverride = name
|
||||
t.Cleanup(func() { invocationNameOverride = prev })
|
||||
}
|
||||
// withInvocationName is provided by persistent_test.go in this package.
|
||||
|
||||
// makeInfoSessionWorkspace writes a workspace under root with the given
|
||||
// slug and returns the workspace directory path. The workspace metadata
|
||||
@@ -65,7 +56,7 @@ func TestInfoNoSession(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInfoShowsActivePersistentSessionWithAttachHint(t *testing.T) {
|
||||
withInvocationNameInfo(t, "ctask")
|
||||
withInvocationName(t, "ctask")
|
||||
root := t.TempDir()
|
||||
wsDir := makeInfoSessionWorkspace(t, root, "active-persist")
|
||||
|
||||
@@ -95,7 +86,7 @@ func TestInfoShowsActivePersistentSessionWithAttachHint(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInfoShowsActiveDirectSessionWithoutAttachHint(t *testing.T) {
|
||||
withInvocationNameInfo(t, "ctask")
|
||||
withInvocationName(t, "ctask")
|
||||
root := t.TempDir()
|
||||
wsDir := makeInfoSessionWorkspace(t, root, "active-direct")
|
||||
|
||||
@@ -124,7 +115,7 @@ func TestInfoShowsActiveDirectSessionWithoutAttachHint(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInfoShowsRemoteHostnameInOwnerLine(t *testing.T) {
|
||||
withInvocationNameInfo(t, "ctask")
|
||||
withInvocationName(t, "ctask")
|
||||
root := t.TempDir()
|
||||
wsDir := makeInfoSessionWorkspace(t, root, "remote-active")
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// v0.5.4 invocation-name audit: spec §2 codifies that command-form
|
||||
// hints use invocationName() while product-identity references stay
|
||||
// literal. The pre-existing tests already cover most paths individually
|
||||
// (resume restore hint in resume_test.go, persistent bypass hints in
|
||||
// persistent_test.go). These tests pin down the remaining piece — the
|
||||
// Layer-1 active-session prompt's attach hint — and re-assert the
|
||||
// resume restore-hint contract against an explicitly non-canonical
|
||||
// invocation name so a regression that hard-codes "ctask" anywhere
|
||||
// upstream of the hint format will fail loudly.
|
||||
|
||||
func TestInvocationNameInActiveSessionPrompt(t *testing.T) {
|
||||
// directModeTmuxHint composes the Layer-1 prompt's attach suggestion
|
||||
// from formatDirectModeTmuxHint. The format-only helper is the right
|
||||
// surface to test: it isolates the rendering decision from the tmux
|
||||
// presence checks (which are environment-dependent) but exercises the
|
||||
// exact string the user will see.
|
||||
withInvocationName(t, "my-bin")
|
||||
|
||||
got := formatDirectModeTmuxHint("demo-ws")
|
||||
|
||||
if !strings.Contains(got, "my-bin attach demo-ws") {
|
||||
t.Errorf("Layer-1 attach hint should use invocation name; got:\n%s", got)
|
||||
}
|
||||
if strings.Contains(got, "ctask attach demo-ws") {
|
||||
t.Errorf("Layer-1 attach hint must NOT hard-code 'ctask attach' when invocation name differs; got:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvocationNameInRestoreHintNonCanonical(t *testing.T) {
|
||||
// Complement to TestResumeArchivedShowsRestoreHint in resume_test.go,
|
||||
// which pins invocationName to "ctask" — that protects against test
|
||||
// binary noise but cannot detect a regression that hard-codes "ctask"
|
||||
// in the format string. Pinning a non-default name flushes that out.
|
||||
withInvocationName(t, "my-bin")
|
||||
|
||||
got := formatResumeRestoreHint("my-archived-ws")
|
||||
|
||||
if !strings.Contains(got, "my-bin restore my-archived-ws") {
|
||||
t.Errorf("resume restore hint should use invocation name; got:\n%s", got)
|
||||
}
|
||||
if strings.Contains(got, "ctask restore") {
|
||||
t.Errorf("resume restore hint must NOT hard-code 'ctask restore' when invocation name differs; got:\n%s", got)
|
||||
}
|
||||
}
|
||||
+17
-3
@@ -58,9 +58,7 @@ func doResume(query string, container, useShell, force bool, agentOverride strin
|
||||
ws := resolveOne(roots, query, true)
|
||||
|
||||
if ws.Meta.Status == "archived" {
|
||||
fmt.Fprintf(os.Stderr,
|
||||
"[ctask] error: workspace %q is archived\n\nTo restore it:\n %s restore %s\n",
|
||||
query, invocationName(), query)
|
||||
fmt.Fprint(os.Stderr, formatResumeRestoreHint(query))
|
||||
return fmt.Errorf("workspace archived")
|
||||
}
|
||||
|
||||
@@ -88,3 +86,19 @@ func doResume(query string, container, useShell, force bool, agentOverride strin
|
||||
CommandName: "resume",
|
||||
})
|
||||
}
|
||||
|
||||
// formatResumeRestoreHint builds the multi-line stderr block printed
|
||||
// when `ctask resume <query>` resolves to an archived workspace.
|
||||
// Extracted so the v0.5.4 invocation-name audit can verify the
|
||||
// command-form line uses invocationName() without depending on the
|
||||
// surrounding fmt.Fprintf machinery.
|
||||
//
|
||||
// The "[ctask]" diagnostic prefix is intentionally a literal product
|
||||
// reference (spec §2: product-identity references stay literal). The
|
||||
// `restore <query>` line is the command-form portion and uses
|
||||
// invocationName().
|
||||
func formatResumeRestoreHint(query string) string {
|
||||
return fmt.Sprintf(
|
||||
"[ctask] error: workspace %q is archived\n\nTo restore it:\n %s restore %s\n",
|
||||
query, invocationName(), query)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user