2026-07-25 15:53:31 +08:00
package browser
import (
"context"
"errors"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strconv"
2026-07-27 10:01:07 +08:00
"strings"
2026-07-25 15:53:31 +08:00
"testing"
"chub/internal/domain"
)
func TestCDPInspectorValidatesEndpointFromProfileFile ( t * testing . T ) {
server := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
if r . URL . Path != "/json/version" {
t . Fatalf ( "path = %q" , r . URL . Path )
}
_ , _ = w . Write ([] byte ( `{"Browser":"Chrome/136.0.0.0"}` ))
}))
defer server . Close ()
parsed , err := url . Parse ( server . URL )
if err != nil {
t . Fatal ( err )
}
_ , portText , err := net . SplitHostPort ( parsed . Host )
if err != nil {
t . Fatal ( err )
}
profile := t . TempDir ()
if err := os . WriteFile ( filepath . Join ( profile , "DevToolsActivePort" ), [] byte ( portText + "\n/devtools/browser/test\n" ), 0 o600 ); err != nil {
t . Fatal ( err )
}
endpoint , err := NewCDPInspector (). InspectRemoteDebugEndpoint ( context . Background (), domain . BrowserChrome , profile )
if err != nil {
t . Fatalf ( "InspectRemoteDebugEndpoint() error = %v" , err )
}
port , _ := strconv . Atoi ( portText )
if endpoint . Port != port || endpoint . Browser != "Chrome/136.0.0.0" {
t . Fatalf ( "endpoint = %#v" , endpoint )
}
direct , err := NewCDPInspector (). InspectRemoteDebugPort ( context . Background (), domain . BrowserChrome , port )
if err != nil || direct . Port != port {
t . Fatalf ( "InspectRemoteDebugPort() = %#v, %v" , direct , err )
}
}
func TestCDPInspectorRejectsMismatchedAndMalformedEndpoints ( t * testing . T ) {
profile := t . TempDir ()
if err := os . WriteFile ( filepath . Join ( profile , "DevToolsActivePort" ), [] byte ( "not-a-port\n/devtools/browser/test\n" ), 0 o600 ); err != nil {
t . Fatal ( err )
}
if _ , err := NewCDPInspector (). InspectRemoteDebugEndpoint ( context . Background (), domain . BrowserChrome , profile ); ! errors . Is ( err , ErrRemoteDebugEndpointUnavailable ) {
t . Fatalf ( "malformed endpoint error = %v" , err )
}
server := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , _ * http . Request ) {
_ , _ = w . Write ([] byte ( `{"Browser":"Edg/136.0.0.0"}` ))
}))
defer server . Close ()
parsed , err := url . Parse ( server . URL )
if err != nil {
t . Fatal ( err )
}
_ , portText , err := net . SplitHostPort ( parsed . Host )
if err != nil {
t . Fatal ( err )
}
if err := os . WriteFile ( filepath . Join ( profile , "DevToolsActivePort" ), [] byte ( portText + "\n/devtools/browser/test\n" ), 0 o600 ); err != nil {
t . Fatal ( err )
}
if _ , err := NewCDPInspector (). InspectRemoteDebugEndpoint ( context . Background (), domain . BrowserChrome , profile ); ! errors . Is ( err , ErrRemoteDebugEndpointUnavailable ) {
t . Fatalf ( "mismatched endpoint error = %v" , err )
}
}
func TestCDPInspectorFindsNextAvailablePort ( t * testing . T ) {
listener , err := net . Listen ( "tcp4" , "127.0.0.1:0" )
if err != nil {
t . Fatal ( err )
}
defer listener . Close ()
start := listener . Addr ().( * net . TCPAddr ). Port
port , err := NewCDPInspector (). FindAvailableRemoteDebugPort ( context . Background (), start )
if err != nil {
t . Fatal ( err )
}
if port != start + 1 {
t . Fatalf ( "port = %d, want %d" , port , start + 1 )
}
}
2026-07-27 10:01:07 +08:00
func TestCDPInspectorListsVerifiedTargetsWithoutControlEndpoints ( t * testing . T ) {
server := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
switch r . URL . Path {
case "/json/version" :
_ , _ = w . Write ([] byte ( `{"Browser":"Chrome/136.0.0.0"}` ))
case "/json/list" :
_ , _ = w . Write ([] byte ( `[
{"id":"page-1","type":"page","title":"运营后台","url":"https://user:secret@example.com/orders?token=hidden#section","webSocketDebuggerUrl":"ws://127.0.0.1/devtools/page/page-1"},
{"id":"worker-1","type":"service_worker","title":"","url":"chrome-extension://example/worker.js?key=hidden"}
]` ))
default :
t . Fatalf ( "path = %q" , r . URL . Path )
}
}))
defer server . Close ()
parsed , err := url . Parse ( server . URL )
if err != nil {
t . Fatal ( err )
}
_ , portText , err := net . SplitHostPort ( parsed . Host )
if err != nil {
t . Fatal ( err )
}
port , _ := strconv . Atoi ( portText )
targets , err := NewCDPInspector (). ListRemoteDebugTargets ( context . Background (), domain . BrowserChrome , port )
if err != nil || len ( targets ) != 2 {
t . Fatalf ( "ListRemoteDebugTargets() = %#v, %v" , targets , err )
}
if targets [ 0 ]. ID != "page-1" || targets [ 0 ]. Type != "page" || targets [ 0 ]. Title != "运营后台" || targets [ 0 ]. URL != "https://example.com/orders" {
t . Fatalf ( "first target = %#v" , targets [ 0 ])
}
if targets [ 1 ]. URL != "chrome-extension://example/worker.js" {
t . Fatalf ( "second target URL = %q" , targets [ 1 ]. URL )
}
}
func TestCDPInspectorRejectsMalformedOrOversizedTargetList ( t * testing . T ) {
for _ , test := range [] struct {
name string
status int
response string
}{
{ name : "malformed JSON" , status : http . StatusOK , response : `not-json` },
{ name : "too many targets" , status : http . StatusOK , response : `[` + strings . Repeat ( `{"id":"page","type":"page"},` , remoteDebugTargetsMaxCount ) + `{"id":"last","type":"page"}]` },
{ name : "non-success status" , status : http . StatusServiceUnavailable , response : `temporarily unavailable` },
} {
t . Run ( test . name , func ( t * testing . T ) {
server := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
if r . URL . Path == "/json/version" {
_ , _ = w . Write ([] byte ( `{"Browser":"Chrome/136.0.0.0"}` ))
return
}
w . WriteHeader ( test . status )
_ , _ = w . Write ([] byte ( test . response ))
}))
defer server . Close ()
parsed , err := url . Parse ( server . URL )
if err != nil {
t . Fatal ( err )
}
_ , portText , err := net . SplitHostPort ( parsed . Host )
if err != nil {
t . Fatal ( err )
}
port , _ := strconv . Atoi ( portText )
if _ , err := NewCDPInspector (). ListRemoteDebugTargets ( context . Background (), domain . BrowserChrome , port ); ! errors . Is ( err , ErrRemoteDebugEndpointUnavailable ) {
t . Fatalf ( "ListRemoteDebugTargets() error = %v" , err )
}
})
}
}