Files
olcrtc/mobile/mobile_test.go
zarazaex69 dc1fe0f19c refactor: replace -carrier with -auth/-engine/-url/-token (stage E)
Break CLI backwards compatibility as planned for refactor/universal-carrier:

- Drop -carrier flag; add -auth (auth provider name), -engine (engine
  name for -auth none), -url and -token (SFU endpoint + access token for
  direct/none auth mode).
- session.Config.Carrier → Auth + Engine + URL + Token.
- session.Gen() is now generic: auth.Get(cfg.Auth).(auth.RoomCreator)
  replaces the hard-coded switch on carrier names.
- Register a "none" carrier in builtin (registerDirect) that bypasses
  auth and connects directly to any engine with caller-supplied URL+Token.
- auth/telemost.Provider.Issue now accepts a raw room-ID hash in addition
  to a full https://telemost.yandex.ru/j/<id> URL.
- Plumb Engine/URL/Token from session.Config through server.Run,
  client.Run/RunWithReady, bringUpLink, link.Config, transport.Config, and
  carrier.Config so the "none" carrier has access to them end-to-end.
- Update all tests and mobile.go call sites.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 13:31:07 +03:00

430 lines
10 KiB
Go

package mobile
import (
"context"
"errors"
"log"
"strings"
"sync"
"testing"
"time"
"github.com/openlibrecommunity/olcrtc/internal/logger"
"github.com/openlibrecommunity/olcrtc/internal/protect"
)
type testProtector struct {
called int
}
func (p *testProtector) Protect(fd int) bool {
p.called = fd
return true
}
type testLogWriter struct {
got string
}
func (w *testLogWriter) WriteLog(msg string) {
w.got += msg
}
func resetMobileGlobals(t *testing.T) {
t.Helper()
mu.Lock()
if cancel != nil {
cancel()
}
cancel = nil
done = nil
ready = nil
errRun = nil
runClientWithReady = clientRunWithReady
defaults = mobileConfig{}
defaultsSet = sync.Once{}
mu.Unlock()
protect.Protector = nil
logger.SetVerbose(false)
}
var clientRunWithReady = runClientWithReady //nolint:gochecknoglobals // package-level state intentional
var (
errMobileCheckFailed = errors.New("check failed")
errMobileRunFailed = errors.New("run failed")
)
func TestProtectorAndLogging(t *testing.T) {
resetMobileGlobals(t)
p := &testProtector{}
SetProtector(p)
if protect.Protector == nil || !protect.Protector(123) || p.called != 123 {
t.Fatal("SetProtector() did not install adapter")
}
SetProtector(nil)
if protect.Protector != nil {
t.Fatal("SetProtector(nil) did not clear protector")
}
w := &testLogWriter{}
SetLogWriter(w)
log.Print("hello")
if !strings.Contains(w.got, "hello") {
t.Fatalf("log writer got %q, want hello", w.got)
}
}
func TestDefaultsAndSetters(t *testing.T) {
resetMobileGlobals(t)
SetTransport("dc")
SetLink("direct")
SetDNS("9.9.9.9:53")
SetVP8Options(-1, 999)
mu.Lock()
got := defaults
mu.Unlock()
if got.transport != dataTransport || got.link != defaultLink || got.dnsServer != "9.9.9.9:53" ||
got.vp8FPS != 1 || got.vp8BatchSize != 64 {
t.Fatalf("defaults = %+v", got)
}
SetDebug(true)
if !logger.IsVerbose() {
t.Fatal("SetDebug(true) did not enable verbose")
}
SetDebug(false)
if logger.IsVerbose() {
t.Fatal("SetDebug(false) did not disable verbose")
}
}
//nolint:cyclop // table-driven test naturally has many branches
func TestNormalizeBuildRoomAndClamp(t *testing.T) {
tests := map[string]string{
"datachannel": dataTransport,
"data": dataTransport,
"dc": dataTransport,
"vp8channel": defaultTransport,
"vp8": defaultTransport,
"bad": defaultTransport,
}
for in, want := range tests {
if got := normalizeTransport(in); got != want {
t.Fatalf("normalizeTransport(%q) = %q, want %q", in, got, want)
}
}
if normalizeCarrier(carrierWBStream) != carrierWBStream || normalizeCarrier("jazz") != "jazz" {
t.Fatal("normalizeCarrier() returned unexpected value")
}
if got := buildRoomURL("telemost", "abc"); got != "https://telemost.yandex.ru/j/abc" {
t.Fatalf("telemost room URL = %q", got)
}
if got := buildRoomURL("jazz", ""); got != "any" {
t.Fatalf("jazz empty room URL = %q", got)
}
if got := buildRoomURL(carrierWBStream, "room"); got != "room" {
t.Fatalf("wbstream room URL = %q", got)
}
if clampAtLeastOne(0, 10) != 1 || clampAtLeastOne(11, 10) != 10 || clampAtLeastOne(5, 10) != 5 {
t.Fatal("clampAtLeastOne() returned unexpected value")
}
}
func TestStartValidation(t *testing.T) {
resetMobileGlobals(t)
if err := startWithConfig("", dataTransport, "room", "client", "key", 1080, "", "", mobileConfig{}); !errors.Is(err, errCarrierRequired) { //nolint:lll // long test description
t.Fatalf("startWithConfig(missing carrier) = %v", err)
}
if err := startWithConfig("telemost", dataTransport, "", "client", "key", 1080, "", "", mobileConfig{}); !errors.Is(err, errRoomIDRequired) { //nolint:lll // long test description
t.Fatalf("startWithConfig(missing room) = %v", err)
}
if err := startWithConfig("jazz", dataTransport, "", "", "key", 1080, "", "", mobileConfig{}); !errors.Is(err, errClientIDRequired) { //nolint:lll // long test description
t.Fatalf("startWithConfig(missing client) = %v", err)
}
if err := startWithConfig("jazz", dataTransport, "", "client", "", 1080, "", "", mobileConfig{}); !errors.Is(err, errKeyHexRequired) { //nolint:lll // long test description
t.Fatalf("startWithConfig(missing key) = %v", err)
}
mu.Lock()
cancel = func() {}
mu.Unlock()
if err := startWithConfig("jazz", dataTransport, "", "client", "key", 1080, "", "", mobileConfig{}); !errors.Is(err, errAlreadyRunning) { //nolint:lll // long test description
t.Fatalf("startWithConfig(running) = %v", err)
}
resetMobileGlobals(t)
}
//nolint:cyclop // table-driven test naturally has many branches
func TestStartWithInjectedRunnerLifecycle(t *testing.T) {
resetMobileGlobals(t)
t.Cleanup(func() {
resetMobileGlobals(t)
})
runClientWithReady = func(
ctx context.Context,
linkName, transportName, carrierName, roomURL, _, clientID string,
localAddr string,
dnsServer, _, _ string,
onReady func(),
_ int,
_ int,
_ int,
_ string,
_ string,
_ int,
_ string,
_ string,
_ int,
_ int,
vp8FPS int,
vp8BatchSize int,
_ int,
_ int,
_ int,
_ int,
_, _, _ string,
) error {
if linkName != defaultLink || transportName != dataTransport || carrierName != carrierJazz ||
roomURL != "any" || clientID != "client" || localAddr != "127.0.0.1:1080" ||
dnsServer != defaultDNSServer || vp8FPS != 60 || vp8BatchSize != 8 {
t.Fatalf("RunWithReady args mismatch: link=%q transport=%q carrier=%q room=%q client=%q local=%q dns=%q vp8=%d/%d",
linkName, transportName, carrierName, roomURL, clientID, localAddr, dnsServer, vp8FPS, vp8BatchSize)
}
onReady()
<-ctx.Done()
return ctx.Err()
}
if err := StartWithTransport(carrierJazz, "dc", "", "client", "key", 1080, "", ""); err != nil {
t.Fatalf("StartWithTransport() error = %v", err)
}
if !IsRunning() {
t.Fatal("IsRunning() = false, want true")
}
if err := WaitReady(100); err != nil {
t.Fatalf("WaitReady() error = %v", err)
}
Stop()
if IsRunning() {
t.Fatal("IsRunning() = true after Stop")
}
}
//nolint:cyclop // table-driven test naturally has many branches
func TestStartUsesDefaultsAndCheckWithInjectedRunner(t *testing.T) {
resetMobileGlobals(t)
t.Cleanup(func() {
resetMobileGlobals(t)
})
runClientWithReady = func(
ctx context.Context,
_, transportName, _, roomURL, _, _ string,
localAddr string,
_, socksUser, socksPass string,
onReady func(),
_ int,
_ int,
_ int,
_ string,
_ string,
_ int,
_ string,
_ string,
_ int,
_ int,
_ int,
_ int,
_ int,
_ int,
_ int,
_ int,
_, _, _ string,
) error {
if transportName != defaultTransport || roomURL != "https://telemost.yandex.ru/j/room" ||
localAddr != "127.0.0.1:1081" || socksUser != "u" || socksPass != "p" {
t.Fatalf("Start args mismatch: transport=%q room=%q local=%q user/pass=%q/%q",
transportName, roomURL, localAddr, socksUser, socksPass)
}
onReady()
<-ctx.Done()
return ctx.Err()
}
if err := Start("telemost", "room", "client", "key", 1081, "u", "p"); err != nil {
t.Fatalf("Start() error = %v", err)
}
if err := WaitReady(100); err != nil {
t.Fatalf("WaitReady() error = %v", err)
}
Stop()
runClientWithReady = func(
ctx context.Context,
_, transportName, _, _, _, _ string,
_ string,
_, _, _ string,
onReady func(),
_ int,
_ int,
_ int,
_ string,
_ string,
_ int,
_ string,
_ string,
_ int,
_ int,
vp8FPS int,
vp8BatchSize int,
_ int,
_ int,
_ int,
_ int,
_, _, _ string,
) error {
if transportName != dataTransport || vp8FPS != 1 || vp8BatchSize != 64 {
t.Fatalf("Check args mismatch: transport=%q vp8=%d/%d", transportName, vp8FPS, vp8BatchSize)
}
onReady()
<-ctx.Done()
return nil
}
elapsed, err := Check("jazz", "dc", "", "client", "key", 1082, 100, -1, 999)
if err != nil {
t.Fatalf("Check() error = %v", err)
}
if elapsed < 0 {
t.Fatalf("Check() elapsed = %d", elapsed)
}
}
func TestCheckTimeoutAndRunError(t *testing.T) {
resetMobileGlobals(t)
t.Cleanup(func() {
resetMobileGlobals(t)
})
runClientWithReady = func(
ctx context.Context,
_, _, _, _, _, _ string,
_ string,
_, _, _ string,
_ func(),
_ int,
_ int,
_ int,
_ string,
_ string,
_ int,
_ string,
_ string,
_ int,
_ int,
_ int,
_ int,
_ int,
_ int,
_ int,
_ int,
_, _, _ string,
) error {
<-ctx.Done()
return nil
}
if _, err := Check("telemost", defaultTransport, "room", "client", "key", 1083, 1, 30, 1); !errors.Is(err, errStartTimedOut) { //nolint:lll // long test description
t.Fatalf("Check(timeout) error = %v, want %v", err, errStartTimedOut)
}
want := errMobileCheckFailed
runClientWithReady = func(
context.Context,
string, string, string, string, string, string,
string,
string, string, string,
func(),
int, int, int,
string,
string,
int,
string,
string,
int,
int,
int,
int,
int,
int,
int,
int,
string, string, string,
) error {
return want
}
if _, err := Check("telemost", defaultTransport, "room", "client", "key", 1084, 100, 30, 1); !errors.Is(err, want) {
t.Fatalf("Check(run error) = %v, want %v", err, want)
}
}
func TestWaitReadyStatesAndStop(t *testing.T) {
resetMobileGlobals(t)
if err := WaitReady(1); !errors.Is(err, errNotRunning) {
t.Fatalf("WaitReady(not running) = %v", err)
}
mu.Lock()
errRun = errMobileRunFailed
mu.Unlock()
if err := WaitReady(1); err == nil || err.Error() != "run failed" {
t.Fatalf("WaitReady(run err) = %v", err)
}
mu.Lock()
errRun = nil
ready = make(chan struct{})
done = make(chan struct{})
cancel = func() {}
mu.Unlock()
if err := WaitReady(1); !errors.Is(err, errStartTimedOut) {
t.Fatalf("WaitReady(timeout) = %v", err)
}
mu.Lock()
close(ready)
mu.Unlock()
if err := WaitReady(1); err != nil {
t.Fatalf("WaitReady(ready) error = %v", err)
}
mu.Lock()
cancel = func() {}
done = make(chan struct{})
doneCh := done
mu.Unlock()
go func() {
time.Sleep(time.Millisecond)
close(doneCh)
}()
Stop()
mu.Lock()
cancel = nil
mu.Unlock()
}
func TestLogBridge(t *testing.T) {
w := &testLogWriter{}
n, err := (&logBridge{w: w}).Write([]byte("abc"))
if err != nil || n != 3 || w.got != "abc" {
t.Fatalf("logBridge.Write() = (%d, %v), got %q", n, err, w.got)
}
}