---
phase: 02-command-test-coverage
plan: 03
type: tdd
wave: 1
depends_on: []
files_modified:
  - cmd/reset.go
  - cmd/reset_test.go
  - cmd/clean_test.go
autonomous: true
requirements:
  - TST-03
must_haves:
  truths:
    - "Running `task test:unit` exercises reset.go RunE — slug validation fires before any API call"
    - "Running `task test:unit` exercises clean.go RunE — invalid slug path returns error"
    - "An invalid slug passed to reset RunE returns a non-nil error immediately (no API call needed)"
    - "An invalid slug passed to clean RunE returns a non-nil error immediately"
    - "A simulated API failure in reset's getChallenge returns a non-nil error from RunE — no panic"
  artifacts:
    - path: "cmd/reset.go"
      provides: "validateChallengeSlug called as first statement of RunE, before ui.Section and getChallenge"
      contains: "validateChallengeSlug(challengeSlug)"
    - path: "cmd/reset_test.go"
      provides: "Unit tests for reset.go RunE error paths"
      exports: ["TestResetRunE_InvalidSlug", "TestResetRunE_APIFailure"]
    - path: "cmd/clean_test.go"
      provides: "Unit test for clean.go invalid slug path"
      exports: ["TestCleanRunE_InvalidSlug"]
  key_links:
    - from: "cmd/reset_test.go"
      to: "cmd/reset.go"
      via: "package cmd — direct access to resetChallengeCmd"
      pattern: "resetChallengeCmd\\.RunE"
    - from: "cmd/clean_test.go"
      to: "cmd/clean.go"
      via: "package cmd — direct access to cleanChallengeCmd"
      pattern: "cleanChallengeCmd\\.RunE"
---

<objective>
Fix reset.go to validate slug upfront, then write unit tests for reset.go and clean.go error paths.

Purpose: TST-03 — reset.go and clean.go have zero test coverage. One production change is required: reset.go currently relies on getChallenge() calling validateChallengeSlug internally (through an API wrapper), whereas clean.go calls validateChallengeSlug directly at the top of RunE. Aligning reset.go enables slug-validation tests that need no API mocks.
Output: cmd/reset.go with validateChallengeSlug added as first statement; cmd/reset_test.go with 2 tests; cmd/clean_test.go with 1 test.
</objective>

<execution_context>
@/Users/paul/.claude/get-shit-done/workflows/execute-plan.md
@/Users/paul/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/02-command-test-coverage/02-CONTEXT.md
@.planning/phases/02-command-test-coverage/02-RESEARCH.md
</context>

<interfaces>
<!-- Key types and contracts the executor needs. Extracted from codebase. -->

From cmd/reset.go (current RunE structure — production change target):
```go
RunE: func(cmd *cobra.Command, args []string) error {
    challengeSlug := args[0]

    ui.Section(fmt.Sprintf("Resetting Challenge: %s", challengeSlug))

    // Verify challenge exists
    _, err := getChallenge(challengeSlug)   // ← validateChallengeSlug is buried inside here
    if err != nil { ... }
    ...
}
```

From cmd/clean.go (reference — already correct):
```go
RunE: func(cmd *cobra.Command, args []string) error {
    challengeSlug := args[0]

    // SAFE-02: validate slug before any cluster call
    if err := validateChallengeSlug(challengeSlug); err != nil {
        return err
    }
    ui.Section(...)
    ...
}
```

From cmd/common.go:
```go
func validateChallengeSlug(slug string) error { ... }
func getChallenge(slug string) (*api.ChallengeEntity, error) { ... }
```

From internal/api/client.go:
```go
func GetChallenge(slug string) (*ChallengeEntity, error)
func ResetChallengeProgress(slugOrID string) error
```

Note on clean.go scope: clean.go calls deleteChallengeResources after slug validation, which calls kube.GetKubernetesClient(). The only testable path without a cluster is the invalid slug path (returns before kube call). TestCleanRunE_InvalidSlug covers exactly this — no mocks needed.

Note on reset.go APIFailure test: After adding validateChallengeSlug at top, the invalid-slug test needs no mocks. For the APIFailure test, getChallenge() calls api.GetChallenge internally. To mock this without touching common.go, add a package-level var in reset.go:
```go
var getChallengeFn = getChallenge
```
Then replace `_, err := getChallenge(challengeSlug)` with `_, err := getChallengeFn(challengeSlug)` in reset.go's RunE.
</interfaces>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Fix reset.go + write reset_test.go and clean_test.go</name>
  <files>cmd/reset.go, cmd/reset_test.go, cmd/clean_test.go</files>
  <behavior>
    - TestResetRunE_InvalidSlug: args=["INVALID_SLUG"] → RunE returns non-nil error containing "invalid challenge slug" (no mocks needed — slug check fires before any function var)
    - TestResetRunE_APIFailure: getChallengeFn returns (nil, fmt.Errorf("challenge not found")) → RunE returns non-nil error; assert.NotPanics confirms no panic
    - TestCleanRunE_InvalidSlug: args=["INVALID_SLUG"] → RunE returns non-nil error containing "invalid challenge slug" (no mocks needed)
  </behavior>
  <action>
**RED phase — write tests first, then make them pass.**

**Step 1: Write failing tests**

Create cmd/reset_test.go in package cmd. Import "fmt", "testing", "github.com/stretchr/testify/assert", "github.com/stretchr/testify/require", "github.com/kubeasy-dev/kubeasy-cli/internal/api".

Write TestResetRunE_InvalidSlug (no fakes needed — just call `resetChallengeCmd.RunE(resetChallengeCmd, []string{"INVALID_SLUG"})` and require.Error).

Write TestResetRunE_APIFailure using `getChallengeFn` var fake that returns error.

Create cmd/clean_test.go in package cmd. Write TestCleanRunE_InvalidSlug (no fakes — call `cleanChallengeCmd.RunE(cleanChallengeCmd, []string{"INVALID_SLUG"})` and require.Error).

Run `go test ./cmd/... -run "TestResetRunE|TestCleanRunE" -v` — tests FAIL (getChallengeFn does not exist yet, reset.go lacks upfront slug check).

**Step 2: Fix cmd/reset.go (production change + function var)**

Add at package level before var resetChallengeCmd:
```go
var getChallengeFn = getChallenge
```

In RunE, add validateChallengeSlug as the FIRST statement (before ui.Section):
```go
RunE: func(cmd *cobra.Command, args []string) error {
    challengeSlug := args[0]

    // Align with clean.go: validate slug before any API or cluster call
    if err := validateChallengeSlug(challengeSlug); err != nil {
        return err
    }

    ui.Section(fmt.Sprintf("Resetting Challenge: %s", challengeSlug))

    // Verify challenge exists
    _, err := getChallengeFn(challengeSlug)  // was: getChallenge(challengeSlug)
    ...
```

**Step 3: Run tests GREEN**

Run `go test ./cmd/... -run "TestResetRunE|TestCleanRunE" -v -race`. All three tests must pass.

**Step 4: Full suite check**

Run `task test:unit` — all tests must remain green.

**Constraint for clean_test.go:** Only TestCleanRunE_InvalidSlug. Do not attempt to test the valid-slug path — it reaches kube.GetKubernetesClient() which requires a real cluster. One test is sufficient to verify error propagation per CONTEXT.md scope.

**Constraint for reset_test.go:** Only two tests: invalid slug (no mocks) and API failure (getChallengeFn mock). Do not test paths past getChallengeFn — deleteChallengeResources requires a real cluster.
  </action>
  <verify>
    <automated>cd /Users/paul/Workspace/kubeasy/kubeasy-cli && go test ./cmd/... -run "TestResetRunE|TestCleanRunE" -v -race</automated>
  </verify>
  <done>TestResetRunE_InvalidSlug, TestResetRunE_APIFailure, TestCleanRunE_InvalidSlug all pass with -race. cmd/reset.go has validateChallengeSlug as first RunE statement and getChallengeFn var. `task test:unit` remains green.</done>
</task>

</tasks>

<verification>
Run `task test:unit` — full unit test suite must pass green with no race conditions.
Run `go test ./cmd/... -v -race` — all cmd tests pass including the three new Reset/Clean tests.
Verify reset.go: grep for `validateChallengeSlug` appearing before `ui.Section` in RunE.
</verification>

<success_criteria>
- cmd/reset.go has `validateChallengeSlug(challengeSlug)` as first statement in RunE (before ui.Section)
- cmd/reset.go has `var getChallengeFn = getChallenge` at package level
- cmd/reset_test.go exists with TestResetRunE_InvalidSlug and TestResetRunE_APIFailure
- cmd/clean_test.go exists with TestCleanRunE_InvalidSlug
- All three tests pass with `go test ./cmd/... -run "TestResetRunE|TestCleanRunE" -v -race`
- TST-03 requirement satisfied: reset.go and clean.go error paths exercised
</success_criteria>

<output>
After completion, create `.planning/phases/02-command-test-coverage/02-03-SUMMARY.md`
</output>
