Files
fn_registry/functions/browser/cdp_wait_idle_test.go
T
egutierrez 8742cb25be feat(browser): auto-commit con 60 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 11:42:31 +02:00

123 lines
4.0 KiB
Go

package browser
import (
"strings"
"testing"
"time"
)
// TestCdpWaitIdleDefaults verifica el comportamiento observable de CdpWaitIdle
// sin requerir una instancia Chrome real.
func TestCdpWaitIdleDefaults(t *testing.T) {
t.Run("conexion nula retorna error inmediato", func(t *testing.T) {
err := CdpWaitIdle(nil, CdpWaitIdleOpts{})
if err == nil {
t.Fatal("esperaba error para conexion nula, got nil")
}
})
t.Run("opts con ceros aplica defaults antes de usar", func(t *testing.T) {
// Zero-value de CdpWaitIdleOpts debe tener todos los campos en 0
// para que la logica de defaults sea alcanzable.
var opts CdpWaitIdleOpts
if opts.QuietMs != 0 || opts.Timeout != 0 || opts.MaxInflight != 0 || opts.PollMs != 0 {
t.Fatal("zero-value de CdpWaitIdleOpts debe tener todos los campos en 0")
}
})
t.Run("mensaje de error nil-conn menciona cdp wait idle", func(t *testing.T) {
err := CdpWaitIdle(nil, CdpWaitIdleOpts{
QuietMs: 100,
Timeout: 500 * time.Millisecond,
PollMs: 50,
})
if err == nil {
t.Fatal("esperaba error, got nil")
}
if !strings.Contains(err.Error(), "cdp wait idle") {
t.Errorf("mensaje de error %q no contiene 'cdp wait idle'", err.Error())
}
})
}
// TestInflightTracker cubre el nucleo puro del contador de red. No requiere Chrome:
// alimenta secuencias de eventos {requestId, resourceType} y verifica el conteo.
func TestInflightTracker(t *testing.T) {
t.Run("golden: carga normal llega a idle", func(t *testing.T) {
tr := NewInflightTracker()
tr.OnRequest("r1", "Document")
tr.OnRequest("r2", "Script")
tr.OnRequest("r3", "Image")
if got := tr.Inflight(); got != 3 {
t.Fatalf("inflight tras 3 requests = %d, esperaba 3", got)
}
tr.OnFinish("r1")
tr.OnFinish("r2")
tr.OnFail("r3") // un recurso que falla tambien deja de estar en vuelo
if got := tr.Inflight(); got != 0 {
t.Fatalf("inflight tras completar todo = %d, esperaba 0", got)
}
if !tr.IsIdle(0) {
t.Error("esperaba IsIdle(0)=true con inflight=0")
}
})
t.Run("edge: analytics residual idle ok con MaxInflight=2", func(t *testing.T) {
tr := NewInflightTracker()
// La pagina cargo, pero 2 beacons de analytics quedan sin finalizar.
tr.OnRequest("doc", "Document")
tr.OnFinish("doc")
tr.OnRequest("beacon1", "Ping")
tr.OnRequest("beacon2", "XHR")
if got := tr.Inflight(); got != 2 {
t.Fatalf("inflight = %d, esperaba 2 (beacons residuales)", got)
}
if tr.IsIdle(0) {
t.Error("con MaxInflight=0 NO deberia ser idle (2 beacons en vuelo)")
}
if !tr.IsIdle(2) {
t.Error("con MaxInflight=2 (default) SI deberia ser idle")
}
})
t.Run("error/regresion: WebSocket abierto NO impide idle", func(t *testing.T) {
tr := NewInflightTracker()
tr.OnRequest("doc", "Document")
tr.OnFinish("doc")
// Un stream WebSocket se abre y nunca emite loadingFinished.
tr.OnRequest("ws1", "WebSocket")
// Un EventSource (SSE) tampoco termina.
tr.OnRequest("sse1", "EventSource")
if got := tr.Inflight(); got != 0 {
t.Fatalf("inflight = %d, esperaba 0 (WS/SSE excluidos)", got)
}
if !tr.IsIdle(0) {
t.Error("con WS+SSE abiertos pero excluidos, deberia ser idle absoluto")
}
})
t.Run("edge: finish de request no trackeado es no-op (no va negativo)", func(t *testing.T) {
tr := NewInflightTracker()
// loadingFinished de un requestId que nunca contamos (p.ej. el handshake
// de un WebSocket excluido) no debe romper el conteo.
tr.OnFinish("desconocido")
tr.OnFail("ws-handshake")
if got := tr.Inflight(); got != 0 {
t.Fatalf("inflight = %d, esperaba 0 (no negativo)", got)
}
tr.OnRequest("r1", "Fetch")
if got := tr.Inflight(); got != 1 {
t.Fatalf("inflight tras un request real = %d, esperaba 1", got)
}
})
t.Run("edge: requestId duplicado no infla el conteo", func(t *testing.T) {
tr := NewInflightTracker()
tr.OnRequest("r1", "Fetch")
tr.OnRequest("r1", "Fetch") // mismo id (redirect re-emite)
if got := tr.Inflight(); got != 1 {
t.Fatalf("inflight = %d, esperaba 1 (id deduplicado)", got)
}
})
}