nandi/oripublic Fork 0
55d4c9e2f7a9027b52846558166f42e50851523a
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

ws_test.go · 133 lines · 4.1 KBGo Blame HistoryRaw
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g yesterday1package bridge_test
2
3import (
4 "context"
5 "net/http/httptest"
6 "strings"
7 "testing"
8 "time"
9
10 acp "github.com/coder/acp-go-sdk"
11 "github.com/coder/websocket"
12 "github.com/coder/websocket/wsjson"
13
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday14 "rickub.com/bots-garden/ori/internal/bridge"
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g yesterday15)
16
17// dialTestServer starts an HTTP test server exposing the bridge's WebSocket
18// handler and connects a client to it.
19func dialTestServer(t *testing.T, b *bridge.Bridge) (*websocket.Conn, context.Context) {
20 t.Helper()
21 server := httptest.NewServer(b.WebSocketHandler())
22 t.Cleanup(server.Close)
23
24 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
25 t.Cleanup(cancel)
26
27 url := "ws" + strings.TrimPrefix(server.URL, "http")
28 conn, _, err := websocket.Dial(ctx, url, nil)
29 if err != nil {
30 t.Fatalf("websocket dial failed: %v", err)
31 }
32 t.Cleanup(func() { _ = conn.CloseNow() })
33 return conn, ctx
34}
35
36// readUntil reads messages until the wanted type arrives.
37func readUntil(t *testing.T, ctx context.Context, conn *websocket.Conn, wantType string) bridge.Outgoing {
38 t.Helper()
39 for {
40 var msg bridge.Outgoing
41 if err := wsjson.Read(ctx, conn, &msg); err != nil {
42 t.Fatalf("websocket read failed while waiting for %q: %v", wantType, err)
43 }
44 if msg.Type == wantType {
45 return msg
46 }
47 }
48}
49
50func TestWebSocketHelloThenPromptTurn(t *testing.T) {
51 b := bridge.New(nil)
52 b.SetSession(&fakeSession{})
53 conn, ctx := dialTestServer(t, b)
54
55 hello := readUntil(t, ctx, conn, bridge.OutgoingHello)
56 if hello.SessionId != "sess-fake" {
57 t.Errorf("hello sessionId = %q, want sess-fake", hello.SessionId)
58 }
59
60 if err := wsjson.Write(ctx, conn, bridge.Incoming{Type: bridge.IncomingPrompt, Text: "hi"}); err != nil {
61 t.Fatalf("websocket write failed: %v", err)
62 }
63 readUntil(t, ctx, conn, bridge.OutgoingTurnStarted)
64 ended := readUntil(t, ctx, conn, bridge.OutgoingTurnEnded)
65 if ended.StopReason != string(acp.StopReasonEndTurn) {
66 t.Errorf("turn_ended stopReason = %q, want end_turn", ended.StopReason)
67 }
68}
69
70func TestWebSocketReceivesSessionUpdates(t *testing.T) {
71 b := bridge.New(nil)
72 b.SetSession(&fakeSession{})
73 conn, ctx := dialTestServer(t, b)
74 readUntil(t, ctx, conn, bridge.OutgoingHello)
75
76 b.HandleSessionUpdate(context.Background(), acp.SessionNotification{
77 SessionId: "sess-fake",
78 Update: acp.UpdateAgentMessageText("streamed"),
79 })
80
81 update := readUntil(t, ctx, conn, bridge.OutgoingSessionUpdate)
82 if update.Update == nil || !strings.Contains(string(update.Update), "streamed") {
83 t.Errorf("session_update payload = %s, want it to carry the streamed text", update.Update)
84 }
85}
86
87func TestWebSocketPermissionRoundTrip(t *testing.T) {
88 b := bridge.New(nil)
89 b.SetSession(&fakeSession{})
90 conn, ctx := dialTestServer(t, b)
91 readUntil(t, ctx, conn, bridge.OutgoingHello)
92
93 done := make(chan acp.RequestPermissionResponse, 1)
94 go func() {
95 resp, _ := b.HandlePermissionRequest(context.Background(), acp.RequestPermissionRequest{
96 SessionId: "sess-fake",
97 Options: []acp.PermissionOption{{OptionId: "allow", Name: "Allow", Kind: acp.PermissionOptionKindAllowOnce}},
98 })
99 done <- resp
100 }()
101
102 request := readUntil(t, ctx, conn, bridge.OutgoingPermissionRequest)
103 if err := wsjson.Write(ctx, conn, bridge.Incoming{
104 Type: bridge.IncomingPermissionResponse,
105 RequestId: request.RequestId,
106 OptionId: "allow",
107 }); err != nil {
108 t.Fatalf("websocket write failed: %v", err)
109 }
110
111 resp := <-done
112 if resp.Outcome.Selected == nil || resp.Outcome.Selected.OptionId != "allow" {
113 t.Errorf("outcome = %+v, want selected \"allow\"", resp.Outcome)
114 }
115 readUntil(t, ctx, conn, bridge.OutgoingPermissionResolved)
116}
117
118func TestWebSocketLateJoinerGetsReplay(t *testing.T) {
119 b := bridge.New(nil)
120 b.SetSession(&fakeSession{})
121
122 b.HandleSessionUpdate(context.Background(), acp.SessionNotification{
123 SessionId: "sess-fake",
124 Update: acp.UpdateAgentMessageText("before you arrived"),
125 })
126
127 conn, ctx := dialTestServer(t, b)
128 readUntil(t, ctx, conn, bridge.OutgoingHello)
129 update := readUntil(t, ctx, conn, bridge.OutgoingSessionUpdate)
130 if !strings.Contains(string(update.Update), "before you arrived") {
131 t.Errorf("replayed update = %s, want the pre-connection chunk", update.Update)
132 }
133}