MWSE/internal/testutil/fakeconn.go

106 lines
2.7 KiB
Go

// Package testutil provides an in-memory WebSocket connection so the engine's
// concurrency tests (#26) can run without real sockets. FakeConn satisfies the
// ws.Conn interface structurally.
package testutil
import (
"io"
"sync"
"time"
)
// textMessage mirrors gorilla/websocket.TextMessage (1) without importing it, so
// this helper stays dependency-light.
const textMessage = 1
// FakeConn is a thread-safe, in-memory connection. Outbound frames are captured
// in Writes(); inbound frames can be injected with Push() and are returned by
// ReadMessage in order until the connection is closed.
type FakeConn struct {
mu sync.Mutex
writes [][]byte
incoming chan []byte
closed bool
pong func(string) error
}
// NewFakeConn returns a ready connection.
func NewFakeConn() *FakeConn {
return &FakeConn{incoming: make(chan []byte, 1024)}
}
// ReadMessage returns the next injected frame, blocking until one is available or
// the connection is closed (then io.EOF).
func (f *FakeConn) ReadMessage() (int, []byte, error) {
b, ok := <-f.incoming
if !ok {
return 0, nil, io.EOF
}
return textMessage, b, nil
}
// Push injects an inbound frame for ReadMessage to return.
func (f *FakeConn) Push(frame []byte) {
f.mu.Lock()
defer f.mu.Unlock()
if f.closed {
return
}
f.incoming <- frame
}
// WriteMessage captures an outbound frame.
func (f *FakeConn) WriteMessage(_ int, data []byte) error {
f.mu.Lock()
defer f.mu.Unlock()
if f.closed {
return io.ErrClosedPipe
}
cp := make([]byte, len(data))
copy(cp, data)
f.writes = append(f.writes, cp)
return nil
}
// Writes returns a copy of all captured outbound frames.
func (f *FakeConn) Writes() [][]byte {
f.mu.Lock()
defer f.mu.Unlock()
out := make([][]byte, len(f.writes))
copy(out, f.writes)
return out
}
// WriteCount returns how many frames have been written.
func (f *FakeConn) WriteCount() int {
f.mu.Lock()
defer f.mu.Unlock()
return len(f.writes)
}
func (f *FakeConn) WriteControl(int, []byte, time.Time) error { return nil }
func (f *FakeConn) SetReadLimit(int64) {}
func (f *FakeConn) SetReadDeadline(time.Time) error { return nil }
func (f *FakeConn) SetWriteDeadline(time.Time) error { return nil }
func (f *FakeConn) SetPongHandler(h func(string) error) { f.pong = h }
// Pong invokes the registered pong handler (used by heartbeat tests).
func (f *FakeConn) Pong(appData string) error {
if f.pong != nil {
return f.pong(appData)
}
return nil
}
// Close makes ReadMessage return EOF and rejects further writes. Idempotent.
func (f *FakeConn) Close() error {
f.mu.Lock()
defer f.mu.Unlock()
if f.closed {
return nil
}
f.closed = true
close(f.incoming)
return nil
}