feat(control-plane): idempotent deletes + command-level wiring tests · Entire

feat(control-plane): idempotent deletes + command-level wiring tests

ed45929·

toothbrush·3w ago·2 files·+105 added/-0 removed

Addresses trail #642 findings.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Sessions

416181183067View transcript

Changes

2

89 unmodified lines

return nil
}
if err := del(ctx, c, id); err != nil {
    // Idempotent delete: a resource that's already gone (a 404 from the
    // delete call — e.g. a ULID passed straight through, or a concurrent
    // delete) is the desired end state, not an error.
    if isNotFound(err) {
        cmd.Printf("%s not found; nothing to delete\n", label)
        return nil
    }
    return err
}
cmd.Printf("Deleted %s\n", label)

Mcmd/entire/cli/corecmd.go+7

package cli

import (
    "bytes"
    "context"
    "net/http"
    "net/http/httptest"
    "testing"

"github.com/spf13/cobra"
    "github.com/stretchr/testify/require"

"github.com/entireio/cli/internal/coreapi"
)

// testDeleteULID is a syntactically valid ULID (26 Crockford base32 chars, no
// I/L/O/U) so it passes looksLikeULID and the delete commands skip the name
// lookup, addressing the resource by id directly.
const testDeleteULID = "01HZX7QABCDEFGHJKMNPQRSTVW"

// runDeleteCmd points the active-context client at srv via the activeCoreClient
// seam, runs newCmd() with args, and returns its stdout and error. The caller
// must not be parallel: the seam is package-global.
func runDeleteCmd(t *testing.T, newCmd func() *cobra.Command, srvURL string, args ...string) (string, error) {
    t.Helper()
    prev := activeCoreClient
    activeCoreClient = func(context.Context) (*coreapi.Client, error) {
        return coreapi.NewWithBearer(srvURL, "tok")
    }
    t.Cleanup(func() { activeCoreClient = prev })

cmd := newCmd()
    var out bytes.Buffer
    cmd.SetOut(&out)
    cmd.SetErr(&bytes.Buffer{})
    cmd.SetArgs(args)
    err := cmd.ExecuteContext(t.Context())
    return out.String(), err
}

// TestControlPlaneDelete_Wiring exercises the org/project/repo delete commands
// end-to-end through cobra: that --force bypasses the prompt and issues DELETE
// against the right path, that an already-gone resource (404) is idempotent,
// and that a non-interactive run without --force refuses rather than deleting
// unprompted.
//
// Not parallel: swaps the package-level activeCoreClient seam.
func TestControlPlaneDelete_Wiring(t *testing.T) {
    cases := []struct {
        noun     string
        newCmd   func() *cobra.Command
        wantPath string
    }{
        {"org", newOrgDeleteCmd, "/api/v1/orgs/" + testDeleteULID},
        {"project", newProjectDeleteCmd, "/api/v1/projects/" + testDeleteULID},
        {"repo", newRepoDeleteCmd, "/api/v1/repos/" + testDeleteULID},
    }

for _, tc := range cases {
        t.Run(tc.noun+"/force deletes via the right path", func(t *testing.T) {
            var gotMethod, gotPath string
            srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                gotMethod, gotPath = r.Method, r.URL.Path
                w.WriteHeader(http.StatusNoContent)
            }))
            t.Cleanup(srv.Close)

out, err := runDeleteCmd(t, tc.newCmd, srv.URL, testDeleteULID, "--force")
            require.NoError(t, err)
            require.Equal(t, http.MethodDelete, gotMethod)
            require.Equal(t, tc.wantPath, gotPath)
            require.Contains(t, out, "Deleted "+tc.noun+" "+testDeleteULID)
        })

t.Run(tc.noun+"/already-gone is idempotent", func(t *testing.T) {
            srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
                writeNotFoundProblem(t, w)
            }))
            t.Cleanup(srv.Close)

out, err := runDeleteCmd(t, tc.newCmd, srv.URL, testDeleteULID, "--force")
            require.NoError(t, err)
            require.Contains(t, out, "not found; nothing to delete")
        })

t.Run(tc.noun+"/refuses without --force when non-interactive", func(t *testing.T) {
            // A ULID needs no resolve, so the refusal lands before any request.
            srv := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
                t.Errorf("unexpected request %s %s", r.Method, r.URL.Path)
            }))
            t.Cleanup(srv.Close)

_, err := runDeleteCmd(t, tc.newCmd, srv.URL, testDeleteULID)
            require.Error(t, err)
            require.Contains(t, err.Error(), "--force")
        })
    }
}