// Tests for `carlos instances door-key` (instance-moves P7, #175): the // apiSession wire calls (doorkeys_cli.go) against a bare httptest server — // method, path and body round trips, the shape instances_cli_test.go and // mail_cli_test.go pin their own wire calls with — plus RunInstancesDoorKey- // level tests against a fake console with a credential written straight to // disk (fleetsCLIFixture/servicesCLIFixture's pattern), proving the key // really reaches stdout exactly once, the list table prints, and revoke's // confirmation names the id. package main import ( "context" "encoding/json" "net/http" "net/http/httptest" "path/filepath" "strings" "testing" ) // ---- apiSession-level: path + body + wire-shape round trips --------------- func TestAPISessionDoorKeyMintWireShape(t *testing.T) { var gotPath, gotMethod string var gotBody struct { Label string `json:"label"` } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotPath, gotMethod = r.URL.Path, r.Method json.NewDecoder(r.Body).Decode(&gotBody) w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]any{ "key": "cdk1.bab.titogo.0123456789abcdef." + strings.Repeat("A", 43), "id": "0123456789abcdef", "account": "bab", "app": "titogo", }) })) defer ts.Close() res, err := newTestSession(ts.URL).doorKeyMint(context.Background(), apiAcct{Seg: "bab"}, "titogo", "router") if err != nil { t.Fatalf("doorKeyMint: %v", err) } if gotMethod != http.MethodPost || gotPath != "/api/cli/apps/bab/titogo/door-keys" { t.Fatalf("method/path = %s %s", gotMethod, gotPath) } if gotBody.Label != "router" { t.Fatalf("body label = %q, want %q", gotBody.Label, "router") } if res.ID != "0123456789abcdef" || !strings.HasPrefix(res.Key, "cdk1.bab.titogo.") { t.Fatalf("result = %+v", res) } } func TestAPISessionDoorKeyListWireShape(t *testing.T) { var gotPath, gotMethod string ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotPath, gotMethod = r.URL.Path, r.Method json.NewEncoder(w).Encode(map[string]any{ "keys": []map[string]any{ {"id": "0123456789abcdef", "label": "router", "created_at": "2026-09-28T00:00:00Z"}, }, }) })) defer ts.Close() keys, err := newTestSession(ts.URL).doorKeyList(context.Background(), apiAcct{Seg: "bab"}, "titogo") if err != nil { t.Fatalf("doorKeyList: %v", err) } if gotMethod != http.MethodGet || gotPath != "/api/cli/apps/bab/titogo/door-keys" { t.Fatalf("method/path = %s %s", gotMethod, gotPath) } if len(keys) != 1 || keys[0].ID != "0123456789abcdef" || keys[0].Label != "router" { t.Fatalf("keys = %+v", keys) } } func TestAPISessionDoorKeyRevokeWireShape(t *testing.T) { var gotPath, gotMethod string ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotPath, gotMethod = r.URL.Path, r.Method json.NewEncoder(w).Encode(map[string]any{"revoked": "0123456789abcdef"}) })) defer ts.Close() revoked, err := newTestSession(ts.URL).doorKeyRevoke(context.Background(), apiAcct{Seg: "bab"}, "titogo", "0123456789abcdef") if err != nil { t.Fatalf("doorKeyRevoke: %v", err) } if gotMethod != http.MethodPost || gotPath != "/api/cli/apps/bab/titogo/door-keys/0123456789abcdef/revoke" { t.Fatalf("method/path = %s %s", gotMethod, gotPath) } if revoked != "0123456789abcdef" { t.Fatalf("revoked = %q", revoked) } } func TestAPISessionDoorKeyRevokeUnknownID404(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]any{"error": "no door key with that id"}) })) defer ts.Close() if _, err := newTestSession(ts.URL).doorKeyRevoke(context.Background(), apiAcct{Seg: "bab"}, "titogo", "deadbeef"); err == nil { t.Fatal("doorKeyRevoke: want an error on 404") } } // ---- RunInstancesDoorKey-level: full verb, good args, real printing ------- // doorKeysCLIFixture wires up a fake console stubbing the three door-key // doors plus a credential pointed at it, entirely in-process — the same // shortcut fleetsCLIFixture/servicesCLIFixture take. func doorKeysCLIFixture(t *testing.T) *httptest.Server { t.Helper() home := t.TempDir() t.Setenv("HOME", home) t.Setenv("CARLOS_CONSOLE", "") t.Setenv("CARLOS_DEPLOYMENT_BUCKET", "") t.Setenv("CARLOS_DEPLOYMENT_DIR", "") t.Chdir(t.TempDir()) creds := filepath.Join(home, "carlos-credentials") t.Setenv("CARLOS_CREDENTIALS", creds) mux := http.NewServeMux() mux.HandleFunc("POST /api/cli/apps/{account}/{app}/door-keys", func(w http.ResponseWriter, r *http.Request) { var req struct{ Label string } json.NewDecoder(r.Body).Decode(&req) w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]any{ "key": "cdk1." + r.PathValue("account") + "." + r.PathValue("app") + ".0123456789abcdef." + strings.Repeat("A", 43), "id": "0123456789abcdef", "account": r.PathValue("account"), "app": r.PathValue("app"), }) }) mux.HandleFunc("GET /api/cli/apps/{account}/{app}/door-keys", func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]any{ "keys": []map[string]any{ {"id": "0123456789abcdef", "label": "router", "created_at": "2026-09-28T00:00:00Z"}, {"id": "fedcba9876543210", "label": "", "created_at": "2026-09-27T00:00:00Z"}, }, }) }) mux.HandleFunc("POST /api/cli/apps/{account}/{app}/door-keys/{id}/revoke", func(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") if id != "0123456789abcdef" { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]any{"error": "no door key with that id"}) return } json.NewEncoder(w).Encode(map[string]any{"revoked": id}) }) ts := httptest.NewServer(mux) t.Cleanup(ts.Close) writeTestCredential(t, creds, ts.URL) return ts } func TestRunInstancesDoorKeyCreatePrintsKeyOnce(t *testing.T) { doorKeysCLIFixture(t) buf := captureStdout(t) runInstancesDoorKey([]string{"create", "--app", "titogo", "--account", "bab", "--label", "router"}) out := buf.String() if got := strings.Count(out, "cdk1.bab.titogo.0123456789abcdef."); got != 1 { t.Fatalf("stdout = %q, want the key exactly once", out) } if !strings.Contains(out, "shown once") { t.Fatalf("stdout = %q, want a shown-once warning", out) } } func TestRunInstancesDoorKeyListPrintsTable(t *testing.T) { doorKeysCLIFixture(t) buf := captureStdout(t) runInstancesDoorKey([]string{"list", "--app", "titogo", "--account", "bab"}) out := buf.String() if !strings.Contains(out, "0123456789abcdef") || !strings.Contains(out, "router") { t.Fatalf("stdout = %q, want the key id and label", out) } if !strings.Contains(out, "fedcba9876543210") { t.Fatalf("stdout = %q, want the second key id", out) } } func TestRunInstancesDoorKeyRevokePrintsConfirmation(t *testing.T) { doorKeysCLIFixture(t) buf := captureStdout(t) runInstancesDoorKey([]string{"revoke", "--app", "titogo", "--account", "bab", "0123456789abcdef"}) out := buf.String() if !strings.Contains(out, "revoked") || !strings.Contains(out, "0123456789abcdef") { t.Fatalf("stdout = %q, want a confirmation naming the id", out) } }