crabbox/internal/cli/code_test.go
2026-05-05 02:34:37 -07:00

147 lines
5.2 KiB
Go

package cli
import (
"net/http"
"strings"
"testing"
"nhooyr.io/websocket"
)
func TestWebCodeURLs(t *testing.T) {
if got := webCodeAgentURL("https://crabbox.openclaw.ai", "cbx_abcdef123456", "code_abc"); got != "wss://crabbox.openclaw.ai/v1/leases/cbx_abcdef123456/code/agent?ticket=code_abc" {
t.Fatalf("agent URL=%q", got)
}
if got := webCodePortalURL("https://crabbox.openclaw.ai/", "cbx_abcdef123456"); got != "https://crabbox.openclaw.ai/portal/leases/cbx_abcdef123456/code/" {
t.Fatalf("portal URL=%q", got)
}
}
func TestCodeUpstreamPathStripsPortalLeasePrefix(t *testing.T) {
tests := map[string]string{
"/portal/leases/cbx_abcdef123456/code/": "/",
"/portal/leases/cbx_abcdef123456/code/static/main.js": "/static/main.js",
"/portal/leases/cbx_abcdef123456/code/?folder=/work/repo": "/?folder=/work/repo",
"/portal/leases/blue-lobster/code/vscode-remote-resource": "/vscode-remote-resource",
"/portal/leases/blue-lobster/vnc/viewer": "/portal/leases/blue-lobster/vnc/viewer",
"/portal/leases/blue-lobster/code/proxy/3000/?q=hello+you": "/proxy/3000/?q=hello+you",
}
for input, want := range tests {
if got := codeUpstreamPath(input); got != want {
t.Fatalf("codeUpstreamPath(%q)=%q want %q", input, got, want)
}
}
}
func TestStartCodeServerCommand(t *testing.T) {
got := startCodeServerCommand("/work/crabbox/cbx_abcdef123456/repo")
for _, want := range []string{
"/usr/local/bin/code-server",
"--auth none",
"--bind-addr 127.0.0.1:8080",
"VSCODE_PROXY_URI='./proxy/{{port}}'",
"/tmp/crabbox-code-server.log",
"/tmp/crabbox-code-server.pid",
} {
if !strings.Contains(got, want) {
t.Fatalf("startCodeServerCommand missing %q:\n%s", want, got)
}
}
if strings.Contains(got, "pkill -f") {
t.Fatalf("startCodeServerCommand should not use pkill -f:\n%s", got)
}
}
func TestRewriteCodeHTMLRemovesEmptyLocaleScript(t *testing.T) {
input := []byte(`<script type="module" src=""></script><script type="module" src="workbench.js"></script>`)
got := string(rewriteCodeHTML(input))
if strings.Contains(got, `src=""`) {
t.Fatalf("rewriteCodeHTML left empty script:\n%s", got)
}
if !strings.Contains(got, `src="workbench.js"`) {
t.Fatalf("rewriteCodeHTML removed non-empty script:\n%s", got)
}
}
func TestCodeServerStaticFallbackServesVSDAStub(t *testing.T) {
body, headers, ok := codeServerStaticFallback("/stable/static/node_modules/vsda/rust/web/vsda.js", 404)
if !ok {
t.Fatal("expected vsda.js fallback")
}
if headers.Get("content-type") != "text/javascript" {
t.Fatalf("content-type=%q", headers.Get("content-type"))
}
text := string(body)
if !strings.Contains(text, "define(") || !strings.Contains(text, "globalThis.vsda_web") {
t.Fatalf("fallback body missing AMD vsda_web stub:\n%s", body)
}
wasm, headers, ok := codeServerStaticFallback("/stable/static/node_modules/vsda/rust/web/vsda_bg.wasm", 404)
if !ok {
t.Fatal("expected vsda wasm fallback")
}
if headers.Get("content-type") != "application/wasm" {
t.Fatalf("wasm content-type=%q", headers.Get("content-type"))
}
if string(wasm[:4]) != "\x00asm" {
t.Fatalf("wasm header=%v", wasm[:4])
}
if _, _, ok := codeServerStaticFallback("/missing.js", 404); ok {
t.Fatal("unexpected fallback for unrelated path")
}
if _, _, ok := codeServerStaticFallback("/stable/static/node_modules/vsda/rust/web/vsda.js", 500); ok {
t.Fatal("unexpected fallback for non-404")
}
}
func TestCodeFrameType(t *testing.T) {
if got := codeFrameType(websocket.MessageText); got != "text" {
t.Fatalf("text frame=%q", got)
}
if got := codeFrameType(websocket.MessageBinary); got != "binary" {
t.Fatalf("binary frame=%q", got)
}
if got := websocketMessageType("text"); got != websocket.MessageText {
t.Fatalf("websocketMessageType text=%v", got)
}
if got := websocketMessageType("binary"); got != websocket.MessageBinary {
t.Fatalf("websocketMessageType binary=%v", got)
}
if got := websocketMessageType(""); got != websocket.MessageBinary {
t.Fatalf("websocketMessageType default=%v", got)
}
}
func TestWebSocketSubprotocols(t *testing.T) {
headers := http.Header{}
headers.Add("Sec-WebSocket-Protocol", "vscode-remote, crabbox")
headers.Add("Sec-WebSocket-Protocol", " second-token ")
got := websocketSubprotocols(headers)
want := []string{"vscode-remote", "crabbox", "second-token"}
if strings.Join(got, "|") != strings.Join(want, "|") {
t.Fatalf("websocketSubprotocols=%q want %q", got, want)
}
}
func TestCodeWebSocketDialHeadersRewritesOrigin(t *testing.T) {
headers, subprotocols := codeWebSocketDialHeaders("http://127.0.0.1:8081", map[string]string{
"cookie": "vscode-tkn=remote-token",
"origin": "https://crabbox.openclaw.ai",
"sec-websocket-protocol": "proto-a, proto-b",
})
if headers.Get("Origin") != "http://127.0.0.1:8081" {
t.Fatalf("origin=%q", headers.Get("Origin"))
}
if headers.Get("Cookie") != "vscode-tkn=remote-token" {
t.Fatalf("cookie=%q", headers.Get("Cookie"))
}
if headers.Get("Sec-WebSocket-Protocol") != "" {
t.Fatalf("raw subprotocol header should be removed: %q", headers.Get("Sec-WebSocket-Protocol"))
}
if strings.Join(subprotocols, "|") != "proto-a|proto-b" {
t.Fatalf("subprotocols=%q", subprotocols)
}
}