Skip to content

Commit 138498d

Browse files
fix: batch E — remaining bugs + test improvements (BUG-21,27,30 + test fixes)
1 parent 6408d08 commit 138498d

6 files changed

Lines changed: 128 additions & 14 deletions

File tree

internal/client/browser.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,9 @@ func (c *Client) ClaimUserTab(tabID string) (Tab, error) {
636636
}
637637
result.normalize()
638638
// Auto-attach debugger so CDP commands work immediately
639-
_ = c.attachTab(tabIDInt)
639+
if err := c.attachTab(tabIDInt); err != nil && c.log != nil {
640+
c.log.Printf("claim tab %d: auto-attach failed: %v", tabIDInt, err)
641+
}
640642
return result, nil
641643
}
642644

internal/client/browser_rpc_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,9 +413,10 @@ func TestNavigateBackUsesHistoryEntry(t *testing.T) {
413413
}
414414

415415
var navCall *recordedCall
416-
for i, call := range rec.snapshot() {
416+
snap := rec.snapshot()
417+
for _, call := range snap {
417418
if call.method == "executeCdp" && call.params["method"] == "Page.navigateToHistoryEntry" {
418-
c := rec.snapshot()[i]
419+
c := call
419420
navCall = &c
420421
break
421422
}
@@ -631,6 +632,9 @@ func TestDomCUAClickComputesBoxCenter(t *testing.T) {
631632
}
632633
params, _ := req.Params.(map[string]interface{})
633634
switch params["method"] {
635+
case "DOM.resolveNode":
636+
// DomCUAClick resolves node before getting box model
637+
return map[string]interface{}{"object": map[string]interface{}{"objectId": "test"}}
634638
case "DOM.getBoxModel":
635639
return map[string]interface{}{
636640
"model": map[string]interface{}{

internal/client/browser_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ func TestUserTabNormalize(t *testing.T) {
3535
}
3636

3737
func TestNewUUIDFormat(t *testing.T) {
38-
u := newUUID()
38+
u, err := newUUID()
39+
if err != nil {
40+
t.Fatalf("newUUID: %v", err)
41+
}
3942
if len(u) != 36 {
4043
t.Fatalf("UUID length = %d, want 36: %q", len(u), u)
4144
}
@@ -56,7 +59,10 @@ func TestNewUUIDFormat(t *testing.T) {
5659
func TestNewUUIDUnique(t *testing.T) {
5760
seen := make(map[string]struct{}, 1000)
5861
for i := 0; i < 1000; i++ {
59-
u := newUUID()
62+
u, err := newUUID()
63+
if err != nil {
64+
t.Fatalf("newUUID iteration %d: %v", i, err)
65+
}
6066
if _, dup := seen[u]; dup {
6167
t.Fatalf("duplicate UUID after %d iterations: %s", i, u)
6268
}

internal/client/client.go

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package client
33
import (
44
"bufio"
55
"context"
6-
"crypto/rand"
6+
cryptoRand "crypto/rand"
77
"encoding/json"
88
"fmt"
99
"log"
@@ -12,6 +12,8 @@ import (
1212
"sync/atomic"
1313
"time"
1414

15+
"math/rand"
16+
1517
"github.com/DeliciousBuding/codex-browser-bridge/internal/discovery"
1618
"github.com/DeliciousBuding/codex-browser-bridge/internal/protocol"
1719
)
@@ -114,12 +116,28 @@ func Connect(pipeName string, logger *log.Logger) (*Client, error) {
114116
// that establish the transport themselves.
115117
func NewFromConn(conn net.Conn, logger *log.Logger) *Client {
116118
ctx, cancel := context.WithCancel(context.Background())
119+
120+
sessionID, err := newUUID()
121+
if err != nil {
122+
if logger != nil {
123+
logger.Printf("newUUID failed, using fallback: %v", err)
124+
}
125+
sessionID = fallbackUUID()
126+
}
127+
turnID, err := newUUID()
128+
if err != nil {
129+
if logger != nil {
130+
logger.Printf("newUUID failed, using fallback: %v", err)
131+
}
132+
turnID = fallbackUUID()
133+
}
134+
117135
c := &Client{
118136
conn: conn,
119137
reader: bufio.NewReaderSize(conn, 256*1024),
120138
session: protocol.SessionParams{
121-
SessionID: newUUID(),
122-
TurnID: newUUID(),
139+
SessionID: sessionID,
140+
TurnID: turnID,
123141
},
124142
pending: make(map[int]chan *protocol.Response),
125143
ctx: ctx,
@@ -255,10 +273,22 @@ func truncate(s string, n int) string {
255273
}
256274

257275
// newUUID generates a UUID v4 string without external dependencies.
258-
func newUUID() string {
276+
func newUUID() (string, error) {
277+
var b [16]byte
278+
if _, err := cryptoRand.Read(b[:]); err != nil {
279+
return "", fmt.Errorf("crypto/rand failed: %w", err)
280+
}
281+
b[6] = (b[6] & 0x0f) | 0x40 // version 4
282+
b[8] = (b[8] & 0x3f) | 0x80 // variant 10
283+
return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
284+
b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]), nil
285+
}
286+
287+
// fallbackUUID generates a UUID v4 using math/rand when crypto/rand is unavailable.
288+
func fallbackUUID() string {
259289
var b [16]byte
260-
if _, err := rand.Read(b[:]); err != nil {
261-
panic(fmt.Sprintf("crypto/rand failed: %v", err))
290+
for i := range b {
291+
b[i] = byte(rand.Intn(256))
262292
}
263293
b[6] = (b[6] & 0x0f) | 0x40 // version 4
264294
b[8] = (b[8] & 0x3f) | 0x80 // variant 10

internal/client/e2e_test.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package client
22

33
import (
4+
"fmt"
5+
"strings"
46
"testing"
57

68
"github.com/DeliciousBuding/codex-browser-bridge/internal/protocol"
@@ -15,8 +17,14 @@ func TestCdpWithAttachSequence(t *testing.T) {
1517
}{
1618
{"Navigate", func(c *Client) error { return c.Navigate("1", "https://test.com") }},
1719
{"Screenshot", func(c *Client) error {
18-
_, err := c.Screenshot("1", false)
19-
return err
20+
b64, err := c.Screenshot("1", false)
21+
if err != nil {
22+
return err
23+
}
24+
if b64 == "" {
25+
return fmt.Errorf("expected non-empty screenshot data")
26+
}
27+
return nil
2028
}},
2129
{"Evaluate", func(c *Client) error {
2230
_, err := c.Evaluate("1", "1+1")
@@ -30,6 +38,7 @@ func TestCdpWithAttachSequence(t *testing.T) {
3038
if req.Method == "executeCdp" {
3139
return map[string]interface{}{
3240
"result": map[string]interface{}{"value": "ok"},
41+
"data": "iVBORw0KGgo...",
3342
}
3443
}
3544
return map[string]bool{"ok": true}
@@ -99,7 +108,7 @@ func TestWaitForLoadTimeout(t *testing.T) {
99108
if err == nil {
100109
t.Fatalf("expected timeout error, got state=%q", state)
101110
}
102-
if err.Error() == "" || err.Error()[:7] != "timed o" {
111+
if err.Error() == "" || !strings.HasPrefix(err.Error(), "timed o") {
103112
t.Errorf("error = %v, should mention timeout", err)
104113
}
105114
}

internal/client/rpc_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package client
22

33
import (
44
"bufio"
5+
"bytes"
56
"encoding/json"
67
"fmt"
78
"net"
@@ -228,3 +229,65 @@ func contains(s, substr string) bool {
228229
}
229230
return false
230231
}
232+
233+
// TestSendNotificationFrame verifies that SendNotification encodes a
234+
// valid JSON-RPC notification frame: no "id" field, correct length prefix,
235+
// method and params properly encoded.
236+
func TestSendNotificationFrame(t *testing.T) {
237+
clientConn, serverConn := net.Pipe()
238+
defer clientConn.Close()
239+
defer serverConn.Close()
240+
241+
c := NewFromConn(clientConn, nil)
242+
defer c.Close()
243+
244+
serverReader := bufio.NewReader(serverConn)
245+
246+
type notifMsg struct {
247+
JSONRPC string `json:"jsonrpc"`
248+
Method string `json:"method"`
249+
Params interface{} `json:"params,omitempty"`
250+
}
251+
252+
var got notifMsg
253+
errCh := make(chan error, 1)
254+
go func() {
255+
raw, err := protocol.DecodeFrame(serverReader)
256+
if err != nil {
257+
errCh <- fmt.Errorf("decode frame: %w", err)
258+
return
259+
}
260+
// Verify no "id" field in raw JSON (JSON-RPC notification)
261+
if bytes.Contains(raw, []byte(`"id"`)) {
262+
errCh <- fmt.Errorf("notification frame contains 'id' field: %s", raw)
263+
return
264+
}
265+
if err := json.Unmarshal(raw, &got); err != nil {
266+
errCh <- fmt.Errorf("unmarshal: %w", err)
267+
return
268+
}
269+
errCh <- nil
270+
}()
271+
272+
err := c.SendNotification("test.event", map[string]string{"hello": "world"})
273+
if err != nil {
274+
t.Fatalf("SendNotification: %v", err)
275+
}
276+
277+
if err := <-errCh; err != nil {
278+
t.Fatal(err)
279+
}
280+
if got.JSONRPC != "2.0" {
281+
t.Errorf("jsonrpc = %q, want \"2.0\"", got.JSONRPC)
282+
}
283+
if got.Method != "test.event" {
284+
t.Errorf("method = %q, want \"test.event\"", got.Method)
285+
}
286+
paramsMap, ok := got.Params.(map[string]interface{})
287+
if !ok {
288+
t.Fatalf("params is %T, want map", got.Params)
289+
}
290+
if v, ok := paramsMap["hello"].(string); !ok || v != "world" {
291+
t.Errorf("params[hello] = %v, want \"world\"", paramsMap["hello"])
292+
}
293+
}

0 commit comments

Comments
 (0)