test: покрытие multi.Provider и shape

multi: round-robin биекция, маршрутизация Handle/Reset, max backoff, panic.
shape: passthrough при interval=0, форвардинг WriteTo, обёртка listener.
This commit is contained in:
samosvalishe
2026-06-19 15:09:49 +03:00
parent e50febc834
commit e63c93ec41
2 changed files with 221 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
package multi
import (
"context"
"errors"
"fmt"
"testing"
"github.com/samosvalishe/free-turn-proxy/internal/provider"
)
// fakeProvider записывает streamID, с которыми его звали, и кодирует свой индекс
// в выданных Credentials - чтобы проверить и маршрут, и значение.
type fakeProvider struct {
idx int
backoff int64
gotCreds []int
gotHandle []int
gotReset []int
authResult bool
}
func (f *fakeProvider) GetCredentials(_ context.Context, streamID int) (provider.Credentials, error) {
f.gotCreds = append(f.gotCreds, streamID)
return provider.Credentials{User: fmt.Sprintf("p%d-s%d", f.idx, streamID)}, nil
}
func (f *fakeProvider) IsAuthError(error) bool { return f.authResult }
func (f *fakeProvider) HandleAuthError(s int) bool { f.gotHandle = append(f.gotHandle, s); return true }
func (f *fakeProvider) ResetErrors(s int) { f.gotReset = append(f.gotReset, s) }
func (f *fakeProvider) BackoffUntilUnix() int64 { return f.backoff }
func (f *fakeProvider) Name() string { return fmt.Sprintf("fake%d", f.idx) }
func newFakes(n int) ([]provider.Provider, []*fakeProvider) {
ps := make([]provider.Provider, n)
fs := make([]*fakeProvider, n)
for i := range ps {
f := &fakeProvider{idx: i}
ps[i], fs[i] = f, f
}
return ps, fs
}
func TestNewPanicsOnEmpty(t *testing.T) {
defer func() {
if recover() == nil {
t.Fatal("expected panic on empty providers")
}
}()
New(nil)
}
func TestGetCredentialsRoundRobin(t *testing.T) {
ps, fs := newFakes(3)
m := New(ps)
// 3 провайдера x 10 стримов = 30 глобальных streamID.
for sid := 1; sid <= 30; sid++ {
creds, err := m.GetCredentials(context.Background(), sid)
if err != nil {
t.Fatalf("streamID %d: %v", sid, err)
}
wantIdx := (sid - 1) % 3
wantInner := ((sid - 1) / 3) + 1
want := fmt.Sprintf("p%d-s%d", wantIdx, wantInner)
if creds.User != want {
t.Fatalf("streamID %d: got %q, want %q", sid, creds.User, want)
}
}
// Каждый провайдер получил ровно innerID 1..10.
for i, f := range fs {
if len(f.gotCreds) != 10 {
t.Fatalf("provider %d got %d calls, want 10", i, len(f.gotCreds))
}
for j, inner := range f.gotCreds {
if inner != j+1 {
t.Fatalf("provider %d call %d: innerID %d, want %d", i, j, inner, j+1)
}
}
}
}
func TestHandleAndResetRouting(t *testing.T) {
ps, fs := newFakes(2)
m := New(ps)
// streamID 5: idx=(5-1)%2=0, inner=(5-1)/2+1=3.
m.HandleAuthError(5)
m.ResetErrors(5)
if len(fs[0].gotHandle) != 1 || fs[0].gotHandle[0] != 3 {
t.Fatalf("HandleAuthError routed wrong: %v", fs[0].gotHandle)
}
if len(fs[0].gotReset) != 1 || fs[0].gotReset[0] != 3 {
t.Fatalf("ResetErrors routed wrong: %v", fs[0].gotReset)
}
if len(fs[1].gotHandle) != 0 {
t.Fatalf("provider 1 should not be touched: %v", fs[1].gotHandle)
}
}
func TestBackoffUntilUnixMax(t *testing.T) {
ps, fs := newFakes(3)
fs[0].backoff = 100
fs[1].backoff = 500
fs[2].backoff = 300
if got := New(ps).BackoffUntilUnix(); got != 500 {
t.Fatalf("BackoffUntilUnix = %d, want 500 (max)", got)
}
}
func TestIsAuthErrorDelegatesToFirst(t *testing.T) {
ps, fs := newFakes(2)
fs[0].authResult = true
if !New(ps).IsAuthError(errors.New("x")) {
t.Fatal("IsAuthError should delegate to providers[0]")
}
}
func TestName(t *testing.T) {
ps, _ := newFakes(4)
if got := New(ps).Name(); got != "multi(4)" {
t.Fatalf("Name = %q, want multi(4)", got)
}
}
+97
View File
@@ -0,0 +1,97 @@
package shape
import (
"net"
"testing"
"time"
dtlsnet "github.com/pion/dtls/v3/pkg/net"
)
type fakeAddr struct{}
func (fakeAddr) Network() string { return "udp" }
func (fakeAddr) String() string { return "fake" }
type fakePacketConn struct {
writes [][]byte
}
func (*fakePacketConn) ReadFrom([]byte) (int, net.Addr, error) { return 0, fakeAddr{}, nil }
func (f *fakePacketConn) WriteTo(p []byte, _ net.Addr) (int, error) {
b := make([]byte, len(p))
copy(b, p)
f.writes = append(f.writes, b)
return len(p), nil
}
func (*fakePacketConn) Close() error { return nil }
func (*fakePacketConn) LocalAddr() net.Addr { return fakeAddr{} }
func (*fakePacketConn) SetDeadline(time.Time) error { return nil }
func (*fakePacketConn) SetReadDeadline(time.Time) error { return nil }
func (*fakePacketConn) SetWriteDeadline(time.Time) error { return nil }
func TestWrapPacketConnPassthrough(t *testing.T) {
f := &fakePacketConn{}
if got := WrapPacketConn(f, 0); got != f {
t.Fatalf("interval=0 must return original conn, got %T", got)
}
}
func TestWrapPacketConnForwards(t *testing.T) {
f := &fakePacketConn{}
wrapped := WrapPacketConn(f, time.Millisecond)
if _, ok := wrapped.(*ShapedPacketConn); !ok {
t.Fatalf("interval>0 must return *ShapedPacketConn, got %T", wrapped)
}
n, err := wrapped.WriteTo([]byte("hello"), fakeAddr{})
if err != nil || n != 5 {
t.Fatalf("WriteTo = (%d, %v)", n, err)
}
if len(f.writes) != 1 || string(f.writes[0]) != "hello" {
t.Fatalf("inner writes = %v", f.writes)
}
}
func TestShaperWaitNoIntervalReturns(t *testing.T) {
done := make(chan struct{})
go func() { New(0).Wait(); close(done) }()
select {
case <-done:
case <-time.After(time.Second):
t.Fatal("Wait blocked with interval=0")
}
}
type fakeListener struct {
inner net.PacketConn
addr net.Addr
}
func (l *fakeListener) Accept() (net.PacketConn, net.Addr, error) { return l.inner, l.addr, nil }
func (*fakeListener) Close() error { return nil }
func (l *fakeListener) Addr() net.Addr { return l.addr }
func TestWrapPacketListenerPassthrough(t *testing.T) {
var l dtlsnet.PacketListener = &fakeListener{addr: fakeAddr{}}
if got := WrapPacketListener(l, 0); got != l {
t.Fatalf("interval=0 must return original listener")
}
}
func TestWrapPacketListenerWrapsAccepted(t *testing.T) {
f := &fakePacketConn{}
l := WrapPacketListener(&fakeListener{inner: f, addr: fakeAddr{}}, time.Millisecond)
pc, _, err := l.Accept()
if err != nil {
t.Fatal(err)
}
if _, ok := pc.(*ShapedPacketConn); !ok {
t.Fatalf("accepted conn must be *ShapedPacketConn, got %T", pc)
}
if _, err := pc.WriteTo([]byte("x"), fakeAddr{}); err != nil {
t.Fatal(err)
}
if len(f.writes) != 1 {
t.Fatalf("inner writes = %d, want 1", len(f.writes))
}
}