diff --git a/.ai/checkpoints/topic-10-shell-completion.md b/.ai/checkpoints/topic-10-shell-completion.md new file mode 100644 index 0000000..f60e2c8 --- /dev/null +++ b/.ai/checkpoints/topic-10-shell-completion.md @@ -0,0 +1,74 @@ +# Checkpoint: Topic 10 — Shell Completion Command + +- **Branch:** `support-autocomplete` +- **Date:** 2026-03-26 +- **Status:** Complete + +--- + +## Scope + +Topic 10 implements the `dcm completion` command per spec section 4.10. The command generates shell autocompletion scripts for bash, zsh, fish, and powershell using Cobra's built-in completion generation. + +### Requirements Addressed + +| ID | Description | Status | +|----|-------------|--------| +| REQ-CMP-010 | Support generating completion scripts for bash, zsh, fish, powershell | Done | +| REQ-CMP-020 | Shell name provided as positional argument | Done | +| REQ-CMP-030 | Generated script written to stdout | Done | +| REQ-CMP-040 | Missing or invalid shell argument → usage error (exit code 2) | Done | +| REQ-CMP-050 | Use Cobra's built-in completion generation | Done | +| REQ-CMP-060 | Help includes usage examples for each shell | Done | + +### Tests Implemented (8 specs) + +| TC ID | Description | Status | +|-------|-------------|--------| +| TC-U132 | Generate bash completion — output contains bash-specific syntax | Pass | +| TC-U133 | Generate zsh completion — output contains `compdef` or `#compdef` | Pass | +| TC-U134 | Generate fish completion — output contains `complete -c dcm` | Pass | +| TC-U135 | Generate powershell completion — output contains `Register-ArgumentCompleter` | Pass | +| TC-U136 | Missing shell argument → UsageError (exit code 2) | Pass | +| TC-U137 | Invalid shell argument → UsageError with "unsupported shell" message | Pass | +| TC-U138 | Help includes usage examples for bash, zsh, fish, powershell | Pass | +| TC-U019 | Root command help lists `completion` subcommand (updated) | Pass | + +--- + +## Files Created / Modified + +| File | Change | Purpose | +|------|--------|---------| +| `internal/commands/completion.go` | Created | `dcm completion` command with bash/zsh/fish/powershell support | +| `internal/commands/completion_test.go` | Created | 7 Ginkgo test specs (TC-U132–TC-U138) | +| `internal/commands/root.go` | Modified | Registered `newCompletionCommand()` | +| `internal/commands/root_test.go` | Modified | Updated TC-U019 to verify `completion` subcommand is listed | +| `.ai/checkpoints/topic-10-shell-completion.md` | Created | This checkpoint | + +--- + +## Key Design Decisions + +1. **Custom Args validator** — Instead of using `cobra.ExactArgs(1)`, a custom `Args` function validates both argument count and shell name, wrapping errors as `UsageError` for exit code 2 compliance. + +2. **ValidArgs for built-in completion** — `ValidArgs` is set to `["bash", "zsh", "fish", "powershell"]` so that Cobra's own completion can suggest valid shell names. + +3. **Cobra built-in generation** — Uses `GenBashCompletionV2`, `GenZshCompletion`, `GenFishCompletion`, and `GenPowerShellCompletionWithDesc` directly from `cmd.Root()`, per REQ-CMP-050. + +4. **Long help with examples** — The `Long` field includes installation instructions for all four shells, satisfying REQ-CMP-060. + +5. **DisableDefaultCmd** — The root command already has `CompletionOptions.DisableDefaultCmd: true`, so Cobra's default `completion` command is disabled. Topic 10's explicit `completion` command replaces it with custom argument validation and help text. + +--- + +## What's Next + +All topics (1–10) are now complete. The CLI supports the full v1alpha1 feature set: +- Policy CRUD operations (Topic 4) +- Catalog service-type read operations (Topic 5) +- Catalog item operations (Topic 6) +- Catalog instance operations (Topic 7) +- Version display (Topic 8) +- SP resource read operations (Topic 9) +- Shell completion generation (Topic 10) diff --git a/.ai/specs/dcm-cli.spec.md b/.ai/specs/dcm-cli.spec.md index 01ba2ca..3408982 100644 --- a/.ai/specs/dcm-cli.spec.md +++ b/.ai/specs/dcm-cli.spec.md @@ -22,6 +22,7 @@ dependencies. - Configuration via file, environment variables, and flags - Pagination support for list operations - TLS support with custom CA certificates, client certificates (mTLS), and skip-verify +- Shell autocompletion generation (bash, zsh, fish, powershell) - Container image for distribution **Out of scope (v1alpha1):** @@ -29,7 +30,6 @@ dependencies. - Authentication and authorization (no auth in v1alpha1 API Gateway) - Interactive/wizard-style resource creation - Watch/streaming operations -- Shell autocompletion generation - Plugin/extension system - Offline mode or local caching - Bulk operations @@ -77,7 +77,8 @@ dcm-cli/ │ │ ├── catalog_service_type.go ← Service-type command group │ │ ├── catalog_item.go ← Catalog item command group │ │ ├── catalog_instance.go ← Catalog instance command group -│ │ └── sp_resource.go ← SP resource command group +│ │ ├── sp_resource.go ← SP resource command group +│ │ └── completion.go ← Shell completion command │ └── version/ ← Build-time version info ├── test/e2e/ ← E2E tests (build tag: e2e) ├── Makefile @@ -101,6 +102,7 @@ dcm-cli/ | 7 | Catalog Instance Commands | CIN | 1, 2, 3 | | 8 | Version Command | VER | 1 | | 9 | SP Resource Commands | SPR | 1, 2, 3 | +| 10 | Shell Completion Command | CMP | 1 | ``` Topic 1: CLI Framework (independent) @@ -114,6 +116,7 @@ Topic 3: Output Formatting (independent) +---------+---------+---> Topic 9: SP Resource Commands (depends on 1, 2, 3) | +-----------------------> Topic 8: Version Command (depends on 1) + +-----------------------> Topic 10: Shell Completion Cmd (depends on 1) ``` Topics 1, 2, and 3 can be delivered in parallel. Topics 4-7 depend on all @@ -141,7 +144,7 @@ Out of scope: shell autocompletion, plugin system, interactive prompts. | ID | Requirement | Priority | Notes | |----|-------------|----------|-------| | REQ-CLI-020 | The CLI MUST define a root command `dcm` with global flags | MUST | | -| REQ-CLI-030 | The root command MUST register all subcommand groups: `policy`, `catalog`, `sp`, `version` | MUST | | +| REQ-CLI-030 | The root command MUST register all subcommand groups: `policy`, `catalog`, `sp`, `version`, `completion` | MUST | | | REQ-CLI-040 | The `catalog` command MUST register subcommand groups: `service-type`, `item`, `instance` | MUST | | | REQ-CLI-050 | Global flags MUST include `--api-gateway-url`, `--output`/`-o`, `--timeout`, `--config`, `--tls-ca-cert`, `--tls-client-cert`, `--tls-client-key`, `--tls-skip-verify` | MUST | | | REQ-CLI-060 | The CLI MUST exit with code 0 on success, 1 on runtime errors, 2 on usage errors | MUST | | @@ -161,7 +164,7 @@ Out of scope: shell autocompletion, plugin system, interactive prompts. - **Validates:** REQ-CLI-030, REQ-CLI-040 - **Given** the CLI is invoked - **When** `dcm --help` is run -- **Then** subcommands `policy`, `catalog`, `sp`, and `version` MUST be listed +- **Then** subcommands `policy`, `catalog`, `sp`, `version`, and `completion` MUST be listed - **And** `dcm catalog --help` MUST list `service-type`, `item`, and `instance` - **And** `dcm sp --help` MUST list `resource` @@ -1066,6 +1069,84 @@ Formatting). --- +### 4.10 Shell Completion Command + +#### Overview + +Implement the `dcm completion` command to generate shell autocompletion scripts +for bash, zsh, fish, and powershell. Uses Cobra's built-in completion generation. + +Out of scope: automatic installation of completion scripts, custom completions +for resource IDs or flag values. + +#### Requirements + +| ID | Requirement | Priority | Notes | +|----|-------------|----------|-------| +| REQ-CMP-010 | `dcm completion` MUST support generating completion scripts for `bash`, `zsh`, `fish`, and `powershell` shells | MUST | | +| REQ-CMP-020 | The shell name MUST be provided as a positional argument | MUST | | +| REQ-CMP-030 | The generated script MUST be written to stdout so it can be piped or redirected | MUST | | +| REQ-CMP-040 | Missing or invalid shell argument MUST result in a usage error (exit code 2) | MUST | | +| REQ-CMP-050 | `dcm completion` MUST use Cobra's built-in completion generation | MUST | | +| REQ-CMP-060 | The command help MUST include usage examples showing how to install completions for each supported shell | MUST | | + +#### Acceptance Criteria + +##### AC-CMP-010: Generate bash completion + +- **Validates:** REQ-CMP-010, REQ-CMP-030 +- **Given** the CLI is invoked +- **When** `dcm completion bash` is run +- **Then** a valid bash completion script MUST be written to stdout + +##### AC-CMP-020: Generate zsh completion + +- **Validates:** REQ-CMP-010, REQ-CMP-030 +- **Given** the CLI is invoked +- **When** `dcm completion zsh` is run +- **Then** a valid zsh completion script MUST be written to stdout + +##### AC-CMP-030: Generate fish completion + +- **Validates:** REQ-CMP-010, REQ-CMP-030 +- **Given** the CLI is invoked +- **When** `dcm completion fish` is run +- **Then** a valid fish completion script MUST be written to stdout + +##### AC-CMP-040: Generate powershell completion + +- **Validates:** REQ-CMP-010, REQ-CMP-030 +- **Given** the CLI is invoked +- **When** `dcm completion powershell` is run +- **Then** a valid powershell completion script MUST be written to stdout + +##### AC-CMP-050: Missing shell argument + +- **Validates:** REQ-CMP-040 +- **Given** no positional argument is provided +- **When** `dcm completion` is invoked +- **Then** the CLI MUST exit with code 2 and display a usage error + +##### AC-CMP-060: Invalid shell argument + +- **Validates:** REQ-CMP-040 +- **Given** an unsupported shell name is provided +- **When** `dcm completion invalid-shell` is invoked +- **Then** the CLI MUST exit with code 2 and display a usage error + +##### AC-CMP-070: Help includes usage examples + +- **Validates:** REQ-CMP-060 +- **Given** the CLI is invoked +- **When** `dcm completion --help` is run +- **Then** the help output MUST include usage examples for bash, zsh, fish, and powershell + +#### Dependencies + +Depends on Topic 1 (CLI Framework). + +--- + ## 5. Cross-Cutting Concerns ### 5.1 Error Handling @@ -1440,8 +1521,9 @@ REQ-OUT-090, REQ-OUT-110, REQ-OUT-120 | REQ-CIN-NNN | 4.7: Catalog Instance Commands | 11 | | REQ-VER-NNN | 4.8: Version Command | 3 | | REQ-SPR-NNN | 4.9: SP Resource Commands | 5 | +| REQ-CMP-NNN | 4.10: Shell Completion Command | 6 | | REQ-XC-ERR-NNN | 5.1: Error Handling | 7 | | REQ-XC-INP-NNN | 5.2: Input File Parsing | 3 | | REQ-XC-CLI-NNN | 5.3: Generated Client Usage | 5 | | REQ-XC-PAG-NNN | 5.4: Pagination | 3 | -| **Total** | | **94** | +| **Total** | | **100** | diff --git a/.ai/test-plans/dcm-cli-unit.test-plan.md b/.ai/test-plans/dcm-cli-unit.test-plan.md index 4593b62..2a628e5 100644 --- a/.ai/test-plans/dcm-cli-unit.test-plan.md +++ b/.ai/test-plans/dcm-cli-unit.test-plan.md @@ -4,7 +4,7 @@ - **Related Spec:** .ai/specs/dcm-cli.spec.md - **Related Plan:** .ai/plan/dcm-cli.plan.md -- **Related Requirements:** REQ-CLI-010–070, REQ-CFG-010–070, REQ-OUT-010–120, REQ-POL-010–130, REQ-CST-010–050, REQ-CIT-010–130, REQ-CIN-010–110, REQ-SPR-010–050, REQ-VER-010–030, REQ-XC-ERR-010–070, REQ-XC-INP-010–030, REQ-XC-CLI-010–050, REQ-XC-PAG-010–030, REQ-XC-TLS-010–080 +- **Related Requirements:** REQ-CLI-010–070, REQ-CFG-010–070, REQ-OUT-010–120, REQ-POL-010–130, REQ-CST-010–050, REQ-CIT-010–130, REQ-CIN-010–110, REQ-SPR-010–050, REQ-VER-010–030, REQ-CMP-010–060, REQ-XC-ERR-010–070, REQ-XC-INP-010–030, REQ-XC-CLI-010–050, REQ-XC-PAG-010–030, REQ-XC-TLS-010–080 - **Framework:** Ginkgo v2 + Gomega - **Created:** 2026-03-09 @@ -262,7 +262,7 @@ test classes. Instead: - **Type:** Unit - **Given:** The root command is created via `NewRootCommand()` - **When:** `dcm --help` is executed -- **Then:** Subcommands `policy`, `catalog`, `sp`, and `version` are listed in the help output +- **Then:** Subcommands `policy`, `catalog`, `sp`, `version`, and `completion` are listed in the help output ### TC-U020: Catalog command registers subcommand groups @@ -954,7 +954,77 @@ test classes. Instead: --- -## 10 · TLS Configuration +## 10 · Shell Completion Command + +> **Suggested Ginkgo structure:** `Describe("Completion Command")` with nested +> `Context` per shell type. + +### TC-U132: Generate bash completion script + +- **Requirement:** REQ-CMP-010, REQ-CMP-030, REQ-CMP-050 +- **Acceptance Criteria:** AC-CMP-010 +- **Type:** Unit +- **Given:** The root command is created +- **When:** `dcm completion bash` is executed +- **Then:** A bash completion script is written to stdout AND the output contains bash-specific syntax (e.g., `_dcm` or `complete`) + +### TC-U133: Generate zsh completion script + +- **Requirement:** REQ-CMP-010, REQ-CMP-030, REQ-CMP-050 +- **Acceptance Criteria:** AC-CMP-020 +- **Type:** Unit +- **Given:** The root command is created +- **When:** `dcm completion zsh` is executed +- **Then:** A zsh completion script is written to stdout AND the output contains zsh-specific syntax (e.g., `compdef` or `#compdef`) + +### TC-U134: Generate fish completion script + +- **Requirement:** REQ-CMP-010, REQ-CMP-030, REQ-CMP-050 +- **Acceptance Criteria:** AC-CMP-030 +- **Type:** Unit +- **Given:** The root command is created +- **When:** `dcm completion fish` is executed +- **Then:** A fish completion script is written to stdout AND the output contains fish-specific syntax (e.g., `complete -c dcm`) + +### TC-U135: Generate powershell completion script + +- **Requirement:** REQ-CMP-010, REQ-CMP-030, REQ-CMP-050 +- **Acceptance Criteria:** AC-CMP-040 +- **Type:** Unit +- **Given:** The root command is created +- **When:** `dcm completion powershell` is executed +- **Then:** A powershell completion script is written to stdout AND the output contains powershell-specific syntax (e.g., `Register-ArgumentCompleter`) + +### TC-U136: Completion without shell argument fails + +- **Requirement:** REQ-CMP-040 +- **Acceptance Criteria:** AC-CMP-050 +- **Type:** Unit +- **Given:** No positional argument is provided +- **When:** `dcm completion` is executed +- **Then:** The CLI exits with code 2 and displays a usage error + +### TC-U137: Completion with invalid shell argument fails + +- **Requirement:** REQ-CMP-040 +- **Acceptance Criteria:** AC-CMP-060 +- **Type:** Unit +- **Given:** An unsupported shell name `invalid-shell` is provided +- **When:** `dcm completion invalid-shell` is executed +- **Then:** The CLI exits with code 2 and displays a usage error + +### TC-U138: Completion help includes usage examples + +- **Requirement:** REQ-CMP-060 +- **Acceptance Criteria:** AC-CMP-070 +- **Type:** Unit +- **Given:** The root command is created +- **When:** `dcm completion --help` is executed +- **Then:** The help output includes usage examples for bash, zsh, fish, and powershell + +--- + +## 11 · TLS Configuration > **Suggested Ginkgo structure:** `Describe("TLS Configuration")` with `Context` > per scenario. Tests use `net/http/httptest` with TLS-enabled servers where @@ -1070,7 +1140,7 @@ test classes. Instead: --- -## 11 · Error Handling +## 12 · Error Handling > **Suggested Ginkgo structure:** `Describe("Error Handling")` with `Context` > per error type. Tests exercise error paths through command execution with @@ -1403,6 +1473,12 @@ dedicated test class or `Describe` block. | REQ-VER-010 | TC-U024 | Covered | | REQ-VER-020 | TC-U024 | Covered | | REQ-VER-030 | TC-U025 | Covered | +| REQ-CMP-010 | TC-U132, TC-U133, TC-U134, TC-U135 | Covered | +| REQ-CMP-020 | TC-U132, TC-U133, TC-U134, TC-U135, TC-U136 | Covered | +| REQ-CMP-030 | TC-U132, TC-U133, TC-U134, TC-U135 | Covered | +| REQ-CMP-040 | TC-U136, TC-U137 | Covered | +| REQ-CMP-050 | TC-U132, TC-U133, TC-U134, TC-U135 | Covered | +| REQ-CMP-060 | TC-U138 | Covered | | REQ-XC-ERR-010 | TC-U080 | Covered | | REQ-XC-ERR-020 | TC-U080 | Covered | | REQ-XC-ERR-030 | TC-U081, TC-U082 | Covered | @@ -1431,7 +1507,7 @@ dedicated test class or `Describe` block. | REQ-XC-TLS-070 | TC-U095, TC-U096 | Covered | | REQ-XC-TLS-080 | TC-U090, TC-U097 | Covered | -**Total:** 95 test case IDs — 71 in behavioural test classes, 24 in the utility +**Total:** 102 test case IDs — 78 in behavioural test classes, 24 in the utility index (tested transitively through higher-level behavioural tests). --- diff --git a/README.md b/README.md index a3d048e..6e18487 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ internal/ catalog_instance.go ← Catalog instance command group sp.go ← SP parent command group sp_resource.go ← SP resource command group + completion.go ← Shell completion command ``` ### 2.3 Component Descriptions @@ -169,6 +170,7 @@ dcm │ └── resource # SP resource management (read-only) │ ├── list │ └── get +├── completion # Shell autocompletion └── version # Print version info ``` @@ -531,7 +533,31 @@ dcm sp resource get INSTANCE_ID dcm sp resource get INSTANCE_ID -o yaml ``` -### 4.7 Version Command +### 4.7 Completion Command + +#### `dcm completion` + +Generate shell autocompletion scripts. + +| Argument | Required | Description | +|----------|----------|-------------| +| `SHELL` | Yes | Shell type: `bash`, `zsh`, `fish`, or `powershell` | + +```bash +# Bash +source <(dcm completion bash) + +# Zsh +dcm completion zsh > "${fpath[1]}/_dcm" + +# Fish +dcm completion fish | source + +# PowerShell +dcm completion powershell | Out-String | Invoke-Expression +``` + +### 4.8 Version Command #### `dcm version` @@ -646,6 +672,9 @@ func newSPCommand() *cobra.Command // parent: dcm sp func newSPResourceCommand() *cobra.Command // parent: dcm sp resource func newSPResourceListCommand() *cobra.Command func newSPResourceGetCommand() *cobra.Command + +// completion.go +func newCompletionCommand() *cobra.Command // dcm completion [bash|zsh|fish|powershell] ``` ### 5.4 `internal/version` @@ -748,7 +777,9 @@ dcm-cli/ │ │ ├── catalog_instance_test.go │ │ ├── sp.go │ │ ├── sp_resource.go -│ │ └── sp_resource_test.go +│ │ ├── sp_resource_test.go +│ │ ├── completion.go +│ │ └── completion_test.go │ └── version/ │ └── version.go ├── test/ @@ -1137,6 +1168,7 @@ make test-e2e # Requires DCM_API_GATEWAY_URL pointing to live stack - Configuration via file, environment variables, and flags - Pagination support for list operations - TLS support with custom CA certificates, client certificates (mTLS), and skip-verify +- Shell autocompletion generation (bash, zsh, fish, powershell) - Container image for distribution ### 12.2 Out of Scope (v1alpha1) @@ -1144,7 +1176,6 @@ make test-e2e # Requires DCM_API_GATEWAY_URL pointing to live stack - Authentication and authorization (no auth in v1alpha1 API Gateway) - Interactive/wizard-style resource creation - Watch/streaming operations -- Shell autocompletion generation - Plugin/extension system - Offline mode or local caching - Bulk operations diff --git a/internal/commands/completion.go b/internal/commands/completion.go new file mode 100644 index 0000000..87fa6af --- /dev/null +++ b/internal/commands/completion.go @@ -0,0 +1,70 @@ +package commands + +import ( + "github.com/spf13/cobra" +) + +func newCompletionCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "completion [bash|zsh|fish|powershell]", + Short: "Generate shell autocompletion script", + Long: `Generate shell autocompletion script for dcm. + +To load completions: + +Bash: + + $ source <(dcm completion bash) + + # To load completions for each session, execute once: + # Linux: + $ dcm completion bash > /etc/bash_completion.d/dcm + # macOS: + $ dcm completion bash > $(brew --prefix)/etc/bash_completion.d/dcm + +Zsh: + + # If shell completion is not already enabled in your environment, + # you will need to enable it. You can execute the following once: + + $ echo "autoload -U compinit; compinit" >> ~/.zshrc + + # To load completions for each session, execute once: + $ dcm completion zsh > "${fpath[1]}/_dcm" + + # You will need to start a new shell for this setup to take effect. + +Fish: + + $ dcm completion fish | source + + # To load completions for each session, execute once: + $ dcm completion fish > ~/.config/fish/completions/dcm.fish + +PowerShell: + + PS> dcm completion powershell | Out-String | Invoke-Expression + + # To load completions for every new session, run: + PS> dcm completion powershell > dcm.ps1 + # and source this file from your PowerShell profile. +`, + Args: ExactValidArgs(1), + ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, + RunE: func(cmd *cobra.Command, args []string) error { + out := cmd.OutOrStdout() + switch args[0] { + case "bash": + return cmd.Root().GenBashCompletionV2(out, true) + case "zsh": + return cmd.Root().GenZshCompletion(out) + case "fish": + return cmd.Root().GenFishCompletion(out, true) + case "powershell": + return cmd.Root().GenPowerShellCompletionWithDesc(out) + } + return nil + }, + } + return cmd +} diff --git a/internal/commands/completion_test.go b/internal/commands/completion_test.go new file mode 100644 index 0000000..4da4f25 --- /dev/null +++ b/internal/commands/completion_test.go @@ -0,0 +1,122 @@ +package commands_test + +import ( + "bytes" + "errors" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/dcm-project/cli/internal/commands" +) + +var _ = Describe("Completion Command", func() { + var ( + outBuf *bytes.Buffer + errBuf *bytes.Buffer + ) + + executeCompletion := func(args ...string) error { + cmd := commands.NewRootCommand() + outBuf = new(bytes.Buffer) + errBuf = new(bytes.Buffer) + cmd.SetOut(outBuf) + cmd.SetErr(errBuf) + cmd.SetArgs(args) + return cmd.Execute() + } + + // TC-U132: Generate bash completion script + Describe("TC-U132: Generate bash completion", func() { + It("should generate a valid bash completion script to stdout", func() { + err := executeCompletion("completion", "bash") + Expect(err).NotTo(HaveOccurred()) + + output := outBuf.String() + Expect(output).NotTo(BeEmpty()) + Expect(output).To(ContainSubstring("bash")) + }) + }) + + // TC-U133: Generate zsh completion script + Describe("TC-U133: Generate zsh completion", func() { + It("should generate a valid zsh completion script to stdout", func() { + err := executeCompletion("completion", "zsh") + Expect(err).NotTo(HaveOccurred()) + + output := outBuf.String() + Expect(output).NotTo(BeEmpty()) + Expect(output).To(SatisfyAny( + ContainSubstring("compdef"), + ContainSubstring("#compdef"), + )) + }) + }) + + // TC-U134: Generate fish completion script + Describe("TC-U134: Generate fish completion", func() { + It("should generate a valid fish completion script to stdout", func() { + err := executeCompletion("completion", "fish") + Expect(err).NotTo(HaveOccurred()) + + output := outBuf.String() + Expect(output).NotTo(BeEmpty()) + Expect(output).To(ContainSubstring("complete -c dcm")) + }) + }) + + // TC-U135: Generate powershell completion script + Describe("TC-U135: Generate powershell completion", func() { + It("should generate a valid powershell completion script to stdout", func() { + err := executeCompletion("completion", "powershell") + Expect(err).NotTo(HaveOccurred()) + + output := outBuf.String() + Expect(output).NotTo(BeEmpty()) + Expect(output).To(ContainSubstring("Register-ArgumentCompleter")) + }) + }) + + // TC-U136: Completion without shell argument fails + Describe("TC-U136: Missing shell argument", func() { + It("should return a UsageError when no shell argument is provided", func() { + err := executeCompletion("completion") + Expect(err).To(HaveOccurred()) + + var usageErr *commands.UsageError + Expect(errors.As(err, &usageErr)).To(BeTrue(), + "expected error to be a UsageError, got: %v", err) + }) + }) + + // TC-U137: Completion with invalid shell argument fails + Describe("TC-U137: Invalid shell argument", func() { + It("should return a UsageError when an unsupported shell is provided", func() { + err := executeCompletion("completion", "invalid-shell") + Expect(err).To(HaveOccurred()) + + var usageErr *commands.UsageError + Expect(errors.As(err, &usageErr)).To(BeTrue(), + "expected error to be a UsageError, got: %v", err) + Expect(err.Error()).To(ContainSubstring("invalid-shell")) + }) + }) + + // TC-U138: Completion help includes usage examples + Describe("TC-U138: Help includes usage examples", func() { + It("should include usage examples for all supported shells", func() { + err := executeCompletion("completion", "--help") + Expect(err).NotTo(HaveOccurred()) + + helpOutput := outBuf.String() + Expect(helpOutput).To(ContainSubstring("bash")) + Expect(helpOutput).To(ContainSubstring("zsh")) + Expect(helpOutput).To(ContainSubstring("fish")) + Expect(helpOutput).To(ContainSubstring("powershell")) + Expect(helpOutput).To(SatisfyAny( + ContainSubstring("PowerShell"), + ContainSubstring("powershell"), + )) + }) + }) +}) diff --git a/internal/commands/root.go b/internal/commands/root.go index bfaf11d..e964c2d 100644 --- a/internal/commands/root.go +++ b/internal/commands/root.go @@ -57,6 +57,7 @@ func NewRootCommand() *cobra.Command { cmd.AddCommand(newCatalogCommand()) cmd.AddCommand(newSPCommand()) cmd.AddCommand(newVersionCommand()) + cmd.AddCommand(newCompletionCommand()) return cmd } @@ -106,6 +107,17 @@ func ExactArgs(n int) cobra.PositionalArgs { } } +// ExactValidArgs returns a cobra.PositionalArgs that checks both the argument +// count and that each argument is in ValidArgs, wrapping errors as UsageError. +func ExactValidArgs(n int) cobra.PositionalArgs { + return func(cmd *cobra.Command, args []string) error { + if err := cobra.MatchAll(cobra.ExactArgs(n), cobra.OnlyValidArgs)(cmd, args); err != nil { + return &UsageError{Err: err} + } + return nil + } +} + // requiredFlagsPreRun is a PreRunE hook that wraps Cobra's required-flag // validation errors as UsageError (exit code 2). Cobra's own // ValidateRequiredFlags call that follows PreRunE becomes a no-op. diff --git a/internal/commands/root_test.go b/internal/commands/root_test.go index fea6dd6..c123805 100644 --- a/internal/commands/root_test.go +++ b/internal/commands/root_test.go @@ -29,6 +29,7 @@ var _ = Describe("Root Command", func() { Expect(helpOutput).To(ContainSubstring("catalog")) Expect(helpOutput).To(ContainSubstring("sp")) Expect(helpOutput).To(ContainSubstring("version")) + Expect(helpOutput).To(ContainSubstring("completion")) }) })