Skip to content

Commit d9aab2f

Browse files
zozo123claude
andcommitted
test(orgo): add end-to-end create/run/delete provider test
Drives the real *orgoHTTPClient through the delegated-run backend against a fake Orgo REST API (httptest), covering the full lifecycle: create workspace, create computer, run bash, delete computer, delete workspace. Asserts the Bearer auth header, exit code, SyncDelegated, stdout, and that every expected API endpoint is hit. No real secrets: the API key is a dummy value via CRABBOX_ORGO_API_KEY and the base URL is the in-process test server via CRABBOX_ORGO_API_BASE, so the test never reaches the live Orgo API. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c201eb1 commit d9aab2f

1 file changed

Lines changed: 114 additions & 0 deletions

File tree

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package orgo
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"net/http"
8+
"net/http/httptest"
9+
"strings"
10+
"sync"
11+
"testing"
12+
)
13+
14+
// TestOrgoBackendEndToEndCreateRunDelete drives the real *orgoHTTPClient through
15+
// backend.Run against a fake Orgo REST API, exercising the full delegated-run
16+
// lifecycle: create workspace -> create computer -> run bash -> delete computer
17+
// -> delete workspace.
18+
//
19+
// No real secrets: the API key is a dummy value supplied via
20+
// CRABBOX_ORGO_API_KEY and the base URL is the in-process httptest server via
21+
// CRABBOX_ORGO_API_BASE, so the test never reaches the live Orgo API.
22+
func TestOrgoBackendEndToEndCreateRunDelete(t *testing.T) {
23+
// Isolate the on-disk lease claim so the test never touches real state.
24+
t.Setenv("XDG_STATE_HOME", t.TempDir())
25+
26+
const (
27+
dummyKey = "dummy-orgo-key"
28+
workspaceID = "ws_e2e"
29+
computerID = "computer_e2e"
30+
)
31+
32+
var (
33+
mu sync.Mutex
34+
hit = map[string]bool{}
35+
)
36+
mark := func(k string) { mu.Lock(); hit[k] = true; mu.Unlock() }
37+
38+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
39+
if got := r.Header.Get("Authorization"); got != "Bearer "+dummyKey {
40+
t.Errorf("authorization=%q, want Bearer %s", got, dummyKey)
41+
http.Error(w, "unauthorized", http.StatusUnauthorized)
42+
return
43+
}
44+
w.Header().Set("Content-Type", "application/json")
45+
switch {
46+
case r.Method == http.MethodPost && r.URL.Path == "/workspaces":
47+
mark("create_workspace")
48+
_ = json.NewEncoder(w).Encode(orgoWorkspace{ID: workspaceID, Name: "crabbox-e2e", Status: "active"})
49+
case r.Method == http.MethodPost && r.URL.Path == "/computers":
50+
mark("create_computer")
51+
var req orgoCreateComputerRequest
52+
_ = json.NewDecoder(r.Body).Decode(&req)
53+
if req.WorkspaceID != workspaceID {
54+
t.Errorf("create computer workspace_id=%q, want %q", req.WorkspaceID, workspaceID)
55+
}
56+
_ = json.NewEncoder(w).Encode(orgoComputer{
57+
ID: computerID, InstanceID: "instance_e2e", Name: req.Name,
58+
WorkspaceID: req.WorkspaceID, Status: "running",
59+
ConnectionURL: "https://www.orgo.ai/desktops/instance_e2e",
60+
})
61+
case r.Method == http.MethodPost && r.URL.Path == "/computers/"+computerID+"/bash":
62+
mark("run_bash")
63+
var req map[string]any
64+
_ = json.NewDecoder(r.Body).Decode(&req)
65+
if cmd, _ := req["command"].(string); !strings.Contains(cmd, "crabbox-orgo-ok") {
66+
t.Errorf("bash command=%q, want it to contain crabbox-orgo-ok", req["command"])
67+
}
68+
zero := 0
69+
_ = json.NewEncoder(w).Encode(orgoBashResponse{Stdout: "crabbox-orgo-ok\n", ExitCodeCamel: &zero})
70+
case r.Method == http.MethodDelete && r.URL.Path == "/computers/"+computerID:
71+
mark("delete_computer")
72+
w.WriteHeader(http.StatusNoContent)
73+
case r.Method == http.MethodDelete && r.URL.Path == "/workspaces/"+workspaceID:
74+
mark("delete_workspace")
75+
w.WriteHeader(http.StatusNoContent)
76+
default:
77+
t.Errorf("unexpected request %s %s", r.Method, r.URL.Path)
78+
http.Error(w, "not found", http.StatusNotFound)
79+
}
80+
}))
81+
t.Cleanup(server.Close)
82+
83+
// Dummy key + fake base URL; the production client is routed at the test server.
84+
t.Setenv("CRABBOX_ORGO_API_KEY", dummyKey)
85+
t.Setenv("CRABBOX_ORGO_API_BASE", server.URL)
86+
87+
var out, errBuf bytes.Buffer
88+
backend := NewOrgoBackend(Provider{}.Spec(), Config{}, Runtime{
89+
Stdout: &out, Stderr: &errBuf, HTTP: server.Client(),
90+
}).(*orgoBackend)
91+
92+
result, err := backend.Run(context.Background(), RunRequest{
93+
Repo: Repo{Root: t.TempDir()},
94+
NoSync: true,
95+
Command: []string{"echo", "crabbox-orgo-ok"},
96+
})
97+
if err != nil {
98+
t.Fatalf("Run: %v (stderr=%q)", err, errBuf.String())
99+
}
100+
if result.ExitCode != 0 {
101+
t.Fatalf("ExitCode=%d, want 0 (result=%#v)", result.ExitCode, result)
102+
}
103+
if !result.SyncDelegated {
104+
t.Fatalf("SyncDelegated=false, want true for a delegated-run provider (result=%#v)", result)
105+
}
106+
if !strings.Contains(out.String(), "crabbox-orgo-ok") {
107+
t.Fatalf("stdout=%q, want it to contain crabbox-orgo-ok", out.String())
108+
}
109+
for _, k := range []string{"create_workspace", "create_computer", "run_bash", "delete_computer", "delete_workspace"} {
110+
if !hit[k] {
111+
t.Fatalf("missing expected Orgo API call %q (hits=%v)", k, hit)
112+
}
113+
}
114+
}

0 commit comments

Comments
 (0)