mirror of
https://github.com/hackdiaz-dev/free-turn-proxy.git
synced 2026-10-12 09:41:53 +02:00
209 lines
5.8 KiB
Go
209 lines
5.8 KiB
Go
package mobile
|
||
|
||
import (
|
||
"encoding/json"
|
||
"strings"
|
||
"sync"
|
||
"testing"
|
||
|
||
"github.com/samosvalishe/free-turn-proxy/internal/session"
|
||
)
|
||
|
||
const testConfig = `{"peer":"1.2.3.4:5000","clientId":"deadbeef","vk":{"links":["https://vk.ru/call/join/CODE"]}}`
|
||
|
||
// recorder - EventSink для тестов.
|
||
type recorder struct {
|
||
mu sync.Mutex
|
||
states []string
|
||
logs []string
|
||
captcha []string
|
||
}
|
||
|
||
func (r *recorder) OnState(state string, _, _ int, _ string) {
|
||
r.mu.Lock()
|
||
defer r.mu.Unlock()
|
||
r.states = append(r.states, state)
|
||
}
|
||
|
||
func (r *recorder) OnLog(level, msg string, _ int64) {
|
||
r.mu.Lock()
|
||
defer r.mu.Unlock()
|
||
r.logs = append(r.logs, level+" "+msg)
|
||
}
|
||
|
||
func (r *recorder) OnCaptcha(url string) {
|
||
r.mu.Lock()
|
||
defer r.mu.Unlock()
|
||
r.captcha = append(r.captcha, url)
|
||
}
|
||
|
||
func TestStateConstantsMatchSessionPhases(t *testing.T) {
|
||
pairs := map[string]session.Phase{
|
||
StateIdle: session.PhaseIdle,
|
||
StateConnecting: session.PhaseConnecting,
|
||
StateConnected: session.PhaseConnected,
|
||
StateCaptcha: session.PhaseCaptcha,
|
||
StateError: session.PhaseError,
|
||
}
|
||
if len(pairs) != 5 {
|
||
t.Fatalf("State constants collide: %v", pairs)
|
||
}
|
||
for state, phase := range pairs {
|
||
if state != string(phase) {
|
||
t.Fatalf("state %q != phase %q", state, phase)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestGetStateIdleWithoutSession(t *testing.T) {
|
||
Stop()
|
||
if got := GetState(); got.State != StateIdle {
|
||
t.Fatalf("GetState().State = %q, want %q", got.State, StateIdle)
|
||
}
|
||
}
|
||
|
||
func TestDefaultConfigJSONParses(t *testing.T) {
|
||
var dto map[string]any
|
||
if err := json.Unmarshal([]byte(DefaultConfigJSON()), &dto); err != nil {
|
||
t.Fatalf("DefaultConfigJSON() is not valid JSON: %v", err)
|
||
}
|
||
// Дефолты не содержат peer и ссылок - валидацию проходить не обязаны.
|
||
if msg := ValidateConfig(DefaultConfigJSON()); msg == "" {
|
||
t.Error("ValidateConfig(defaults) = \"\", want error about peer")
|
||
}
|
||
}
|
||
|
||
func TestValidateConfig(t *testing.T) {
|
||
if msg := ValidateConfig(testConfig); msg != "" {
|
||
t.Errorf("ValidateConfig(valid) = %q, want empty", msg)
|
||
}
|
||
if msg := ValidateConfig(`{"peer":"1.2.3.4:5000","bogus":1}`); msg == "" {
|
||
t.Error("ValidateConfig(unknown field) = \"\", want error")
|
||
}
|
||
if msg := ValidateConfig("{"); msg == "" {
|
||
t.Error("ValidateConfig(malformed) = \"\", want error")
|
||
}
|
||
}
|
||
|
||
func TestConfigToArgs(t *testing.T) {
|
||
got, err := ConfigToArgs(testConfig)
|
||
if err != nil {
|
||
t.Fatalf("ConfigToArgs() error = %v", err)
|
||
}
|
||
for _, want := range []string{"-peer 1.2.3.4:5000", "-links CODE", "-client-id deadbeef"} {
|
||
if !strings.Contains(got, want) {
|
||
t.Errorf("ConfigToArgs() = %q, missing %q", got, want)
|
||
}
|
||
}
|
||
if _, err := ConfigToArgs("{"); err == nil {
|
||
t.Error("ConfigToArgs(malformed) error = nil")
|
||
}
|
||
}
|
||
|
||
func TestStartRequiresClientID(t *testing.T) {
|
||
t.Cleanup(Stop)
|
||
err := Start(`{"peer":"1.2.3.4:5000","vk":{"links":["CODE"]}}`)
|
||
if err == nil || !strings.Contains(err.Error(), "clientId") {
|
||
t.Fatalf("Start() error = %v, want clientId requirement", err)
|
||
}
|
||
}
|
||
|
||
func TestStartManualCaptchaRequiresSink(t *testing.T) {
|
||
SetEventSink(nil)
|
||
t.Cleanup(Stop)
|
||
|
||
in := `{"peer":"1.2.3.4:5000","clientId":"deadbeef","vk":{"links":["CODE"],"manualCaptcha":true}}`
|
||
err := Start(in)
|
||
if err == nil || !strings.Contains(err.Error(), "event sink") {
|
||
t.Fatalf("Start() error = %v, want event sink requirement", err)
|
||
}
|
||
}
|
||
|
||
func TestStartRejectsBadConfig(t *testing.T) {
|
||
t.Cleanup(Stop)
|
||
if err := Start("{"); err == nil {
|
||
t.Fatal("Start(malformed) error = nil")
|
||
}
|
||
if GetState().State != StateIdle {
|
||
t.Errorf("state after failed Start = %q", GetState().State)
|
||
}
|
||
}
|
||
|
||
func TestSinkReceivesStateAndLogs(t *testing.T) {
|
||
rec := &recorder{}
|
||
SetEventSink(rec)
|
||
t.Cleanup(func() {
|
||
Stop()
|
||
SetEventSink(nil)
|
||
})
|
||
|
||
if err := Start(testConfig); err != nil {
|
||
t.Fatalf("Start() error = %v", err)
|
||
}
|
||
Stop()
|
||
|
||
rec.mu.Lock()
|
||
defer rec.mu.Unlock()
|
||
if len(rec.states) == 0 || rec.states[0] != StateConnecting {
|
||
t.Fatalf("states = %v, want first %q", rec.states, StateConnecting)
|
||
}
|
||
if len(rec.logs) == 0 {
|
||
t.Error("sink received no log lines")
|
||
}
|
||
}
|
||
|
||
func TestDoubleStartRejected(t *testing.T) {
|
||
t.Cleanup(Stop)
|
||
if err := Start(testConfig); err != nil {
|
||
t.Fatalf("Start() error = %v", err)
|
||
}
|
||
if err := Start(testConfig); err == nil || !strings.Contains(err.Error(), "already running") {
|
||
t.Fatalf("second Start() error = %v, want already running", err)
|
||
}
|
||
}
|
||
|
||
func TestStopIsIdempotent(t *testing.T) {
|
||
if err := Start(testConfig); err != nil {
|
||
t.Fatalf("Start() error = %v", err)
|
||
}
|
||
Stop()
|
||
Stop()
|
||
if got := GetState().State; got != StateIdle {
|
||
t.Fatalf("GetState().State = %q, want %q", got, StateIdle)
|
||
}
|
||
}
|
||
|
||
func TestRestartReplacesSession(t *testing.T) {
|
||
t.Cleanup(Stop)
|
||
if err := Start(testConfig); err != nil {
|
||
t.Fatalf("Start() error = %v", err)
|
||
}
|
||
if err := Restart(testConfig, 0); err != nil {
|
||
t.Fatalf("Restart() error = %v", err)
|
||
}
|
||
if got := GetState().State; got == StateIdle {
|
||
t.Errorf("GetState().State = %q, want running session", got)
|
||
}
|
||
}
|
||
|
||
// tcp-режим не требует tun: приложение зовёт Start, а не StartTunnel.
|
||
func TestTCPModeConfig(t *testing.T) {
|
||
const tcpConfig = `{"peer":"1.2.3.4:5000","clientId":"deadbeef","proxy":{"mode":"tcp"},"vk":{"links":["https://vk.ru/call/join/CODE"]}}`
|
||
if msg := ValidateConfig(tcpConfig); msg != "" {
|
||
t.Errorf("ValidateConfig(tcp) = %q, want empty", msg)
|
||
}
|
||
args, err := ConfigToArgs(tcpConfig)
|
||
if err != nil {
|
||
t.Fatalf("ConfigToArgs() error = %v", err)
|
||
}
|
||
if !strings.Contains(args, "-mode tcp") {
|
||
t.Errorf("ConfigToArgs() = %q, missing -mode tcp", args)
|
||
}
|
||
|
||
tcpWithTunnel := `{"peer":"1.2.3.4:5000","clientId":"deadbeef","proxy":{"mode":"tcp"},` +
|
||
`"tunnel":{"mode":"wg","config":"[Interface]","mtu":1280},"vk":{"links":["https://vk.ru/call/join/CODE"]}}`
|
||
if msg := ValidateConfig(tcpWithTunnel); msg == "" {
|
||
t.Error("ValidateConfig(tcp+wg) = \"\", want error")
|
||
}
|
||
}
|