Skip to content

Commit cbb4e85

Browse files
committed
test(e2e): add sandbox lifecycle e2e test
1 parent b407a7b commit cbb4e85

1 file changed

Lines changed: 110 additions & 0 deletions

File tree

test/e2e/lifecycle_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//go:build e2e
2+
3+
package e2e
4+
5+
import (
6+
"context"
7+
"fmt"
8+
"testing"
9+
)
10+
11+
func TestLifecycle(t *testing.T) {
12+
// Step 1: Create sandbox and wait until running.
13+
sb := newSandbox(t)
14+
if sb.Status != "running" {
15+
t.Fatalf("expected status 'running' after create, got %q", sb.Status)
16+
}
17+
18+
// Step 2: Get — verify the sandbox is reachable and fields match.
19+
getCtx, getCancel := context.WithTimeout(context.Background(), defaultTimeout)
20+
defer getCancel()
21+
getOut, getErr, getCode := runCLICtx(getCtx, "sandbox", "get", "-o", "json", sb.ID)
22+
if getCode != 0 {
23+
t.Fatalf("sandbox get failed (exit %d)\nstdout: %s\nstderr: %s", getCode, getOut, getErr)
24+
}
25+
got := mustJSON[SandboxView](t, getOut)
26+
if got.ID != sb.ID {
27+
t.Errorf("get: ID mismatch: want %q, got %q", sb.ID, got.ID)
28+
}
29+
if testShape != "" && got.Shape != testShape {
30+
t.Errorf("get: Shape mismatch: want %q, got %q", testShape, got.Shape)
31+
}
32+
if got.Status != "running" {
33+
t.Errorf("get: expected status 'running', got %q", got.Status)
34+
}
35+
36+
// Step 3: Edit — set auto-pause to 30m (1800 seconds).
37+
editCtx, editCancel := context.WithTimeout(context.Background(), defaultTimeout)
38+
defer editCancel()
39+
editOut, editErr, editCode := runCLICtx(editCtx, "sandbox", "edit", sb.ID, "--auto-pause", "30m", "-o", "json")
40+
if editCode != 0 {
41+
t.Fatalf("sandbox edit failed (exit %d)\nstdout: %s\nstderr: %s", editCode, editOut, editErr)
42+
}
43+
edited := mustJSON[SandboxView](t, editOut)
44+
if edited.AutoPauseAfterSeconds == nil {
45+
t.Fatalf("edit: AutoPauseAfterSeconds is nil; expected 1800")
46+
}
47+
if *edited.AutoPauseAfterSeconds != 1800 {
48+
t.Errorf("edit: AutoPauseAfterSeconds: want 1800, got %d", *edited.AutoPauseAfterSeconds)
49+
}
50+
51+
// Step 4: Pause — command polls internally and renders JSON when not on TTY.
52+
pauseCtx, pauseCancel := context.WithTimeout(context.Background(), createTimeout)
53+
defer pauseCancel()
54+
pauseOut, pauseErr, pauseCode := runCLICtx(pauseCtx, "sandbox", "pause", sb.ID, "-o", "json")
55+
if pauseCode != 0 {
56+
t.Fatalf("sandbox pause failed (exit %d)\nstdout: %s\nstderr: %s", pauseCode, pauseOut, pauseErr)
57+
}
58+
paused := mustJSON[SandboxView](t, pauseOut)
59+
if paused.Status != "paused" {
60+
t.Errorf("pause: expected status 'paused', got %q", paused.Status)
61+
}
62+
63+
// Step 5: Resume — command polls internally and renders JSON when not on TTY.
64+
resumeCtx, resumeCancel := context.WithTimeout(context.Background(), createTimeout)
65+
defer resumeCancel()
66+
resumeOut, resumeErr, resumeCode := runCLICtx(resumeCtx, "sandbox", "resume", sb.ID, "-o", "json")
67+
if resumeCode != 0 {
68+
t.Fatalf("sandbox resume failed (exit %d)\nstdout: %s\nstderr: %s", resumeCode, resumeOut, resumeErr)
69+
}
70+
resumed := mustJSON[SandboxView](t, resumeOut)
71+
if resumed.Status != "running" {
72+
t.Errorf("resume: expected status 'running', got %q", resumed.Status)
73+
}
74+
75+
// Step 6: Fork — command polls until running (default) and renders JSON.
76+
// Note: sandbox/fork does not expose a --name flag; the server assigns a name.
77+
forkName := fmt.Sprintf("e2e-%s-fork", runID)
78+
_ = forkName // no --name flag; kept for tracing only
79+
forkCtx, forkCancel := context.WithTimeout(context.Background(), createTimeout)
80+
defer forkCancel()
81+
forkOut, forkErr, forkCode := runCLICtx(forkCtx, "sandbox", "fork", sb.ID, "-o", "json")
82+
if forkCode != 0 {
83+
t.Fatalf("sandbox fork failed (exit %d)\nstdout: %s\nstderr: %s", forkCode, forkOut, forkErr)
84+
}
85+
forked := mustJSON[SandboxView](t, forkOut)
86+
if forked.ID == sb.ID {
87+
t.Errorf("fork: expected new sandbox ID, got same ID %q", forked.ID)
88+
}
89+
// Register cleanup for the forked sandbox.
90+
t.Cleanup(func() {
91+
runCLI("sandbox", "rm", "--force", forked.ID) //nolint:errcheck
92+
})
93+
// Fork auto-resumes by default; wait to confirm it is running.
94+
waitRunning(t, forked.ID)
95+
96+
// Step 7: Rm — explicitly delete the original sandbox.
97+
rmCtx, rmCancel := context.WithTimeout(context.Background(), defaultTimeout)
98+
defer rmCancel()
99+
rmOut, rmErr, rmCode := runCLICtx(rmCtx, "sandbox", "rm", "--force", sb.ID)
100+
if rmCode != 0 {
101+
t.Fatalf("sandbox rm failed (exit %d)\nstdout: %s\nstderr: %s", rmCode, rmOut, rmErr)
102+
}
103+
// Verify removal: get should fail with a non-zero exit (404).
104+
verCtx, verCancel := context.WithTimeout(context.Background(), defaultTimeout)
105+
defer verCancel()
106+
_, _, verCode := runCLICtx(verCtx, "sandbox", "get", "-o", "json", sb.ID)
107+
if verCode == 0 {
108+
t.Errorf("expected non-zero exit after rm, but sandbox get succeeded for %q", sb.ID)
109+
}
110+
}

0 commit comments

Comments
 (0)