mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
test: backport remaining test files from v9.2.x-stable
Import 18 network packet test files and 5 server infrastructure test files, adapted for main branch APIs: fix config import alias (_config), remove non-existent DevMode field, use global handlerTable instead of per-server handlers map, and correct validateToken mock expectations to include both token and tokenID arguments. Adds go-sqlmock dependency for database mocking in signserver tests.
This commit is contained in:
229
server/channelserver/handlers_register_test.go
Normal file
229
server/channelserver/handlers_register_test.go
Normal file
@@ -0,0 +1,229 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
// createMockServerWithRaviente creates a mock server with raviente and semaphore
|
||||
// initialized, which the base createMockServer() does not do.
|
||||
func createMockServerWithRaviente() *Server {
|
||||
s := createMockServer()
|
||||
s.raviente = &Raviente{
|
||||
register: make([]uint32, 30),
|
||||
state: make([]uint32, 30),
|
||||
support: make([]uint32, 30),
|
||||
}
|
||||
s.semaphore = make(map[string]*Semaphore)
|
||||
return s
|
||||
}
|
||||
|
||||
func TestRavienteInitialization(t *testing.T) {
|
||||
r := &Raviente{
|
||||
register: make([]uint32, 30),
|
||||
state: make([]uint32, 30),
|
||||
support: make([]uint32, 30),
|
||||
}
|
||||
if r == nil {
|
||||
t.Fatal("Raviente is nil")
|
||||
}
|
||||
if len(r.register) != 30 {
|
||||
t.Errorf("register length = %d, want 30", len(r.register))
|
||||
}
|
||||
if len(r.state) != 30 {
|
||||
t.Errorf("state length = %d, want 30", len(r.state))
|
||||
}
|
||||
if len(r.support) != 30 {
|
||||
t.Errorf("support length = %d, want 30", len(r.support))
|
||||
}
|
||||
// All values should be zero-initialized
|
||||
for i, v := range r.register {
|
||||
if v != 0 {
|
||||
t.Errorf("register[%d] = %d, want 0", i, v)
|
||||
}
|
||||
}
|
||||
for i, v := range r.state {
|
||||
if v != 0 {
|
||||
t.Errorf("state[%d] = %d, want 0", i, v)
|
||||
}
|
||||
}
|
||||
for i, v := range r.support {
|
||||
if v != 0 {
|
||||
t.Errorf("support[%d] = %d, want 0", i, v)
|
||||
}
|
||||
}
|
||||
if r.id != 0 {
|
||||
t.Errorf("id = %d, want 0", r.id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRavienteMutex(t *testing.T) {
|
||||
r := &Raviente{
|
||||
register: make([]uint32, 30),
|
||||
state: make([]uint32, 30),
|
||||
support: make([]uint32, 30),
|
||||
}
|
||||
|
||||
// Test that we can lock and unlock without deadlock
|
||||
r.Lock()
|
||||
r.register[0] = 42
|
||||
r.Unlock()
|
||||
|
||||
r.Lock()
|
||||
val := r.register[0]
|
||||
r.Unlock()
|
||||
|
||||
if val != 42 {
|
||||
t.Errorf("register[0] = %d, want 42", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRavienteDataAccess(t *testing.T) {
|
||||
r := &Raviente{
|
||||
register: make([]uint32, 30),
|
||||
state: make([]uint32, 30),
|
||||
support: make([]uint32, 30),
|
||||
}
|
||||
|
||||
// Write and verify register data
|
||||
r.register[0] = 100
|
||||
r.register[4] = 200
|
||||
r.register[29] = 300
|
||||
|
||||
if r.register[0] != 100 {
|
||||
t.Errorf("register[0] = %d, want 100", r.register[0])
|
||||
}
|
||||
if r.register[4] != 200 {
|
||||
t.Errorf("register[4] = %d, want 200", r.register[4])
|
||||
}
|
||||
if r.register[29] != 300 {
|
||||
t.Errorf("register[29] = %d, want 300", r.register[29])
|
||||
}
|
||||
|
||||
// Write and verify state data
|
||||
r.state[0] = 500
|
||||
r.state[28] = 600
|
||||
|
||||
if r.state[0] != 500 {
|
||||
t.Errorf("state[0] = %d, want 500", r.state[0])
|
||||
}
|
||||
if r.state[28] != 600 {
|
||||
t.Errorf("state[28] = %d, want 600", r.state[28])
|
||||
}
|
||||
|
||||
// Write and verify support data
|
||||
r.support[0] = 700
|
||||
r.support[24] = 800
|
||||
|
||||
if r.support[0] != 700 {
|
||||
t.Errorf("support[0] = %d, want 700", r.support[0])
|
||||
}
|
||||
if r.support[24] != 800 {
|
||||
t.Errorf("support[24] = %d, want 800", r.support[24])
|
||||
}
|
||||
}
|
||||
|
||||
func TestRavienteID(t *testing.T) {
|
||||
r := &Raviente{
|
||||
register: make([]uint32, 30),
|
||||
state: make([]uint32, 30),
|
||||
support: make([]uint32, 30),
|
||||
}
|
||||
|
||||
r.id = 12345
|
||||
if r.id != 12345 {
|
||||
t.Errorf("id = %d, want 12345", r.id)
|
||||
}
|
||||
|
||||
r.id = 0xFFFF
|
||||
if r.id != 0xFFFF {
|
||||
t.Errorf("id = %d, want %d", r.id, uint16(0xFFFF))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateMockServerWithRaviente(t *testing.T) {
|
||||
s := createMockServerWithRaviente()
|
||||
if s == nil {
|
||||
t.Fatal("createMockServerWithRaviente() returned nil")
|
||||
}
|
||||
if s.raviente == nil {
|
||||
t.Fatal("raviente should not be nil")
|
||||
}
|
||||
if s.semaphore == nil {
|
||||
t.Fatal("semaphore should not be nil")
|
||||
}
|
||||
if len(s.raviente.register) != 30 {
|
||||
t.Errorf("raviente register length = %d, want 30", len(s.raviente.register))
|
||||
}
|
||||
if len(s.raviente.state) != 30 {
|
||||
t.Errorf("raviente state length = %d, want 30", len(s.raviente.state))
|
||||
}
|
||||
if len(s.raviente.support) != 30 {
|
||||
t.Errorf("raviente support length = %d, want 30", len(s.raviente.support))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableRegistered(t *testing.T) {
|
||||
s := createMockServer()
|
||||
if s == nil {
|
||||
t.Fatal("createMockServer() returned nil")
|
||||
}
|
||||
|
||||
// Verify handler table is populated
|
||||
if len(handlerTable) == 0 {
|
||||
t.Error("handlers table should not be empty")
|
||||
}
|
||||
|
||||
// Check that key handler types are registered
|
||||
// (these are critical handlers that must always be present)
|
||||
criticalHandlers := []string{
|
||||
"handleMsgSysCreateStage",
|
||||
"handleMsgSysStageDestruct",
|
||||
}
|
||||
_ = criticalHandlers // We just verify the table is non-empty since handler function names aren't directly accessible
|
||||
|
||||
// Verify minimum handler count
|
||||
if len(handlerTable) < 50 {
|
||||
t.Errorf("handlers count = %d, expected at least 50", len(handlerTable))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableNilSession(t *testing.T) {
|
||||
// This test verifies that the handler table exists and has entries
|
||||
// but doesn't call handlers (which would require a real session)
|
||||
_ = createMockServer()
|
||||
|
||||
count := 0
|
||||
for range handlerTable {
|
||||
count++
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
t.Error("No handlers registered")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMockServerPacketHandling(t *testing.T) {
|
||||
s := createMockServerWithRaviente()
|
||||
session := createMockSession(1, s)
|
||||
|
||||
// Verify the session and server are properly linked
|
||||
if session.server != s {
|
||||
t.Error("Session server reference mismatch")
|
||||
}
|
||||
|
||||
// Verify byteframe can be created for packet construction
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(0) // AckHandle
|
||||
if len(bf.Data()) != 4 {
|
||||
t.Errorf("ByteFrame length = %d, want 4", len(bf.Data()))
|
||||
}
|
||||
|
||||
// Verify packet types can be instantiated
|
||||
pkt := &mhfpacket.MsgSysAck{}
|
||||
if pkt == nil {
|
||||
t.Error("Failed to create MsgSysAck")
|
||||
}
|
||||
}
|
||||
522
server/entranceserver/entrance_server_test.go
Normal file
522
server/entranceserver/entrance_server_test.go
Normal file
@@ -0,0 +1,522 @@
|
||||
package entranceserver
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
_config "erupe-ce/config"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestNewServer(t *testing.T) {
|
||||
cfg := &Config{
|
||||
Logger: nil,
|
||||
DB: nil,
|
||||
ErupeConfig: &_config.Config{},
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
if s == nil {
|
||||
t.Fatal("NewServer() returned nil")
|
||||
}
|
||||
if s.isShuttingDown {
|
||||
t.Error("New server should not be shutting down")
|
||||
}
|
||||
if s.erupeConfig == nil {
|
||||
t.Error("erupeConfig should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewServerWithNilConfig(t *testing.T) {
|
||||
cfg := &Config{}
|
||||
s := NewServer(cfg)
|
||||
if s == nil {
|
||||
t.Fatal("NewServer() returned nil for empty config")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerType(t *testing.T) {
|
||||
s := &Server{}
|
||||
if s.isShuttingDown {
|
||||
t.Error("Zero value server should not be shutting down")
|
||||
}
|
||||
if s.listener != nil {
|
||||
t.Error("Zero value server should have nil listener")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigFields(t *testing.T) {
|
||||
cfg := &Config{
|
||||
Logger: nil,
|
||||
DB: nil,
|
||||
ErupeConfig: nil,
|
||||
}
|
||||
|
||||
if cfg.Logger != nil {
|
||||
t.Error("Config Logger should be nil")
|
||||
}
|
||||
if cfg.DB != nil {
|
||||
t.Error("Config DB should be nil")
|
||||
}
|
||||
if cfg.ErupeConfig != nil {
|
||||
t.Error("Config ErupeConfig should be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerShutdownFlag(t *testing.T) {
|
||||
cfg := &Config{
|
||||
ErupeConfig: &_config.Config{},
|
||||
}
|
||||
s := NewServer(cfg)
|
||||
|
||||
if s.isShuttingDown {
|
||||
t.Error("New server should not be shutting down")
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
s.isShuttingDown = true
|
||||
s.Unlock()
|
||||
|
||||
if !s.isShuttingDown {
|
||||
t.Error("Server should be shutting down after flag is set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerConfigStorage(t *testing.T) {
|
||||
erupeConfig := &_config.Config{
|
||||
Host: "192.168.1.100",
|
||||
Entrance: _config.Entrance{
|
||||
Enabled: true,
|
||||
Port: 53310,
|
||||
Entries: []_config.EntranceServerInfo{
|
||||
{
|
||||
Name: "Test Server",
|
||||
IP: "127.0.0.1",
|
||||
Type: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
|
||||
if s.erupeConfig.Host != "192.168.1.100" {
|
||||
t.Errorf("Host = %s, want 192.168.1.100", s.erupeConfig.Host)
|
||||
}
|
||||
if s.erupeConfig.Entrance.Port != 53310 {
|
||||
t.Errorf("Entrance.Port = %d, want 53310", s.erupeConfig.Entrance.Port)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerEntranceEntries(t *testing.T) {
|
||||
entries := []_config.EntranceServerInfo{
|
||||
{
|
||||
Name: "World 1",
|
||||
IP: "10.0.0.1",
|
||||
Type: 1,
|
||||
Recommended: 1,
|
||||
Channels: []_config.EntranceChannelInfo{
|
||||
{Port: 54001, MaxPlayers: 100},
|
||||
{Port: 54002, MaxPlayers: 100},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "World 2",
|
||||
IP: "10.0.0.2",
|
||||
Type: 2,
|
||||
Recommended: 0,
|
||||
Channels: []_config.EntranceChannelInfo{
|
||||
{Port: 54003, MaxPlayers: 50},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
erupeConfig := &_config.Config{
|
||||
Entrance: _config.Entrance{
|
||||
Enabled: true,
|
||||
Port: 53310,
|
||||
Entries: entries,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{ErupeConfig: erupeConfig}
|
||||
s := NewServer(cfg)
|
||||
|
||||
if len(s.erupeConfig.Entrance.Entries) != 2 {
|
||||
t.Errorf("Entries count = %d, want 2", len(s.erupeConfig.Entrance.Entries))
|
||||
}
|
||||
|
||||
if s.erupeConfig.Entrance.Entries[0].Name != "World 1" {
|
||||
t.Errorf("First entry name = %s, want World 1", s.erupeConfig.Entrance.Entries[0].Name)
|
||||
}
|
||||
|
||||
if len(s.erupeConfig.Entrance.Entries[0].Channels) != 2 {
|
||||
t.Errorf("First entry channels = %d, want 2", len(s.erupeConfig.Entrance.Entries[0].Channels))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptDecryptRoundTrip(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
key byte
|
||||
}{
|
||||
{"empty", []byte{}, 0x00},
|
||||
{"single byte", []byte{0x42}, 0x00},
|
||||
{"multiple bytes", []byte{0x01, 0x02, 0x03, 0x04}, 0x00},
|
||||
{"with key", []byte{0xDE, 0xAD, 0xBE, 0xEF}, 0x55},
|
||||
{"max key", []byte{0x01, 0x02}, 0xFF},
|
||||
{"long data", make([]byte, 100), 0x42},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
encrypted := EncryptBin8(tt.data, tt.key)
|
||||
decrypted := DecryptBin8(encrypted, tt.key)
|
||||
|
||||
if len(decrypted) != len(tt.data) {
|
||||
t.Errorf("decrypted length = %d, want %d", len(decrypted), len(tt.data))
|
||||
return
|
||||
}
|
||||
|
||||
for i := range tt.data {
|
||||
if decrypted[i] != tt.data[i] {
|
||||
t.Errorf("decrypted[%d] = 0x%X, want 0x%X", i, decrypted[i], tt.data[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalcSum32Deterministic(t *testing.T) {
|
||||
data := []byte{0x01, 0x02, 0x03, 0x04, 0x05}
|
||||
|
||||
sum1 := CalcSum32(data)
|
||||
sum2 := CalcSum32(data)
|
||||
|
||||
if sum1 != sum2 {
|
||||
t.Errorf("CalcSum32 not deterministic: got 0x%X and 0x%X", sum1, sum2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalcSum32DifferentInputs(t *testing.T) {
|
||||
data1 := []byte{0x01, 0x02, 0x03}
|
||||
data2 := []byte{0x01, 0x02, 0x04}
|
||||
|
||||
sum1 := CalcSum32(data1)
|
||||
sum2 := CalcSum32(data2)
|
||||
|
||||
if sum1 == sum2 {
|
||||
t.Error("Different inputs should produce different checksums")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptBin8KeyVariation(t *testing.T) {
|
||||
data := []byte{0x01, 0x02, 0x03, 0x04}
|
||||
|
||||
enc1 := EncryptBin8(data, 0x00)
|
||||
enc2 := EncryptBin8(data, 0x01)
|
||||
enc3 := EncryptBin8(data, 0xFF)
|
||||
|
||||
if bytesEqual(enc1, enc2) {
|
||||
t.Error("Different keys should produce different encrypted data (0x00 vs 0x01)")
|
||||
}
|
||||
if bytesEqual(enc2, enc3) {
|
||||
t.Error("Different keys should produce different encrypted data (0x01 vs 0xFF)")
|
||||
}
|
||||
}
|
||||
|
||||
func bytesEqual(a, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestEncryptBin8LengthPreservation(t *testing.T) {
|
||||
lengths := []int{0, 1, 7, 8, 9, 100, 1000}
|
||||
|
||||
for _, length := range lengths {
|
||||
data := make([]byte, length)
|
||||
for i := range data {
|
||||
data[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
encrypted := EncryptBin8(data, 0x42)
|
||||
if len(encrypted) != length {
|
||||
t.Errorf("EncryptBin8 length %d changed to %d", length, len(encrypted))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalcSum32LargeInput(t *testing.T) {
|
||||
data := make([]byte, 10000)
|
||||
for i := range data {
|
||||
data[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
sum := CalcSum32(data)
|
||||
sum2 := CalcSum32(data)
|
||||
if sum != sum2 {
|
||||
t.Errorf("CalcSum32 inconsistent for large input: 0x%X vs 0x%X", sum, sum2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerMutexLocking(t *testing.T) {
|
||||
cfg := &Config{ErupeConfig: &_config.Config{}}
|
||||
s := NewServer(cfg)
|
||||
|
||||
s.Lock()
|
||||
s.isShuttingDown = true
|
||||
s.Unlock()
|
||||
|
||||
s.Lock()
|
||||
result := s.isShuttingDown
|
||||
s.Unlock()
|
||||
|
||||
if !result {
|
||||
t.Error("Mutex should protect isShuttingDown flag")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerStartAndShutdown(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
Entrance: _config.Entrance{
|
||||
Enabled: true,
|
||||
Port: 0,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Logger: logger,
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Start() error: %v", err)
|
||||
}
|
||||
|
||||
if s.listener == nil {
|
||||
t.Error("Server listener should not be nil after Start()")
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
if s.isShuttingDown {
|
||||
t.Error("Server should not be shutting down after Start()")
|
||||
}
|
||||
s.Unlock()
|
||||
|
||||
s.Shutdown()
|
||||
|
||||
s.Lock()
|
||||
if !s.isShuttingDown {
|
||||
t.Error("Server should be shutting down after Shutdown()")
|
||||
}
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func TestServerStartWithInvalidPort(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
Entrance: _config.Entrance{
|
||||
Port: 1,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Logger: logger,
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
err := s.Start()
|
||||
if err == nil {
|
||||
s.Shutdown()
|
||||
t.Error("Start() should fail with invalid port")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerListenerAddress(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
Entrance: _config.Entrance{
|
||||
Enabled: true,
|
||||
Port: 0,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Logger: logger,
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Start() error: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
|
||||
addr := s.listener.Addr()
|
||||
if addr == nil {
|
||||
t.Error("Listener address should not be nil")
|
||||
}
|
||||
|
||||
tcpAddr, ok := addr.(*net.TCPAddr)
|
||||
if !ok {
|
||||
t.Error("Listener address should be a TCP address")
|
||||
}
|
||||
|
||||
if tcpAddr.Port == 0 {
|
||||
t.Error("Listener port should be assigned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerAcceptClientsExitsOnShutdown(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
Entrance: _config.Entrance{
|
||||
Enabled: true,
|
||||
Port: 0,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Logger: logger,
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Start() error: %v", err)
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
s.Shutdown()
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
s.Lock()
|
||||
if !s.isShuttingDown {
|
||||
t.Error("Server should be marked as shutting down")
|
||||
}
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func TestServerHandleConnectionImmediateClose(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
Entrance: _config.Entrance{
|
||||
Enabled: true,
|
||||
Port: 0,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Logger: logger,
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Start() error: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
|
||||
addr := s.listener.Addr().String()
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
t.Fatalf("Dial() error: %v", err)
|
||||
}
|
||||
conn.Close()
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestServerHandleConnectionShortInit(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
Entrance: _config.Entrance{
|
||||
Enabled: true,
|
||||
Port: 0,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Logger: logger,
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Start() error: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
|
||||
addr := s.listener.Addr().String()
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
t.Fatalf("Dial() error: %v", err)
|
||||
}
|
||||
_, _ = conn.Write([]byte{0, 0, 0, 0})
|
||||
conn.Close()
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestServerMultipleConnections(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
Entrance: _config.Entrance{
|
||||
Enabled: true,
|
||||
Port: 0,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Logger: logger,
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Start() error: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
|
||||
addr := s.listener.Addr().String()
|
||||
|
||||
conns := make([]net.Conn, 3)
|
||||
for i := range conns {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
t.Fatalf("Dial() %d error: %v", i, err)
|
||||
}
|
||||
conns[i] = conn
|
||||
}
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
for _, conn := range conns {
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
825
server/signserver/dbutils_test.go
Normal file
825
server/signserver/dbutils_test.go
Normal file
@@ -0,0 +1,825 @@
|
||||
package signserver
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestCharacterStruct(t *testing.T) {
|
||||
c := character{
|
||||
ID: 12345,
|
||||
IsFemale: true,
|
||||
IsNewCharacter: false,
|
||||
Name: "TestHunter",
|
||||
UnkDescString: "Test description",
|
||||
HR: 999,
|
||||
GR: 300,
|
||||
WeaponType: 5,
|
||||
LastLogin: 1700000000,
|
||||
}
|
||||
|
||||
if c.ID != 12345 {
|
||||
t.Errorf("ID = %d, want 12345", c.ID)
|
||||
}
|
||||
if c.IsFemale != true {
|
||||
t.Error("IsFemale should be true")
|
||||
}
|
||||
if c.IsNewCharacter != false {
|
||||
t.Error("IsNewCharacter should be false")
|
||||
}
|
||||
if c.Name != "TestHunter" {
|
||||
t.Errorf("Name = %s, want TestHunter", c.Name)
|
||||
}
|
||||
if c.UnkDescString != "Test description" {
|
||||
t.Errorf("UnkDescString = %s, want Test description", c.UnkDescString)
|
||||
}
|
||||
if c.HR != 999 {
|
||||
t.Errorf("HR = %d, want 999", c.HR)
|
||||
}
|
||||
if c.GR != 300 {
|
||||
t.Errorf("GR = %d, want 300", c.GR)
|
||||
}
|
||||
if c.WeaponType != 5 {
|
||||
t.Errorf("WeaponType = %d, want 5", c.WeaponType)
|
||||
}
|
||||
if c.LastLogin != 1700000000 {
|
||||
t.Errorf("LastLogin = %d, want 1700000000", c.LastLogin)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterStructDefaults(t *testing.T) {
|
||||
c := character{}
|
||||
|
||||
if c.ID != 0 {
|
||||
t.Errorf("default ID = %d, want 0", c.ID)
|
||||
}
|
||||
if c.IsFemale != false {
|
||||
t.Error("default IsFemale should be false")
|
||||
}
|
||||
if c.IsNewCharacter != false {
|
||||
t.Error("default IsNewCharacter should be false")
|
||||
}
|
||||
if c.Name != "" {
|
||||
t.Errorf("default Name = %s, want empty", c.Name)
|
||||
}
|
||||
if c.HR != 0 {
|
||||
t.Errorf("default HR = %d, want 0", c.HR)
|
||||
}
|
||||
if c.GR != 0 {
|
||||
t.Errorf("default GR = %d, want 0", c.GR)
|
||||
}
|
||||
if c.WeaponType != 0 {
|
||||
t.Errorf("default WeaponType = %d, want 0", c.WeaponType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMembersStruct(t *testing.T) {
|
||||
m := members{
|
||||
CID: 100,
|
||||
ID: 200,
|
||||
Name: "FriendName",
|
||||
}
|
||||
|
||||
if m.CID != 100 {
|
||||
t.Errorf("CID = %d, want 100", m.CID)
|
||||
}
|
||||
if m.ID != 200 {
|
||||
t.Errorf("ID = %d, want 200", m.ID)
|
||||
}
|
||||
if m.Name != "FriendName" {
|
||||
t.Errorf("Name = %s, want FriendName", m.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMembersStructDefaults(t *testing.T) {
|
||||
m := members{}
|
||||
|
||||
if m.CID != 0 {
|
||||
t.Errorf("default CID = %d, want 0", m.CID)
|
||||
}
|
||||
if m.ID != 0 {
|
||||
t.Errorf("default ID = %d, want 0", m.ID)
|
||||
}
|
||||
if m.Name != "" {
|
||||
t.Errorf("default Name = %s, want empty", m.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterWeaponTypes(t *testing.T) {
|
||||
weaponTypes := []uint16{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}
|
||||
|
||||
for _, wt := range weaponTypes {
|
||||
c := character{WeaponType: wt}
|
||||
if c.WeaponType != wt {
|
||||
t.Errorf("WeaponType = %d, want %d", c.WeaponType, wt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterHRRange(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hr uint16
|
||||
}{
|
||||
{"min", 0},
|
||||
{"beginner", 1},
|
||||
{"hr30", 30},
|
||||
{"hr50", 50},
|
||||
{"hr99", 99},
|
||||
{"hr299", 299},
|
||||
{"hr998", 998},
|
||||
{"hr999", 999},
|
||||
{"max uint16", 65535},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := character{HR: tt.hr}
|
||||
if c.HR != tt.hr {
|
||||
t.Errorf("HR = %d, want %d", c.HR, tt.hr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterGRRange(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
gr uint16
|
||||
}{
|
||||
{"min", 0},
|
||||
{"gr1", 1},
|
||||
{"gr100", 100},
|
||||
{"gr300", 300},
|
||||
{"gr999", 999},
|
||||
{"max uint16", 65535},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := character{GR: tt.gr}
|
||||
if c.GR != tt.gr {
|
||||
t.Errorf("GR = %d, want %d", c.GR, tt.gr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterIDRange(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
id uint32
|
||||
}{
|
||||
{"min", 0},
|
||||
{"small", 1},
|
||||
{"medium", 1000000},
|
||||
{"large", 0xFFFFFFFF},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := character{ID: tt.id}
|
||||
if c.ID != tt.id {
|
||||
t.Errorf("ID = %d, want %d", c.ID, tt.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterGender(t *testing.T) {
|
||||
male := character{IsFemale: false}
|
||||
if male.IsFemale != false {
|
||||
t.Error("Male character should have IsFemale = false")
|
||||
}
|
||||
|
||||
female := character{IsFemale: true}
|
||||
if female.IsFemale != true {
|
||||
t.Error("Female character should have IsFemale = true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterNewStatus(t *testing.T) {
|
||||
newChar := character{IsNewCharacter: true}
|
||||
if newChar.IsNewCharacter != true {
|
||||
t.Error("New character should have IsNewCharacter = true")
|
||||
}
|
||||
|
||||
existingChar := character{IsNewCharacter: false}
|
||||
if existingChar.IsNewCharacter != false {
|
||||
t.Error("Existing character should have IsNewCharacter = false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterNameLength(t *testing.T) {
|
||||
names := []string{
|
||||
"",
|
||||
"A",
|
||||
"Hunter",
|
||||
"LongHunterName123",
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
c := character{Name: name}
|
||||
if c.Name != name {
|
||||
t.Errorf("Name = %s, want %s", c.Name, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterLastLogin(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
lastLogin uint32
|
||||
}{
|
||||
{"zero", 0},
|
||||
{"past", 1600000000},
|
||||
{"present", 1700000000},
|
||||
{"future", 1800000000},
|
||||
{"max", 0xFFFFFFFF},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := character{LastLogin: tt.lastLogin}
|
||||
if c.LastLogin != tt.lastLogin {
|
||||
t.Errorf("LastLogin = %d, want %d", c.LastLogin, tt.lastLogin)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMembersCIDAssignment(t *testing.T) {
|
||||
m := members{CID: 12345}
|
||||
if m.CID != 12345 {
|
||||
t.Errorf("CID = %d, want 12345", m.CID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleCharacters(t *testing.T) {
|
||||
chars := []character{
|
||||
{ID: 1, Name: "Char1", HR: 100},
|
||||
{ID: 2, Name: "Char2", HR: 200},
|
||||
{ID: 3, Name: "Char3", HR: 300},
|
||||
}
|
||||
|
||||
for i, c := range chars {
|
||||
expectedID := uint32(i + 1)
|
||||
if c.ID != expectedID {
|
||||
t.Errorf("chars[%d].ID = %d, want %d", i, c.ID, expectedID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleMembers(t *testing.T) {
|
||||
membersList := []members{
|
||||
{CID: 1, ID: 10, Name: "Friend1"},
|
||||
{CID: 1, ID: 20, Name: "Friend2"},
|
||||
{CID: 2, ID: 30, Name: "Friend3"},
|
||||
}
|
||||
|
||||
if membersList[0].CID != membersList[1].CID {
|
||||
t.Error("First two members should share the same CID")
|
||||
}
|
||||
|
||||
if membersList[1].CID == membersList[2].CID {
|
||||
t.Error("Third member should have different CID")
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to create a test server with mocked database
|
||||
func newTestServerWithMock(t *testing.T) (*Server, sqlmock.Sqlmock) {
|
||||
db, mock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create sqlmock: %v", err)
|
||||
}
|
||||
|
||||
sqlxDB := sqlx.NewDb(db, "sqlmock")
|
||||
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
db: sqlxDB,
|
||||
}
|
||||
|
||||
return server, mock
|
||||
}
|
||||
|
||||
func TestGetCharactersForUser(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
rows := sqlmock.NewRows([]string{"id", "is_female", "is_new_character", "name", "unk_desc_string", "hr", "gr", "weapon_type", "last_login"}).
|
||||
AddRow(1, false, false, "Hunter1", "desc1", 100, 50, 3, 1700000000).
|
||||
AddRow(2, true, false, "Hunter2", "desc2", 200, 100, 7, 1700000001)
|
||||
|
||||
mock.ExpectQuery("SELECT id, is_female, is_new_character, name, unk_desc_string, hr, gr, weapon_type, last_login FROM characters WHERE user_id = \\$1 AND deleted = false ORDER BY id").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(rows)
|
||||
|
||||
chars, err := server.getCharactersForUser(1)
|
||||
if err != nil {
|
||||
t.Errorf("getCharactersForUser() error: %v", err)
|
||||
}
|
||||
|
||||
if len(chars) != 2 {
|
||||
t.Errorf("getCharactersForUser() returned %d characters, want 2", len(chars))
|
||||
}
|
||||
|
||||
if chars[0].Name != "Hunter1" {
|
||||
t.Errorf("First character name = %s, want Hunter1", chars[0].Name)
|
||||
}
|
||||
|
||||
if chars[1].IsFemale != true {
|
||||
t.Error("Second character should be female")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCharactersForUserNoCharacters(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
rows := sqlmock.NewRows([]string{"id", "is_female", "is_new_character", "name", "unk_desc_string", "hr", "gr", "weapon_type", "last_login"})
|
||||
|
||||
mock.ExpectQuery("SELECT id, is_female, is_new_character, name, unk_desc_string, hr, gr, weapon_type, last_login FROM characters WHERE user_id = \\$1 AND deleted = false ORDER BY id").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(rows)
|
||||
|
||||
chars, err := server.getCharactersForUser(1)
|
||||
if err != nil {
|
||||
t.Errorf("getCharactersForUser() error: %v", err)
|
||||
}
|
||||
|
||||
if len(chars) != 0 {
|
||||
t.Errorf("getCharactersForUser() returned %d characters, want 0", len(chars))
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCharactersForUserDBError(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
mock.ExpectQuery("SELECT id, is_female, is_new_character, name, unk_desc_string, hr, gr, weapon_type, last_login FROM characters WHERE user_id = \\$1 AND deleted = false ORDER BY id").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnError(sql.ErrConnDone)
|
||||
|
||||
_, err := server.getCharactersForUser(1)
|
||||
if err == nil {
|
||||
t.Error("getCharactersForUser() should return error")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLastCID(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
mock.ExpectQuery("SELECT last_character FROM users WHERE id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"last_character"}).AddRow(12345))
|
||||
|
||||
lastCID := server.getLastCID(1)
|
||||
if lastCID != 12345 {
|
||||
t.Errorf("getLastCID() = %d, want 12345", lastCID)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLastCIDNoResult(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
mock.ExpectQuery("SELECT last_character FROM users WHERE id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnError(sql.ErrNoRows)
|
||||
|
||||
lastCID := server.getLastCID(1)
|
||||
if lastCID != 0 {
|
||||
t.Errorf("getLastCID() with no result = %d, want 0", lastCID)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUserRights(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
mock.ExpectQuery("SELECT rights FROM users WHERE id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"rights"}).AddRow(30))
|
||||
|
||||
rights := server.getUserRights(1)
|
||||
if rights == 0 {
|
||||
t.Error("getUserRights() should return non-zero value")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetReturnExpiry(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
recentLogin := time.Now().Add(-time.Hour * 24)
|
||||
mock.ExpectQuery("SELECT COALESCE\\(last_login, now\\(\\)\\) FROM users WHERE id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"last_login"}).AddRow(recentLogin))
|
||||
|
||||
mock.ExpectQuery("SELECT return_expires FROM users WHERE id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"return_expires"}).AddRow(time.Now().Add(time.Hour * 24 * 30)))
|
||||
|
||||
mock.ExpectExec("UPDATE users SET last_login=\\$1 WHERE id=\\$2").
|
||||
WithArgs(sqlmock.AnyArg(), uint32(1)).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
expiry := server.getReturnExpiry(1)
|
||||
|
||||
if expiry.Before(time.Now()) {
|
||||
t.Error("getReturnExpiry() should return future date")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetReturnExpiryInactiveUser(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
oldLogin := time.Now().Add(-time.Hour * 24 * 100)
|
||||
mock.ExpectQuery("SELECT COALESCE\\(last_login, now\\(\\)\\) FROM users WHERE id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"last_login"}).AddRow(oldLogin))
|
||||
|
||||
mock.ExpectExec("UPDATE users SET return_expires=\\$1 WHERE id=\\$2").
|
||||
WithArgs(sqlmock.AnyArg(), uint32(1)).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
mock.ExpectExec("UPDATE users SET last_login=\\$1 WHERE id=\\$2").
|
||||
WithArgs(sqlmock.AnyArg(), uint32(1)).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
expiry := server.getReturnExpiry(1)
|
||||
|
||||
if expiry.Before(time.Now()) {
|
||||
t.Error("getReturnExpiry() should return future date for inactive user")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetReturnExpiryDBError(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
recentLogin := time.Now().Add(-time.Hour * 24)
|
||||
mock.ExpectQuery("SELECT COALESCE\\(last_login, now\\(\\)\\) FROM users WHERE id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"last_login"}).AddRow(recentLogin))
|
||||
|
||||
mock.ExpectQuery("SELECT return_expires FROM users WHERE id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnError(sql.ErrNoRows)
|
||||
|
||||
mock.ExpectExec("UPDATE users SET return_expires=\\$1 WHERE id=\\$2").
|
||||
WithArgs(sqlmock.AnyArg(), uint32(1)).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
mock.ExpectExec("UPDATE users SET last_login=\\$1 WHERE id=\\$2").
|
||||
WithArgs(sqlmock.AnyArg(), uint32(1)).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
expiry := server.getReturnExpiry(1)
|
||||
|
||||
if expiry.IsZero() {
|
||||
t.Error("getReturnExpiry() should return non-zero time even on error")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewUserChara(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM characters WHERE user_id = \\$1 AND is_new_character = true").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
|
||||
|
||||
mock.ExpectExec("INSERT INTO characters").
|
||||
WithArgs(uint32(1), sqlmock.AnyArg()).
|
||||
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
|
||||
err := server.newUserChara(1)
|
||||
if err != nil {
|
||||
t.Errorf("newUserChara() error: %v", err)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewUserCharaAlreadyHasNewChar(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM characters WHERE user_id = \\$1 AND is_new_character = true").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
|
||||
err := server.newUserChara(1)
|
||||
if err != nil {
|
||||
t.Errorf("newUserChara() should return nil when user already has new char: %v", err)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewUserCharaCountError(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM characters WHERE user_id = \\$1 AND is_new_character = true").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnError(sql.ErrConnDone)
|
||||
|
||||
err := server.newUserChara(1)
|
||||
if err == nil {
|
||||
t.Error("newUserChara() should return error when count query fails")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewUserCharaInsertError(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM characters WHERE user_id = \\$1 AND is_new_character = true").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
|
||||
|
||||
mock.ExpectExec("INSERT INTO characters").
|
||||
WithArgs(uint32(1), sqlmock.AnyArg()).
|
||||
WillReturnError(sql.ErrConnDone)
|
||||
|
||||
err := server.newUserChara(1)
|
||||
if err == nil {
|
||||
t.Error("newUserChara() should return error when insert fails")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterDBAccount(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
mock.ExpectQuery("INSERT INTO users \\(username, password, return_expires\\) VALUES \\(\\$1, \\$2, \\$3\\) RETURNING id").
|
||||
WithArgs("newuser", sqlmock.AnyArg(), sqlmock.AnyArg()).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
|
||||
|
||||
uid, err := server.registerDBAccount("newuser", "password123")
|
||||
if err != nil {
|
||||
t.Errorf("registerDBAccount() error: %v", err)
|
||||
}
|
||||
if uid != 1 {
|
||||
t.Errorf("registerDBAccount() uid = %d, want 1", uid)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterDBAccountDuplicateUser(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
mock.ExpectQuery("INSERT INTO users \\(username, password, return_expires\\) VALUES \\(\\$1, \\$2, \\$3\\) RETURNING id").
|
||||
WithArgs("existinguser", sqlmock.AnyArg(), sqlmock.AnyArg()).
|
||||
WillReturnError(sql.ErrNoRows)
|
||||
|
||||
_, err := server.registerDBAccount("existinguser", "password123")
|
||||
if err == nil {
|
||||
t.Error("registerDBAccount() should return error for duplicate user")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteCharacter(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
// validateToken: SELECT count(*) FROM sign_sessions WHERE token = $1
|
||||
// When tokenID=0, query has no AND clause but both args are still passed to QueryRow
|
||||
mock.ExpectQuery("SELECT count\\(\\*\\) FROM sign_sessions WHERE token = \\$1").
|
||||
WithArgs("validtoken", uint32(0)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
|
||||
mock.ExpectQuery("SELECT is_new_character FROM characters WHERE id = \\$1").
|
||||
WithArgs(123).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"is_new_character"}).AddRow(false))
|
||||
|
||||
mock.ExpectExec("UPDATE characters SET deleted = true WHERE id = \\$1").
|
||||
WithArgs(123).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
err := server.deleteCharacter(123, "validtoken", 0)
|
||||
if err != nil {
|
||||
t.Errorf("deleteCharacter() error: %v", err)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteNewCharacter(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
mock.ExpectQuery("SELECT count\\(\\*\\) FROM sign_sessions WHERE token = \\$1").
|
||||
WithArgs("validtoken", uint32(0)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
|
||||
mock.ExpectQuery("SELECT is_new_character FROM characters WHERE id = \\$1").
|
||||
WithArgs(123).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"is_new_character"}).AddRow(true))
|
||||
|
||||
mock.ExpectExec("DELETE FROM characters WHERE id = \\$1").
|
||||
WithArgs(123).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
err := server.deleteCharacter(123, "validtoken", 0)
|
||||
if err != nil {
|
||||
t.Errorf("deleteCharacter() error: %v", err)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteCharacterInvalidToken(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
mock.ExpectQuery("SELECT count\\(\\*\\) FROM sign_sessions WHERE token = \\$1").
|
||||
WithArgs("invalidtoken", uint32(0)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
|
||||
|
||||
err := server.deleteCharacter(123, "invalidtoken", 0)
|
||||
if err == nil {
|
||||
t.Error("deleteCharacter() should return error for invalid token")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteCharacterDeleteError(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
mock.ExpectQuery("SELECT count\\(\\*\\) FROM sign_sessions WHERE token = \\$1").
|
||||
WithArgs("validtoken", uint32(0)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
|
||||
mock.ExpectQuery("SELECT is_new_character FROM characters WHERE id = \\$1").
|
||||
WithArgs(123).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"is_new_character"}).AddRow(false))
|
||||
|
||||
mock.ExpectExec("UPDATE characters SET deleted = true WHERE id = \\$1").
|
||||
WithArgs(123).
|
||||
WillReturnError(sql.ErrConnDone)
|
||||
|
||||
err := server.deleteCharacter(123, "validtoken", 0)
|
||||
if err == nil {
|
||||
t.Error("deleteCharacter() should return error when update fails")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFriendsForCharactersEmpty(t *testing.T) {
|
||||
server, _ := newTestServerWithMock(t)
|
||||
|
||||
chars := []character{}
|
||||
|
||||
friends := server.getFriendsForCharacters(chars)
|
||||
if len(friends) != 0 {
|
||||
t.Errorf("getFriendsForCharacters() for empty chars = %d, want 0", len(friends))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGuildmatesForCharactersEmpty(t *testing.T) {
|
||||
server, _ := newTestServerWithMock(t)
|
||||
|
||||
chars := []character{}
|
||||
|
||||
guildmates := server.getGuildmatesForCharacters(chars)
|
||||
if len(guildmates) != 0 {
|
||||
t.Errorf("getGuildmatesForCharacters() for empty chars = %d, want 0", len(guildmates))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFriendsForCharacters(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
chars := []character{
|
||||
{ID: 1, Name: "Hunter1"},
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT friends FROM characters WHERE id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"friends"}).AddRow("2,3"))
|
||||
|
||||
mock.ExpectQuery("SELECT id, name FROM characters WHERE id=2 OR id=3").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).
|
||||
AddRow(2, "Friend1").
|
||||
AddRow(3, "Friend2"))
|
||||
|
||||
friends := server.getFriendsForCharacters(chars)
|
||||
if len(friends) != 2 {
|
||||
t.Errorf("getFriendsForCharacters() = %d, want 2", len(friends))
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGuildmatesForCharacters(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
chars := []character{
|
||||
{ID: 1, Name: "Hunter1"},
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT count\\(\\*\\) FROM guild_characters WHERE character_id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
|
||||
mock.ExpectQuery("SELECT guild_id FROM guild_characters WHERE character_id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"guild_id"}).AddRow(100))
|
||||
|
||||
mock.ExpectQuery("SELECT character_id AS id, c.name FROM guild_characters gc JOIN characters c ON c.id = gc.character_id WHERE guild_id=\\$1 AND character_id!=\\$2").
|
||||
WithArgs(100, uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).
|
||||
AddRow(2, "Guildmate1").
|
||||
AddRow(3, "Guildmate2"))
|
||||
|
||||
guildmates := server.getGuildmatesForCharacters(chars)
|
||||
if len(guildmates) != 2 {
|
||||
t.Errorf("getGuildmatesForCharacters() = %d, want 2", len(guildmates))
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGuildmatesNotInGuild(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
chars := []character{
|
||||
{ID: 1, Name: "Hunter1"},
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT count\\(\\*\\) FROM guild_characters WHERE character_id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
|
||||
|
||||
guildmates := server.getGuildmatesForCharacters(chars)
|
||||
if len(guildmates) != 0 {
|
||||
t.Errorf("getGuildmatesForCharacters() for non-guild member = %d, want 0", len(guildmates))
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
393
server/signserver/session_test.go
Normal file
393
server/signserver/session_test.go
Normal file
@@ -0,0 +1,393 @@
|
||||
package signserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// mockConn implements net.Conn for testing
|
||||
type mockConn struct {
|
||||
readBuf *bytes.Buffer
|
||||
writeBuf *bytes.Buffer
|
||||
closed bool
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func newMockConn() *mockConn {
|
||||
return &mockConn{
|
||||
readBuf: new(bytes.Buffer),
|
||||
writeBuf: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mockConn) Read(b []byte) (n int, err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.closed {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return m.readBuf.Read(b)
|
||||
}
|
||||
|
||||
func (m *mockConn) Write(b []byte) (n int, err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.closed {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
return m.writeBuf.Write(b)
|
||||
}
|
||||
|
||||
func (m *mockConn) Close() error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockConn) LocalAddr() net.Addr {
|
||||
return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 53312}
|
||||
}
|
||||
|
||||
func (m *mockConn) RemoteAddr() net.Addr {
|
||||
return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 12345}
|
||||
}
|
||||
|
||||
func (m *mockConn) SetDeadline(t time.Time) error { return nil }
|
||||
func (m *mockConn) SetReadDeadline(t time.Time) error { return nil }
|
||||
func (m *mockConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||
|
||||
func TestSessionStruct(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
conn := newMockConn()
|
||||
|
||||
s := &Session{
|
||||
logger: logger,
|
||||
server: nil,
|
||||
rawConn: conn,
|
||||
cryptConn: network.NewCryptConn(conn),
|
||||
}
|
||||
|
||||
if s.logger != logger {
|
||||
t.Error("Session logger not set correctly")
|
||||
}
|
||||
if s.rawConn != conn {
|
||||
t.Error("Session rawConn not set correctly")
|
||||
}
|
||||
if s.cryptConn == nil {
|
||||
t.Error("Session cryptConn should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionStructDefaults(t *testing.T) {
|
||||
s := &Session{}
|
||||
|
||||
if s.logger != nil {
|
||||
t.Error("Default Session logger should be nil")
|
||||
}
|
||||
if s.server != nil {
|
||||
t.Error("Default Session server should be nil")
|
||||
}
|
||||
if s.rawConn != nil {
|
||||
t.Error("Default Session rawConn should be nil")
|
||||
}
|
||||
if s.cryptConn != nil {
|
||||
t.Error("Default Session cryptConn should be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionMutex(t *testing.T) {
|
||||
s := &Session{}
|
||||
|
||||
s.Lock()
|
||||
s.Unlock()
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
s.Lock()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
s.Unlock()
|
||||
done <- true
|
||||
}()
|
||||
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
s.Lock()
|
||||
s.Unlock()
|
||||
|
||||
<-done
|
||||
}
|
||||
|
||||
func TestHandlePacketUnknownRequest(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
logger: logger,
|
||||
erupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
conn := newMockConn()
|
||||
session := &Session{
|
||||
logger: logger,
|
||||
server: server,
|
||||
rawConn: conn,
|
||||
cryptConn: network.NewCryptConn(conn),
|
||||
}
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteNullTerminatedBytes([]byte("UNKNOWN:100"))
|
||||
bf.WriteNullTerminatedBytes([]byte("data"))
|
||||
|
||||
err := session.handlePacket(bf.Data())
|
||||
if err != nil {
|
||||
t.Errorf("handlePacket() returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlePacketWithDevModeLogging(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
DebugOptions: _config.DebugOptions{
|
||||
LogInboundMessages: true,
|
||||
},
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
logger: logger,
|
||||
erupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
conn := newMockConn()
|
||||
session := &Session{
|
||||
logger: logger,
|
||||
server: server,
|
||||
rawConn: conn,
|
||||
cryptConn: network.NewCryptConn(conn),
|
||||
}
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteNullTerminatedBytes([]byte("TEST:100"))
|
||||
|
||||
err := session.handlePacket(bf.Data())
|
||||
if err != nil {
|
||||
t.Errorf("handlePacket() with dev mode returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlePacketRequestTypes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
reqType string
|
||||
}{
|
||||
{"unknown", "UNKNOWN:100"},
|
||||
{"invalid", "INVALID"},
|
||||
{"empty_version", "TEST:"},
|
||||
{"no_version", "NOVERSION"},
|
||||
{"special_chars", "TEST@#$:100"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{}
|
||||
server := &Server{
|
||||
logger: logger,
|
||||
erupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
conn := newMockConn()
|
||||
session := &Session{
|
||||
logger: logger,
|
||||
server: server,
|
||||
rawConn: conn,
|
||||
cryptConn: network.NewCryptConn(conn),
|
||||
}
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteNullTerminatedBytes([]byte(tt.reqType))
|
||||
|
||||
err := session.handlePacket(bf.Data())
|
||||
if err != nil {
|
||||
t.Errorf("handlePacket(%s) returned error: %v", tt.reqType, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMockConnImplementsNetConn(t *testing.T) {
|
||||
var _ net.Conn = (*mockConn)(nil)
|
||||
}
|
||||
|
||||
func TestMockConnReadWrite(t *testing.T) {
|
||||
conn := newMockConn()
|
||||
|
||||
testData := []byte("hello")
|
||||
conn.readBuf.Write(testData)
|
||||
|
||||
buf := make([]byte, len(testData))
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
t.Errorf("Read() error: %v", err)
|
||||
}
|
||||
if n != len(testData) {
|
||||
t.Errorf("Read() n = %d, want %d", n, len(testData))
|
||||
}
|
||||
if !bytes.Equal(buf, testData) {
|
||||
t.Errorf("Read() data = %v, want %v", buf, testData)
|
||||
}
|
||||
|
||||
outData := []byte("world")
|
||||
n, err = conn.Write(outData)
|
||||
if err != nil {
|
||||
t.Errorf("Write() error: %v", err)
|
||||
}
|
||||
if n != len(outData) {
|
||||
t.Errorf("Write() n = %d, want %d", n, len(outData))
|
||||
}
|
||||
if !bytes.Equal(conn.writeBuf.Bytes(), outData) {
|
||||
t.Errorf("Write() buffer = %v, want %v", conn.writeBuf.Bytes(), outData)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMockConnClose(t *testing.T) {
|
||||
conn := newMockConn()
|
||||
|
||||
err := conn.Close()
|
||||
if err != nil {
|
||||
t.Errorf("Close() error: %v", err)
|
||||
}
|
||||
|
||||
if !conn.closed {
|
||||
t.Error("conn.closed should be true after Close()")
|
||||
}
|
||||
|
||||
buf := make([]byte, 10)
|
||||
_, err = conn.Read(buf)
|
||||
if err != io.EOF {
|
||||
t.Errorf("Read() after close should return EOF, got: %v", err)
|
||||
}
|
||||
|
||||
_, err = conn.Write([]byte("test"))
|
||||
if err != io.ErrClosedPipe {
|
||||
t.Errorf("Write() after close should return ErrClosedPipe, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMockConnAddresses(t *testing.T) {
|
||||
conn := newMockConn()
|
||||
|
||||
local := conn.LocalAddr()
|
||||
if local == nil {
|
||||
t.Error("LocalAddr() should not be nil")
|
||||
}
|
||||
if local.String() != "127.0.0.1:53312" {
|
||||
t.Errorf("LocalAddr() = %s, want 127.0.0.1:53312", local.String())
|
||||
}
|
||||
|
||||
remote := conn.RemoteAddr()
|
||||
if remote == nil {
|
||||
t.Error("RemoteAddr() should not be nil")
|
||||
}
|
||||
if remote.String() != "127.0.0.1:12345" {
|
||||
t.Errorf("RemoteAddr() = %s, want 127.0.0.1:12345", remote.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMockConnDeadlines(t *testing.T) {
|
||||
conn := newMockConn()
|
||||
deadline := time.Now().Add(time.Second)
|
||||
|
||||
if err := conn.SetDeadline(deadline); err != nil {
|
||||
t.Errorf("SetDeadline() error: %v", err)
|
||||
}
|
||||
if err := conn.SetReadDeadline(deadline); err != nil {
|
||||
t.Errorf("SetReadDeadline() error: %v", err)
|
||||
}
|
||||
if err := conn.SetWriteDeadline(deadline); err != nil {
|
||||
t.Errorf("SetWriteDeadline() error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionWithCryptConn(t *testing.T) {
|
||||
conn := newMockConn()
|
||||
cryptConn := network.NewCryptConn(conn)
|
||||
|
||||
if cryptConn == nil {
|
||||
t.Fatal("NewCryptConn() returned nil")
|
||||
}
|
||||
|
||||
session := &Session{
|
||||
rawConn: conn,
|
||||
cryptConn: cryptConn,
|
||||
}
|
||||
|
||||
if session.cryptConn != cryptConn {
|
||||
t.Error("Session cryptConn not set correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionWorkWithDevModeLogging(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
DebugOptions: _config.DebugOptions{
|
||||
LogInboundMessages: true,
|
||||
},
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
logger: logger,
|
||||
erupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
clientConn, serverConn := net.Pipe()
|
||||
defer clientConn.Close()
|
||||
defer serverConn.Close()
|
||||
|
||||
session := &Session{
|
||||
logger: logger,
|
||||
server: server,
|
||||
rawConn: serverConn,
|
||||
cryptConn: network.NewCryptConn(serverConn),
|
||||
}
|
||||
|
||||
clientConn.Close()
|
||||
|
||||
session.work()
|
||||
}
|
||||
|
||||
func TestSessionWorkWithEmptyRead(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
logger: logger,
|
||||
erupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
clientConn, serverConn := net.Pipe()
|
||||
defer serverConn.Close()
|
||||
|
||||
session := &Session{
|
||||
logger: logger,
|
||||
server: server,
|
||||
rawConn: serverConn,
|
||||
cryptConn: network.NewCryptConn(serverConn),
|
||||
}
|
||||
|
||||
clientConn.Close()
|
||||
|
||||
session.work()
|
||||
}
|
||||
582
server/signserver/sign_server_test.go
Normal file
582
server/signserver/sign_server_test.go
Normal file
@@ -0,0 +1,582 @@
|
||||
package signserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
_config "erupe-ce/config"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// makeSignInFailureResp creates a 1-byte failure response for the given RespID.
|
||||
func makeSignInFailureResp(id RespID) []byte {
|
||||
return []byte{uint8(id)}
|
||||
}
|
||||
|
||||
func TestRespIDConstants(t *testing.T) {
|
||||
tests := []struct {
|
||||
respID RespID
|
||||
value uint8
|
||||
}{
|
||||
{SIGN_UNKNOWN, 0},
|
||||
{SIGN_SUCCESS, 1},
|
||||
{SIGN_EFAILED, 2},
|
||||
{SIGN_EILLEGAL, 3},
|
||||
{SIGN_EALERT, 4},
|
||||
{SIGN_EABORT, 5},
|
||||
{SIGN_ERESPONSE, 6},
|
||||
{SIGN_EDATABASE, 7},
|
||||
{SIGN_EABSENCE, 8},
|
||||
{SIGN_ERESIGN, 9},
|
||||
{SIGN_ESUSPEND_D, 10},
|
||||
{SIGN_ELOCK, 11},
|
||||
{SIGN_EPASS, 12},
|
||||
{SIGN_ERIGHT, 13},
|
||||
{SIGN_EAUTH, 14},
|
||||
{SIGN_ESUSPEND, 15},
|
||||
{SIGN_EELIMINATE, 16},
|
||||
{SIGN_ECLOSE, 17},
|
||||
{SIGN_ECLOSE_EX, 18},
|
||||
{SIGN_EINTERVAL, 19},
|
||||
{SIGN_EMOVED, 20},
|
||||
{SIGN_ENOTREADY, 21},
|
||||
{SIGN_EALREADY, 22},
|
||||
{SIGN_EIPADDR, 23},
|
||||
{SIGN_EHANGAME, 24},
|
||||
{SIGN_UPD_ONLY, 25},
|
||||
{SIGN_EMBID, 26},
|
||||
{SIGN_ECOGCODE, 27},
|
||||
{SIGN_ETOKEN, 28},
|
||||
{SIGN_ECOGLINK, 29},
|
||||
{SIGN_EMAINTE, 30},
|
||||
{SIGN_EMAINTE_NOUPDATE, 31},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(fmt.Sprintf("RespID_%d", tt.value), func(t *testing.T) {
|
||||
if uint8(tt.respID) != tt.value {
|
||||
t.Errorf("RespID = %d, want %d", uint8(tt.respID), tt.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRespIDType(t *testing.T) {
|
||||
var r RespID = 0xFF
|
||||
if uint8(r) != 0xFF {
|
||||
t.Errorf("RespID max value = %d, want %d", uint8(r), 0xFF)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeSignInFailureResp(t *testing.T) {
|
||||
tests := []RespID{
|
||||
SIGN_UNKNOWN,
|
||||
SIGN_EFAILED,
|
||||
SIGN_EILLEGAL,
|
||||
SIGN_ESUSPEND,
|
||||
SIGN_EELIMINATE,
|
||||
SIGN_EIPADDR,
|
||||
}
|
||||
|
||||
for _, respID := range tests {
|
||||
t.Run(fmt.Sprintf("RespID_%d", respID), func(t *testing.T) {
|
||||
resp := makeSignInFailureResp(respID)
|
||||
|
||||
if len(resp) != 1 {
|
||||
t.Errorf("makeSignInFailureResp() len = %d, want 1", len(resp))
|
||||
}
|
||||
if resp[0] != uint8(respID) {
|
||||
t.Errorf("makeSignInFailureResp() = %d, want %d", resp[0], uint8(respID))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeSignInFailureRespAllCodes(t *testing.T) {
|
||||
for i := uint8(0); i <= 40; i++ {
|
||||
resp := makeSignInFailureResp(RespID(i))
|
||||
if len(resp) != 1 {
|
||||
t.Errorf("makeSignInFailureResp(%d) len = %d, want 1", i, len(resp))
|
||||
}
|
||||
if resp[0] != i {
|
||||
t.Errorf("makeSignInFailureResp(%d) = %d", i, resp[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignSuccessIsOne(t *testing.T) {
|
||||
if SIGN_SUCCESS != 1 {
|
||||
t.Errorf("SIGN_SUCCESS = %d, must be 1", SIGN_SUCCESS)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignUnknownIsZero(t *testing.T) {
|
||||
if SIGN_UNKNOWN != 0 {
|
||||
t.Errorf("SIGN_UNKNOWN = %d, must be 0", SIGN_UNKNOWN)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRespIDValues(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
respID RespID
|
||||
value uint8
|
||||
}{
|
||||
{"SIGN_UNKNOWN", SIGN_UNKNOWN, 0},
|
||||
{"SIGN_SUCCESS", SIGN_SUCCESS, 1},
|
||||
{"SIGN_EFAILED", SIGN_EFAILED, 2},
|
||||
{"SIGN_EILLEGAL", SIGN_EILLEGAL, 3},
|
||||
{"SIGN_ESUSPEND", SIGN_ESUSPEND, 15},
|
||||
{"SIGN_EELIMINATE", SIGN_EELIMINATE, 16},
|
||||
{"SIGN_EIPADDR", SIGN_EIPADDR, 23},
|
||||
{"SIGN_EMAINTE", SIGN_EMAINTE, 30},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if uint8(tt.respID) != tt.value {
|
||||
t.Errorf("%s = %d, want %d", tt.name, uint8(tt.respID), tt.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnknownRespIDRange(t *testing.T) {
|
||||
unknownIDs := []RespID{UNK_32, UNK_33, UNK_34, UNK_35}
|
||||
expectedValues := []uint8{32, 33, 34, 35}
|
||||
|
||||
for i, id := range unknownIDs {
|
||||
if uint8(id) != expectedValues[i] {
|
||||
t.Errorf("Unknown ID %d = %d, want %d", i, uint8(id), expectedValues[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpecialRespIDs(t *testing.T) {
|
||||
if SIGN_XBRESPONSE != 36 {
|
||||
t.Errorf("SIGN_XBRESPONSE = %d, want 36", SIGN_XBRESPONSE)
|
||||
}
|
||||
if SIGN_EPSI != 37 {
|
||||
t.Errorf("SIGN_EPSI = %d, want 37", SIGN_EPSI)
|
||||
}
|
||||
if SIGN_EMBID_PSI != 38 {
|
||||
t.Errorf("SIGN_EMBID_PSI = %d, want 38", SIGN_EMBID_PSI)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeSignInFailureRespBoundary(t *testing.T) {
|
||||
resp := makeSignInFailureResp(RespID(0))
|
||||
if resp[0] != 0 {
|
||||
t.Errorf("makeSignInFailureResp(0) = %d, want 0", resp[0])
|
||||
}
|
||||
|
||||
resp = makeSignInFailureResp(RespID(255))
|
||||
if resp[0] != 255 {
|
||||
t.Errorf("makeSignInFailureResp(255) = %d, want 255", resp[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorRespIDsAreDifferent(t *testing.T) {
|
||||
seen := make(map[RespID]bool)
|
||||
errorCodes := []RespID{
|
||||
SIGN_UNKNOWN, SIGN_SUCCESS, SIGN_EFAILED, SIGN_EILLEGAL,
|
||||
SIGN_EALERT, SIGN_EABORT, SIGN_ERESPONSE, SIGN_EDATABASE,
|
||||
SIGN_EABSENCE, SIGN_ERESIGN, SIGN_ESUSPEND_D, SIGN_ELOCK,
|
||||
SIGN_EPASS, SIGN_ERIGHT, SIGN_EAUTH, SIGN_ESUSPEND,
|
||||
SIGN_EELIMINATE, SIGN_ECLOSE, SIGN_ECLOSE_EX, SIGN_EINTERVAL,
|
||||
SIGN_EMOVED, SIGN_ENOTREADY, SIGN_EALREADY, SIGN_EIPADDR,
|
||||
SIGN_EHANGAME, SIGN_UPD_ONLY, SIGN_EMBID, SIGN_ECOGCODE,
|
||||
SIGN_ETOKEN, SIGN_ECOGLINK, SIGN_EMAINTE, SIGN_EMAINTE_NOUPDATE,
|
||||
}
|
||||
|
||||
for _, code := range errorCodes {
|
||||
if seen[code] {
|
||||
t.Errorf("Duplicate RespID value: %d", code)
|
||||
}
|
||||
seen[code] = true
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailureRespIsMinimal(t *testing.T) {
|
||||
for i := RespID(0); i <= SIGN_EMBID_PSI; i++ {
|
||||
if i == SIGN_SUCCESS {
|
||||
continue
|
||||
}
|
||||
resp := makeSignInFailureResp(i)
|
||||
if len(resp) != 1 {
|
||||
t.Errorf("makeSignInFailureResp(%d) should be 1 byte, got %d", i, len(resp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewServer(t *testing.T) {
|
||||
cfg := &Config{
|
||||
Logger: nil,
|
||||
DB: nil,
|
||||
ErupeConfig: nil,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
if s == nil {
|
||||
t.Fatal("NewServer() returned nil")
|
||||
}
|
||||
if s.isShuttingDown {
|
||||
t.Error("New server should not be shutting down")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewServerWithNilConfig(t *testing.T) {
|
||||
cfg := &Config{}
|
||||
s := NewServer(cfg)
|
||||
if s == nil {
|
||||
t.Fatal("NewServer() returned nil for empty config")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerType(t *testing.T) {
|
||||
s := &Server{}
|
||||
if s.isShuttingDown {
|
||||
t.Error("Zero value server should not be shutting down")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigFields(t *testing.T) {
|
||||
cfg := &Config{
|
||||
Logger: nil,
|
||||
DB: nil,
|
||||
ErupeConfig: nil,
|
||||
}
|
||||
|
||||
if cfg.Logger != nil {
|
||||
t.Error("Config Logger should be nil")
|
||||
}
|
||||
if cfg.DB != nil {
|
||||
t.Error("Config DB should be nil")
|
||||
}
|
||||
if cfg.ErupeConfig != nil {
|
||||
t.Error("Config ErupeConfig should be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerStartAndShutdown(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
Sign: _config.Sign{
|
||||
Port: 0,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Logger: logger,
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
if s == nil {
|
||||
t.Fatal("NewServer() returned nil")
|
||||
}
|
||||
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Start() error: %v", err)
|
||||
}
|
||||
|
||||
if s.listener == nil {
|
||||
t.Error("Server listener should not be nil after Start()")
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
if s.isShuttingDown {
|
||||
t.Error("Server should not be shutting down after Start()")
|
||||
}
|
||||
s.Unlock()
|
||||
|
||||
s.Shutdown()
|
||||
|
||||
s.Lock()
|
||||
if !s.isShuttingDown {
|
||||
t.Error("Server should be shutting down after Shutdown()")
|
||||
}
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func TestServerStartWithInvalidPort(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
Sign: _config.Sign{
|
||||
Port: -1,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Logger: logger,
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
err := s.Start()
|
||||
|
||||
if err == nil {
|
||||
s.Shutdown()
|
||||
t.Error("Start() should fail with invalid port")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerMutex(t *testing.T) {
|
||||
s := &Server{}
|
||||
|
||||
s.Lock()
|
||||
s.Unlock()
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
s.Lock()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
s.Unlock()
|
||||
done <- true
|
||||
}()
|
||||
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
s.Lock()
|
||||
s.Unlock()
|
||||
|
||||
<-done
|
||||
}
|
||||
|
||||
func TestServerShutdownIdempotent(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
Sign: _config.Sign{
|
||||
Port: 0,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Logger: logger,
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Start() error: %v", err)
|
||||
}
|
||||
|
||||
s.Shutdown()
|
||||
|
||||
s.Lock()
|
||||
if !s.isShuttingDown {
|
||||
t.Error("Server should be shutting down")
|
||||
}
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func TestServerAcceptClientsExitsOnShutdown(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
Sign: _config.Sign{
|
||||
Port: 0,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Logger: logger,
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Start() error: %v", err)
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
s.Shutdown()
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
s.Lock()
|
||||
if !s.isShuttingDown {
|
||||
t.Error("Server should be marked as shutting down")
|
||||
}
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func TestServerHandleConnection(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
Sign: _config.Sign{
|
||||
Port: 0,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Logger: logger,
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Start() error: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
|
||||
addr := s.listener.Addr().String()
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
t.Fatalf("Dial() error: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
nullInit := make([]byte, 8)
|
||||
_, err = conn.Write(nullInit)
|
||||
if err != nil {
|
||||
t.Fatalf("Write() error: %v", err)
|
||||
}
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestServerHandleConnectionWithShortInit(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
Sign: _config.Sign{
|
||||
Port: 0,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Logger: logger,
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Start() error: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
|
||||
addr := s.listener.Addr().String()
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
t.Fatalf("Dial() error: %v", err)
|
||||
}
|
||||
|
||||
_, _ = conn.Write([]byte{0, 0, 0, 0})
|
||||
conn.Close()
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestServerHandleConnectionImmediateClose(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
Sign: _config.Sign{
|
||||
Port: 0,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Logger: logger,
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Start() error: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
|
||||
addr := s.listener.Addr().String()
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
t.Fatalf("Dial() error: %v", err)
|
||||
}
|
||||
conn.Close()
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestServerMultipleConnections(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
Sign: _config.Sign{
|
||||
Port: 0,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Logger: logger,
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Start() error: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
|
||||
addr := s.listener.Addr().String()
|
||||
|
||||
conns := make([]net.Conn, 3)
|
||||
for i := range conns {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
t.Fatalf("Dial() %d error: %v", i, err)
|
||||
}
|
||||
conns[i] = conn
|
||||
|
||||
nullInit := make([]byte, 8)
|
||||
_, _ = conn.Write(nullInit)
|
||||
}
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
for _, conn := range conns {
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerListenerAddress(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
erupeConfig := &_config.Config{
|
||||
Sign: _config.Sign{
|
||||
Port: 0,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Logger: logger,
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Start() error: %v", err)
|
||||
}
|
||||
defer s.Shutdown()
|
||||
|
||||
addr := s.listener.Addr()
|
||||
if addr == nil {
|
||||
t.Error("Listener address should not be nil")
|
||||
}
|
||||
|
||||
tcpAddr, ok := addr.(*net.TCPAddr)
|
||||
if !ok {
|
||||
t.Error("Listener address should be a TCP address")
|
||||
}
|
||||
|
||||
if tcpAddr.Port == 0 {
|
||||
t.Error("Listener port should be assigned")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user