package api_test import ( "context" "encoding/json" "fmt" "net/http/httptest" "strings" "testing" "time" "github.com/coder/websocket" "github.com/gin-gonic/gin" ) // TestBrowserWSOnly verifies the Web-only realtime contract. Agent/device WSS // is intentionally not part of the V1 router. func TestBrowserWSOnly(t *testing.T) { resetTestDB() access, _ := register(t, "browser-ws@test.com") srv := httptest.NewServer(testRouter) defer srv.Close() wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") + "/ws/browser?token=" + access conn, _, err := websocket.Dial(context.Background(), wsURL, nil) if err != nil { t.Fatalf("browser websocket dial failed: %v", err) } defer conn.Close(websocket.StatusNormalClosure, "done") code, env := doReq(t, "POST", "/api/v1/accounts", access, gin.H{"platform": "douyin", "remark": "browser ws"}) if code != 200 || env.Code != 0 { t.Fatalf("create account failed: %d %s", code, env.Message) } var account struct { ID uint64 `json:"id"` } if err := json.Unmarshal(env.Data, &account); err != nil || account.ID == 0 { t.Fatalf("account response missing id: %s", env.Data) } code, env = doReq(t, "POST", fmt.Sprintf("/api/v1/accounts/%d/bind", account.ID), access, nil) if code != 200 || env.Code != 0 { t.Fatalf("bind failed: %d %s", code, env.Message) } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() _, raw, err := conn.Read(ctx) if err != nil { t.Fatalf("browser websocket did not receive challenge event: %v", err) } var event struct { Type string `json:"type"` } if err := json.Unmarshal(raw, &event); err != nil { t.Fatal(err) } if event.Type != "challenge.status" && event.Type != "notification.new" { t.Fatalf("unexpected browser event type: %s", event.Type) } code, _ = doReq(t, "GET", "/api/v1/agent/devices", access, nil) if code != 404 { t.Fatalf("agent route must be absent in Web-only mode, got %d", code) } }