first commit
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"browser.local/run/protocol"
|
||||
)
|
||||
|
||||
func TestNewPlatformClientNormalizesBaseURL(t *testing.T) {
|
||||
client, err := NewPlatformClient("http://platform.test/")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if client.BaseURL() != "http://platform.test" {
|
||||
t.Fatalf("expected normalized base URL, got %q", client.BaseURL())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPlatformClientRequiresAbsoluteURL(t *testing.T) {
|
||||
if _, err := NewPlatformClient("platform.local"); err == nil {
|
||||
t.Fatal("expected error for URL without scheme and host")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlatformClientHelloPostsJSONAndDecodesResponse(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost || r.URL.Path != "/api/v1/run/control/hello" {
|
||||
t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
if contentType := r.Header.Get("Content-Type"); contentType != "application/json" {
|
||||
t.Fatalf("expected JSON content type, got %q", contentType)
|
||||
}
|
||||
var request protocol.RunHelloRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
t.Fatalf("decode hello request: %v", err)
|
||||
}
|
||||
if request.RunEndpointID != "run-local" || request.CapabilityReport.Fingerprint != "cap-v1" {
|
||||
t.Fatalf("unexpected hello payload: %+v", request)
|
||||
}
|
||||
writeTestJSON(t, w, protocol.RunHelloResponse{
|
||||
Accepted: true,
|
||||
RunEndpointID: "run-local",
|
||||
SessionToken: "session-token",
|
||||
ServerTime: time.Date(2026, 7, 3, 12, 0, 0, 0, time.UTC),
|
||||
HeartbeatIntervalSeconds: 15,
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewPlatformClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("new client: %v", err)
|
||||
}
|
||||
response, err := client.Hello(context.Background(), validRunHelloRequest())
|
||||
if err != nil {
|
||||
t.Fatalf("hello: %v", err)
|
||||
}
|
||||
if !response.Accepted || response.SessionToken != "session-token" || response.HeartbeatIntervalSeconds != 15 {
|
||||
t.Fatalf("unexpected hello response: %+v", response)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlatformClientHeartbeatPostsJSONAndDecodesResponse(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost || r.URL.Path != "/api/v1/run/control/heartbeat" {
|
||||
t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
var request protocol.RunHeartbeatRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
t.Fatalf("decode heartbeat request: %v", err)
|
||||
}
|
||||
if request.SessionToken != "session-token" || request.Capacity.RunningJobs != 1 {
|
||||
t.Fatalf("unexpected heartbeat payload: %+v", request)
|
||||
}
|
||||
writeTestJSON(t, w, protocol.RunHeartbeatResponse{
|
||||
Accepted: true,
|
||||
RunEndpointID: "run-local",
|
||||
NextHeartbeatSeconds: 15,
|
||||
RefreshCapabilities: true,
|
||||
ServerTime: time.Date(2026, 7, 3, 12, 0, 0, 0, time.UTC),
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewPlatformClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("new client: %v", err)
|
||||
}
|
||||
response, err := client.Heartbeat(context.Background(), validRunHeartbeatRequest("session-token"))
|
||||
if err != nil {
|
||||
t.Fatalf("heartbeat: %v", err)
|
||||
}
|
||||
if !response.Accepted || !response.RefreshCapabilities || response.NextHeartbeatSeconds != 15 {
|
||||
t.Fatalf("unexpected heartbeat response: %+v", response)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlatformClientReturnsErrorForPlatformFailure(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = w.Write([]byte(`{"code":"validation_failed"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewPlatformClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("new client: %v", err)
|
||||
}
|
||||
if _, err := client.Hello(context.Background(), validRunHelloRequest()); err == nil {
|
||||
t.Fatal("expected platform error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlatformClientHelloThenHeartbeatFlow(t *testing.T) {
|
||||
var activeSessionToken string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/v1/run/control/hello":
|
||||
var request protocol.RunHelloRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
t.Fatalf("decode hello request: %v", err)
|
||||
}
|
||||
if request.RegistrationToken == "" || request.RunEndpointID != "run-local" {
|
||||
t.Fatalf("unexpected hello request: %+v", request)
|
||||
}
|
||||
activeSessionToken = "session-token"
|
||||
writeTestJSON(t, w, protocol.RunHelloResponse{
|
||||
Accepted: true,
|
||||
RunEndpointID: request.RunEndpointID,
|
||||
SessionToken: activeSessionToken,
|
||||
ServerTime: time.Date(2026, 7, 3, 12, 0, 0, 0, time.UTC),
|
||||
HeartbeatIntervalSeconds: 15,
|
||||
})
|
||||
case "/api/v1/run/control/heartbeat":
|
||||
var request protocol.RunHeartbeatRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
t.Fatalf("decode heartbeat request: %v", err)
|
||||
}
|
||||
if request.SessionToken != activeSessionToken {
|
||||
t.Fatalf("heartbeat did not use active session token: %+v", request)
|
||||
}
|
||||
writeTestJSON(t, w, protocol.RunHeartbeatResponse{
|
||||
Accepted: true,
|
||||
RunEndpointID: request.RunEndpointID,
|
||||
NextHeartbeatSeconds: 15,
|
||||
ServerTime: time.Date(2026, 7, 3, 12, 0, 0, 0, time.UTC),
|
||||
})
|
||||
default:
|
||||
t.Fatalf("unexpected request path %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewPlatformClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("new client: %v", err)
|
||||
}
|
||||
hello, err := client.Hello(context.Background(), validRunHelloRequest())
|
||||
if err != nil {
|
||||
t.Fatalf("hello: %v", err)
|
||||
}
|
||||
heartbeat, err := client.Heartbeat(context.Background(), validRunHeartbeatRequest(hello.SessionToken))
|
||||
if err != nil {
|
||||
t.Fatalf("heartbeat: %v", err)
|
||||
}
|
||||
if !hello.Accepted || !heartbeat.Accepted {
|
||||
t.Fatalf("expected accepted hello and heartbeat, got %+v %+v", hello, heartbeat)
|
||||
}
|
||||
}
|
||||
|
||||
func writeTestJSON(t *testing.T, w http.ResponseWriter, value any) {
|
||||
t.Helper()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(value); err != nil {
|
||||
t.Fatalf("encode response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func validRunHelloRequest() protocol.RunHelloRequest {
|
||||
return protocol.RunHelloRequest{
|
||||
RegistrationToken: "registration-token",
|
||||
RunEndpointID: "run-local",
|
||||
DisplayName: "Local Run",
|
||||
Version: "0.1.0",
|
||||
Status: "online",
|
||||
Platform: "darwin/arm64",
|
||||
CapabilityReport: protocol.RunCapabilityReport{
|
||||
Capabilities: []string{"control.hello", "control.heartbeat"},
|
||||
Fingerprint: "cap-v1",
|
||||
},
|
||||
Capacity: protocol.RunCapacityReport{MaxJobs: 4},
|
||||
}
|
||||
}
|
||||
|
||||
func validRunHeartbeatRequest(sessionToken string) protocol.RunHeartbeatRequest {
|
||||
return protocol.RunHeartbeatRequest{
|
||||
RunEndpointID: "run-local",
|
||||
SessionToken: sessionToken,
|
||||
Version: "0.1.0",
|
||||
Status: "online",
|
||||
CapabilityFingerprint: "cap-v1",
|
||||
Capacity: protocol.RunCapacityReport{
|
||||
MaxJobs: 4,
|
||||
RunningJobs: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user