carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1// Tests for `carlos instances door-key` (instance-moves P7, #175): the
2// apiSession wire calls (doorkeys_cli.go) against a bare httptest server —
3// method, path and body round trips, the shape instances_cli_test.go and
4// mail_cli_test.go pin their own wire calls with — plus RunInstancesDoorKey-
5// level tests against a fake console with a credential written straight to
6// disk (fleetsCLIFixture/servicesCLIFixture's pattern), proving the key
7// really reaches stdout exactly once, the list table prints, and revoke's
8// confirmation names the id.
9package main
10
11import (
12 "context"
13 "encoding/json"
14 "net/http"
15 "net/http/httptest"
16 "path/filepath"
17 "strings"
18 "testing"
19)
20
21// ---- apiSession-level: path + body + wire-shape round trips ---------------
22
23func TestAPISessionDoorKeyMintWireShape(t *testing.T) {
24 var gotPath, gotMethod string
25 var gotBody struct {
26 Label string `json:"label"`
27 }
28 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
29 gotPath, gotMethod = r.URL.Path, r.Method
30 json.NewDecoder(r.Body).Decode(&gotBody)
31 w.WriteHeader(http.StatusCreated)
32 json.NewEncoder(w).Encode(map[string]any{
33 "key": "cdk1.bab.titogo.0123456789abcdef." + strings.Repeat("A", 43),
34 "id": "0123456789abcdef", "account": "bab", "app": "titogo",
35 })
36 }))
37 defer ts.Close()
38
39 res, err := newTestSession(ts.URL).doorKeyMint(context.Background(), apiAcct{Seg: "bab"}, "titogo", "router")
40 if err != nil {
41 t.Fatalf("doorKeyMint: %v", err)
42 }
43 if gotMethod != http.MethodPost || gotPath != "/api/cli/apps/bab/titogo/door-keys" {
44 t.Fatalf("method/path = %s %s", gotMethod, gotPath)
45 }
46 if gotBody.Label != "router" {
47 t.Fatalf("body label = %q, want %q", gotBody.Label, "router")
48 }
49 if res.ID != "0123456789abcdef" || !strings.HasPrefix(res.Key, "cdk1.bab.titogo.") {
50 t.Fatalf("result = %+v", res)
51 }
52}
53
54func TestAPISessionDoorKeyListWireShape(t *testing.T) {
55 var gotPath, gotMethod string
56 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
57 gotPath, gotMethod = r.URL.Path, r.Method
58 json.NewEncoder(w).Encode(map[string]any{
59 "keys": []map[string]any{
60 {"id": "0123456789abcdef", "label": "router", "created_at": "2026-09-28T00:00:00Z"},
61 },
62 })
63 }))
64 defer ts.Close()
65
66 keys, err := newTestSession(ts.URL).doorKeyList(context.Background(), apiAcct{Seg: "bab"}, "titogo")
67 if err != nil {
68 t.Fatalf("doorKeyList: %v", err)
69 }
70 if gotMethod != http.MethodGet || gotPath != "/api/cli/apps/bab/titogo/door-keys" {
71 t.Fatalf("method/path = %s %s", gotMethod, gotPath)
72 }
73 if len(keys) != 1 || keys[0].ID != "0123456789abcdef" || keys[0].Label != "router" {
74 t.Fatalf("keys = %+v", keys)
75 }
76}
77
78func TestAPISessionDoorKeyRevokeWireShape(t *testing.T) {
79 var gotPath, gotMethod string
80 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
81 gotPath, gotMethod = r.URL.Path, r.Method
82 json.NewEncoder(w).Encode(map[string]any{"revoked": "0123456789abcdef"})
83 }))
84 defer ts.Close()
85
86 revoked, err := newTestSession(ts.URL).doorKeyRevoke(context.Background(), apiAcct{Seg: "bab"}, "titogo", "0123456789abcdef")
87 if err != nil {
88 t.Fatalf("doorKeyRevoke: %v", err)
89 }
90 if gotMethod != http.MethodPost || gotPath != "/api/cli/apps/bab/titogo/door-keys/0123456789abcdef/revoke" {
91 t.Fatalf("method/path = %s %s", gotMethod, gotPath)
92 }
93 if revoked != "0123456789abcdef" {
94 t.Fatalf("revoked = %q", revoked)
95 }
96}
97
98func TestAPISessionDoorKeyRevokeUnknownID404(t *testing.T) {
99 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
100 w.WriteHeader(http.StatusNotFound)
101 json.NewEncoder(w).Encode(map[string]any{"error": "no door key with that id"})
102 }))
103 defer ts.Close()
104
105 if _, err := newTestSession(ts.URL).doorKeyRevoke(context.Background(), apiAcct{Seg: "bab"}, "titogo", "deadbeef"); err == nil {
106 t.Fatal("doorKeyRevoke: want an error on 404")
107 }
108}
109
110// ---- RunInstancesDoorKey-level: full verb, good args, real printing -------
111
112// doorKeysCLIFixture wires up a fake console stubbing the three door-key
113// doors plus a credential pointed at it, entirely in-process — the same
114// shortcut fleetsCLIFixture/servicesCLIFixture take.
115func doorKeysCLIFixture(t *testing.T) *httptest.Server {
116 t.Helper()
117 home := t.TempDir()
118 t.Setenv("HOME", home)
119 t.Setenv("CARLOS_CONSOLE", "")
120 t.Setenv("CARLOS_DEPLOYMENT_BUCKET", "")
121 t.Setenv("CARLOS_DEPLOYMENT_DIR", "")
122 t.Chdir(t.TempDir())
123 creds := filepath.Join(home, "carlos-credentials")
124 t.Setenv("CARLOS_CREDENTIALS", creds)
125
126 mux := http.NewServeMux()
127 mux.HandleFunc("POST /api/cli/apps/{account}/{app}/door-keys", func(w http.ResponseWriter, r *http.Request) {
128 var req struct{ Label string }
129 json.NewDecoder(r.Body).Decode(&req)
130 w.WriteHeader(http.StatusCreated)
131 json.NewEncoder(w).Encode(map[string]any{
132 "key": "cdk1." + r.PathValue("account") + "." + r.PathValue("app") + ".0123456789abcdef." + strings.Repeat("A", 43),
133 "id": "0123456789abcdef", "account": r.PathValue("account"), "app": r.PathValue("app"),
134 })
135 })
136 mux.HandleFunc("GET /api/cli/apps/{account}/{app}/door-keys", func(w http.ResponseWriter, r *http.Request) {
137 json.NewEncoder(w).Encode(map[string]any{
138 "keys": []map[string]any{
139 {"id": "0123456789abcdef", "label": "router", "created_at": "2026-09-28T00:00:00Z"},
140 {"id": "fedcba9876543210", "label": "", "created_at": "2026-09-27T00:00:00Z"},
141 },
142 })
143 })
144 mux.HandleFunc("POST /api/cli/apps/{account}/{app}/door-keys/{id}/revoke", func(w http.ResponseWriter, r *http.Request) {
145 id := r.PathValue("id")
146 if id != "0123456789abcdef" {
147 w.WriteHeader(http.StatusNotFound)
148 json.NewEncoder(w).Encode(map[string]any{"error": "no door key with that id"})
149 return
150 }
151 json.NewEncoder(w).Encode(map[string]any{"revoked": id})
152 })
153 ts := httptest.NewServer(mux)
154 t.Cleanup(ts.Close)
155 writeTestCredential(t, creds, ts.URL)
156 return ts
157}
158
159func TestRunInstancesDoorKeyCreatePrintsKeyOnce(t *testing.T) {
160 doorKeysCLIFixture(t)
161 buf := captureStdout(t)
162
163 runInstancesDoorKey([]string{"create", "--app", "titogo", "--account", "bab", "--label", "router"})
164
165 out := buf.String()
166 if got := strings.Count(out, "cdk1.bab.titogo.0123456789abcdef."); got != 1 {
167 t.Fatalf("stdout = %q, want the key exactly once", out)
168 }
169 if !strings.Contains(out, "shown once") {
170 t.Fatalf("stdout = %q, want a shown-once warning", out)
171 }
172}
173
174func TestRunInstancesDoorKeyListPrintsTable(t *testing.T) {
175 doorKeysCLIFixture(t)
176 buf := captureStdout(t)
177
178 runInstancesDoorKey([]string{"list", "--app", "titogo", "--account", "bab"})
179
180 out := buf.String()
181 if !strings.Contains(out, "0123456789abcdef") || !strings.Contains(out, "router") {
182 t.Fatalf("stdout = %q, want the key id and label", out)
183 }
184 if !strings.Contains(out, "fedcba9876543210") {
185 t.Fatalf("stdout = %q, want the second key id", out)
186 }
187}
188
189func TestRunInstancesDoorKeyRevokePrintsConfirmation(t *testing.T) {
190 doorKeysCLIFixture(t)
191 buf := captureStdout(t)
192
193 runInstancesDoorKey([]string{"revoke", "--app", "titogo", "--account", "bab", "0123456789abcdef"})
194
195 out := buf.String()
196 if !strings.Contains(out, "revoked") || !strings.Contains(out, "0123456789abcdef") {
197 t.Fatalf("stdout = %q, want a confirmation naming the id", out)
198 }
199}
200