mirror of
https://github.com/hackdiaz-dev/free-turn-proxy.git
synced 2026-10-12 09:41:53 +02:00
test: покрытие multi.Provider и shape
multi: round-robin биекция, маршрутизация Handle/Reset, max backoff, panic. shape: passthrough при interval=0, форвардинг WriteTo, обёртка listener.
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user