Skip to content

Commit f93d017

Browse files
danmuxclaude
andcommitted
Migrate sidecar client to V3 API
Switch all sidecar endpoints from /api/v2/sidecar/ to /api/v3/sidecar/. Resource-creation requests (create sidecar, create snapshot) now send V3 data envelopes; scoped actions (exec, add-key) stay flat. Responses decoded from V3 envelope using pre-initialized typed pointers. Add GetCommand method. Update fakes and all tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d4fcc27 commit f93d017

9 files changed

Lines changed: 385 additions & 125 deletions

File tree

acceptance/sidecar_gaps_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func TestSidecarExecArgsInRequestBody(t *testing.T) {
9494
assert.Equal(t, result.ExitCode, 0, "stderr: %s", result.Stderr)
9595

9696
reqs := cci.Recorder.AllRequests()
97-
execReqs := filterByPath(reqs, "/api/v2/sidecar/instances/sb-111/exec")
97+
execReqs := filterByPath(reqs, "/api/v3/sidecar/instances/sb-111/exec")
9898
assert.Equal(t, len(execReqs), 1)
9999

100100
var body map[string]interface{}
@@ -216,13 +216,13 @@ func TestSidecarCreateOrgIDFromConfig(t *testing.T) {
216216

217217
// Verify org_id in request body came from config
218218
reqs := cci.Recorder.AllRequests()
219-
createReqs := filterByMethod(reqs, "POST", "/api/v2/sidecar/instances")
219+
createReqs := filterByMethod(reqs, "POST", "/api/v3/sidecar/instances")
220220
assert.Equal(t, len(createReqs), 1)
221221

222222
var body map[string]interface{}
223223
err := json.Unmarshal(createReqs[0].Body, &body)
224224
assert.NilError(t, err)
225-
assert.Equal(t, body["org_id"], "org-from-config")
225+
assert.Equal(t, body["data"].(map[string]any)["references"].(map[string]any)["org"].(map[string]any)["id"], "org-from-config")
226226
}
227227

228228
func TestSidecarCreateNoOrgIDNoConfig(t *testing.T) {

acceptance/sidecar_snapshot_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ func TestSidecarSnapshotCreateSendsSidecarIDInBody(t *testing.T) {
3030
assert.Equal(t, result.ExitCode, 0, "stderr: %s", result.Stderr)
3131

3232
reqs := cci.Recorder.AllRequests()
33-
snapReqs := filterByMethod(reqs, "POST", "/api/v2/sidecar/snapshots")
33+
snapReqs := filterByMethod(reqs, "POST", "/api/v3/sidecar/snapshots")
3434
assert.Equal(t, len(snapReqs), 1, "expected 1 create snapshot request")
3535

3636
var body map[string]interface{}
3737
err := json.Unmarshal(snapReqs[0].Body, &body)
3838
assert.NilError(t, err)
39-
assert.Equal(t, body["sidecar_id"], "sb-111")
40-
assert.Equal(t, body["name"], "my-checkpoint")
39+
assert.Equal(t, body["data"].(map[string]any)["references"].(map[string]any)["sidecar_instance"].(map[string]any)["id"], "sb-111")
40+
assert.Equal(t, body["data"].(map[string]any)["attributes"].(map[string]any)["name"], "my-checkpoint")
4141
}
4242

4343
func TestSidecarSnapshotCreateMissingName(t *testing.T) {
@@ -79,13 +79,13 @@ func TestSidecarSnapshotCreateUsesActiveSidecar(t *testing.T) {
7979
assert.Equal(t, result.ExitCode, 0, "snapshot create stderr: %s", result.Stderr)
8080

8181
reqs := cci.Recorder.AllRequests()
82-
snapReqs := filterByMethod(reqs, "POST", "/api/v2/sidecar/snapshots")
82+
snapReqs := filterByMethod(reqs, "POST", "/api/v3/sidecar/snapshots")
8383
assert.Assert(t, len(snapReqs) >= 1, "expected at least 1 create snapshot request")
8484

8585
var body map[string]interface{}
8686
err := json.Unmarshal(snapReqs[0].Body, &body)
8787
assert.NilError(t, err)
88-
assert.Equal(t, body["sidecar_id"], "sidecar-new-123",
88+
assert.Equal(t, body["data"].(map[string]any)["references"].(map[string]any)["sidecar_instance"].(map[string]any)["id"], "sidecar-new-123",
8989
"expected active sidecar ID in request body")
9090

9191
// After a successful snapshot, the source sidecar should have been deleted
@@ -116,7 +116,7 @@ func TestSidecarSnapshotCreateDeletesSourceSidecar(t *testing.T) {
116116
"expected delete confirmation in stderr, got: %s", result.Stderr)
117117

118118
reqs := cci.Recorder.AllRequests()
119-
deleteReqs := filterByMethod(reqs, "DELETE", "/api/v2/sidecar/instances/sb-111")
119+
deleteReqs := filterByMethod(reqs, "DELETE", "/api/v3/sidecar/instances/sb-111")
120120
assert.Equal(t, len(deleteReqs), 1, "expected exactly 1 DELETE request, got %d", len(deleteReqs))
121121
}
122122

acceptance/sidecar_test.go

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func TestSidecarsListHappyPath(t *testing.T) {
4343

4444
// Verify org_id query param was sent
4545
reqs := cci.Recorder.AllRequests()
46-
listReqs := filterByPath(reqs, "/api/v2/sidecar/instances")
46+
listReqs := filterByPath(reqs, "/api/v3/sidecar/instances")
4747
assert.Assert(t, len(listReqs) >= 1, "expected at least 1 list request")
4848
assert.Equal(t, listReqs[0].URL.Query().Get("org_id"), "org-aaa")
4949
}
@@ -134,14 +134,20 @@ func TestSidecarsCreateHappyPath(t *testing.T) {
134134

135135
// Verify request body
136136
reqs := cci.Recorder.AllRequests()
137-
createReqs := filterByMethod(reqs, "POST", "/api/v2/sidecar/instances")
137+
createReqs := filterByMethod(reqs, "POST", "/api/v3/sidecar/instances")
138138
assert.Equal(t, len(createReqs), 1, "expected 1 create request")
139139

140-
var body map[string]interface{}
140+
var body map[string]any
141141
err := json.Unmarshal(createReqs[0].Body, &body)
142142
assert.NilError(t, err)
143-
assert.Equal(t, body["org_id"], "org-aaa")
144-
assert.Equal(t, body["name"], "my-new-sidecar")
143+
144+
data := body["data"].(map[string]any)
145+
attrs := data["attributes"].(map[string]any)
146+
refs := data["references"].(map[string]any)
147+
orgRef := refs["org"].(map[string]any)
148+
149+
assert.Equal(t, orgRef["id"], "org-aaa")
150+
assert.Equal(t, attrs["name"], "my-new-sidecar")
145151
}
146152

147153
func TestSidecarsCreateWithImage(t *testing.T) {
@@ -162,13 +168,16 @@ func TestSidecarsCreateWithImage(t *testing.T) {
162168
assert.Equal(t, result.ExitCode, 0, "stderr: %s", result.Stderr)
163169

164170
reqs := cci.Recorder.AllRequests()
165-
createReqs := filterByMethod(reqs, "POST", "/api/v2/sidecar/instances")
171+
createReqs := filterByMethod(reqs, "POST", "/api/v3/sidecar/instances")
166172
assert.Equal(t, len(createReqs), 1)
167173

168-
var body map[string]interface{}
174+
var body map[string]any
169175
err := json.Unmarshal(createReqs[0].Body, &body)
170176
assert.NilError(t, err)
171-
assert.Equal(t, body["image"], "ubuntu:22.04")
177+
178+
data := body["data"].(map[string]any)
179+
attrs := data["attributes"].(map[string]any)
180+
assert.Equal(t, attrs["image"], "ubuntu:22.04")
172181
}
173182

174183
func TestSidecarsExecHappyPath(t *testing.T) {
@@ -199,7 +208,7 @@ func TestSidecarsExecHappyPath(t *testing.T) {
199208

200209
// Verify exec request with sidecar ID in path
201210
reqs := cci.Recorder.AllRequests()
202-
execReqs := filterByPath(reqs, "/api/v2/sidecar/instances/sb-111/exec")
211+
execReqs := filterByPath(reqs, "/api/v3/sidecar/instances/sb-111/exec")
203212
assert.Equal(t, len(execReqs), 1, "expected 1 exec request")
204213

205214
var body map[string]interface{}
@@ -233,7 +242,7 @@ func TestSidecarsAddSSHKeyFromString(t *testing.T) {
233242

234243
// Verify add-key request with sidecar ID in path
235244
reqs := cci.Recorder.AllRequests()
236-
addKeyReqs := filterByPath(reqs, "/api/v2/sidecar/instances/sb-111/ssh/add-key")
245+
addKeyReqs := filterByPath(reqs, "/api/v3/sidecar/instances/sb-111/ssh/add-key")
237246
assert.Equal(t, len(addKeyReqs), 1, "expected 1 add-key request")
238247

239248
var body map[string]interface{}
@@ -266,7 +275,7 @@ func TestSidecarsAddSSHKeyFromFile(t *testing.T) {
266275

267276
// Verify the key was sent in the request
268277
reqs := cci.Recorder.AllRequests()
269-
addKeyReqs := filterByPath(reqs, "/api/v2/sidecar/instances/sb-111/ssh/add-key")
278+
addKeyReqs := filterByPath(reqs, "/api/v3/sidecar/instances/sb-111/ssh/add-key")
270279
assert.Equal(t, len(addKeyReqs), 1)
271280

272281
var body map[string]interface{}
@@ -402,7 +411,7 @@ func TestSidecarsExecWithArgs(t *testing.T) {
402411

403412
// Verify exec request body has the command
404413
reqs := cci.Recorder.AllRequests()
405-
execReqs := filterByPath(reqs, "/api/v2/sidecar/instances/sb-111/exec")
414+
execReqs := filterByPath(reqs, "/api/v3/sidecar/instances/sb-111/exec")
406415
assert.Equal(t, len(execReqs), 1)
407416

408417
var body map[string]interface{}
@@ -612,7 +621,7 @@ func TestSidecarsExplicitIDOverridesActive(t *testing.T) {
612621
assert.Equal(t, result.ExitCode, 0, "exec stderr: %s", result.Stderr)
613622

614623
reqs := cci.Recorder.AllRequests()
615-
execReqs := filterByPath(reqs, "/api/v2/sidecar/instances/sb-explicit/exec")
624+
execReqs := filterByPath(reqs, "/api/v3/sidecar/instances/sb-explicit/exec")
616625
assert.Assert(t, len(execReqs) >= 1, "expected exec request to use explicit sidecar ID, got requests: %v", reqs)
617626
}
618627

acceptance/validate_test.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -389,11 +389,11 @@ func TestValidateRunRemoteUsesSSH(t *testing.T) {
389389
reqs := cci.Recorder.AllRequests()
390390

391391
// AddSSHKey must be called — proves SSH path was taken.
392-
addKeyReqs := filterByPath(reqs, "/api/v2/sidecar/instances/sidecar-123/ssh/add-key")
392+
addKeyReqs := filterByPath(reqs, "/api/v3/sidecar/instances/sidecar-123/ssh/add-key")
393393
assert.Equal(t, len(addKeyReqs), 1, "expected 1 add-key request; got: %v", reqs)
394394

395395
// HTTP exec must NOT be called — SSH is used instead.
396-
execReqs := filterByPath(reqs, "/api/v2/sidecar/instances/sidecar-123/exec")
396+
execReqs := filterByPath(reqs, "/api/v3/sidecar/instances/sidecar-123/exec")
397397
assert.Equal(t, len(execReqs), 0, "expected 0 HTTP exec requests (SSH should be used)")
398398
}
399399

@@ -442,16 +442,19 @@ func TestValidateAutoCreatesSidecar(t *testing.T) {
442442
reqs := cci.Recorder.AllRequests()
443443

444444
// A sidecar must have been created with the configured image.
445-
createReqs := filterByPath(reqs, "/api/v2/sidecar/instances")
445+
createReqs := filterByPath(reqs, "/api/v3/sidecar/instances")
446446
assert.Equal(t, len(createReqs), 1, "expected 1 create-sidecar request; got: %v", reqs)
447447

448-
var body map[string]interface{}
448+
var body map[string]any
449449
assert.NilError(t, json.Unmarshal(createReqs[0].Body, &body))
450-
assert.Equal(t, body["image"], "my-snapshot-abc123", "expected sidecar image from config")
451-
assert.Equal(t, body["org_id"], "org-aaa", "expected org from CIRCLECI_ORG_ID")
450+
envelope := body["data"].(map[string]any)
451+
attrs := envelope["attributes"].(map[string]any)
452+
refs := envelope["references"].(map[string]any)
453+
assert.Equal(t, attrs["image"], "my-snapshot-abc123", "expected sidecar image from config")
454+
assert.Equal(t, refs["org"].(map[string]any)["id"], "org-aaa", "expected org from CIRCLECI_ORG_ID")
452455

453456
// AddSSHKey must be called on the newly created sidecar — proves it was used.
454-
addKeyReqs := filterByPath(reqs, "/api/v2/sidecar/instances/sidecar-new-123/ssh/add-key")
457+
addKeyReqs := filterByPath(reqs, "/api/v3/sidecar/instances/sidecar-new-123/ssh/add-key")
455458
assert.Equal(t, len(addKeyReqs), 1, "expected 1 add-key request for newly created sidecar; got: %v", reqs)
456459
}
457460

internal/circleci/circleci_test.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func TestListSidecars(t *testing.T) {
9292
last := reqs[len(reqs)-1]
9393
assert.Equal(t, last.Method, "GET")
9494
assert.Equal(t, last.URL.Query().Get("org_id"), "org-1")
95-
assert.Equal(t, last.URL.Query().Get("all"), "")
95+
assert.Equal(t, last.URL.Query().Get("all"), "false")
9696
})
9797

9898
t.Run("sends all=true when requested", func(t *testing.T) {
@@ -126,9 +126,6 @@ func TestCreateSidecar(t *testing.T) {
126126
if sb.OrgID != "org-1" {
127127
t.Errorf("expected org org-1, got %s", sb.OrgID)
128128
}
129-
if sb.Image != "ubuntu:22.04" {
130-
t.Errorf("expected image ubuntu:22.04, got %s", sb.Image)
131-
}
132129
}
133130

134131
func TestDeleteSidecar(t *testing.T) {
@@ -149,7 +146,7 @@ func TestDeleteSidecar(t *testing.T) {
149146
if last.Method != "DELETE" {
150147
t.Errorf("expected DELETE, got %s", last.Method)
151148
}
152-
if last.URL.Path != "/api/v2/sidecar/instances/sb-1" {
149+
if last.URL.Path != "/api/v3/sidecar/instances/sb-1" {
153150
t.Errorf("unexpected path: %s", last.URL.Path)
154151
}
155152
if got := last.Header.Get("Circle-Token"); got != "test-token" {
@@ -211,7 +208,7 @@ func TestAddSSHKey(t *testing.T) {
211208
t.Errorf("expected Circle-Token test-token, got %s", got)
212209
}
213210
// Verify sidecar ID in URL path.
214-
if last.URL.Path != "/api/v2/sidecar/instances/sb-1/ssh/add-key" {
211+
if last.URL.Path != "/api/v3/sidecar/instances/sb-1/ssh/add-key" {
215212
t.Errorf("unexpected path: %s", last.URL.Path)
216213
}
217214
}
@@ -285,8 +282,8 @@ func TestExec(t *testing.T) {
285282

286283
reqs := fake.Recorder.AllRequests()
287284
last := reqs[len(reqs)-1]
288-
if last.URL.Path != "/api/v2/sidecar/instances/sb-1/exec" {
289-
t.Errorf("expected /api/v2/sidecar/instances/sb-1/exec, got %s", last.URL.Path)
285+
if last.URL.Path != "/api/v3/sidecar/instances/sb-1/exec" {
286+
t.Errorf("expected /api/v3/sidecar/instances/sb-1/exec, got %s", last.URL.Path)
290287
}
291288
})
292289
}

0 commit comments

Comments
 (0)