first commit
This commit is contained in:
127
transports/bifrost-http/handlers/ssestreaming_test.go
Normal file
127
transports/bifrost-http/handlers/ssestreaming_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/maximhq/bifrost/transports/bifrost-http/lib"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
// TestSSEStreamReaderNoEventBatching verifies that SSE events are delivered
|
||||
// individually through fasthttp's chunked transfer encoding, not batched
|
||||
// into larger TCP segments. This is the core regression test for the
|
||||
// fasthttputil.PipeConns batching fix.
|
||||
func TestSSEStreamReaderNoEventBatching(t *testing.T) {
|
||||
const numEvents = 20
|
||||
|
||||
// Build expected events
|
||||
events := make([]string, numEvents)
|
||||
for i := range events {
|
||||
events[i] = fmt.Sprintf("data: {\"index\":%d,\"content\":\"chunk-%d\"}\n\n", i, i)
|
||||
}
|
||||
|
||||
handler := func(ctx *fasthttp.RequestCtx) {
|
||||
ctx.SetContentType("text/event-stream")
|
||||
ctx.Response.Header.Set("Cache-Control", "no-cache")
|
||||
|
||||
reader := lib.NewSSEStreamReader()
|
||||
|
||||
go func() {
|
||||
defer reader.Done()
|
||||
for _, event := range events {
|
||||
if !reader.Send([]byte(event)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
ctx.Response.SetBodyStream(reader, -1)
|
||||
}
|
||||
|
||||
// Use net.Pipe for deterministic in-process testing
|
||||
serverConn, clientConn := net.Pipe()
|
||||
defer clientConn.Close()
|
||||
|
||||
// Run fasthttp server on one end of the pipe
|
||||
go func() {
|
||||
_ = fasthttp.ServeConn(serverConn, handler)
|
||||
}()
|
||||
|
||||
// Send HTTP request through the pipe
|
||||
_, err := clientConn.Write([]byte("GET /stream HTTP/1.1\r\nHost: test\r\n\r\n"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write request: %v", err)
|
||||
}
|
||||
|
||||
// Read response using bufio to parse chunked encoding
|
||||
br := bufio.NewReader(clientConn)
|
||||
|
||||
// Read and skip HTTP response headers
|
||||
for {
|
||||
line, err := br.ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read response header: %v", err)
|
||||
}
|
||||
if strings.TrimSpace(line) == "" {
|
||||
break // End of headers
|
||||
}
|
||||
}
|
||||
|
||||
// Read chunked transfer-encoded body.
|
||||
// Each HTTP chunk should contain exactly one SSE event.
|
||||
var receivedEvents []string
|
||||
for {
|
||||
// Read chunk size line (hex size + CRLF)
|
||||
sizeLine, err := br.ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read chunk size: %v", err)
|
||||
}
|
||||
sizeLine = strings.TrimSpace(sizeLine)
|
||||
|
||||
var chunkSize int
|
||||
_, err = fmt.Sscanf(sizeLine, "%x", &chunkSize)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse chunk size %q: %v", sizeLine, err)
|
||||
}
|
||||
|
||||
if chunkSize == 0 {
|
||||
break // Terminal chunk
|
||||
}
|
||||
|
||||
// Read exactly chunkSize bytes + trailing CRLF
|
||||
chunkData := make([]byte, chunkSize+2) // +2 for CRLF
|
||||
n := 0
|
||||
for n < len(chunkData) {
|
||||
nn, err := br.Read(chunkData[n:])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read chunk data: %v", err)
|
||||
}
|
||||
n += nn
|
||||
}
|
||||
|
||||
chunk := string(chunkData[:chunkSize])
|
||||
receivedEvents = append(receivedEvents, chunk)
|
||||
}
|
||||
|
||||
// Verify each chunk contains exactly one SSE event
|
||||
if len(receivedEvents) != numEvents {
|
||||
t.Errorf("expected %d individual chunks, got %d (events were batched)", numEvents, len(receivedEvents))
|
||||
for i, chunk := range receivedEvents {
|
||||
eventCount := strings.Count(chunk, "\n\n")
|
||||
t.Logf(" chunk %d: %d SSE events, %d bytes", i, eventCount, len(chunk))
|
||||
}
|
||||
}
|
||||
|
||||
for i, chunk := range receivedEvents {
|
||||
if i >= len(events) {
|
||||
break
|
||||
}
|
||||
if chunk != events[i] {
|
||||
t.Errorf("chunk %d: got %q, want %q", i, chunk, events[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user