mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
test: add unit tests for core packages
Add comprehensive test coverage for: - common/token: token generation and RNG tests - common/stringsupport: string encoding, CSV operations - common/byteframe: binary read/write operations - common/mhfcourse: course/subscription logic - network/crypt_packet: packet header parsing - network/binpacket: binary packet round-trips - network/mhfpacket: packet interface and opcode mapping - config: configuration struct and loading - server/entranceserver: response building - server/signserver: response ID constants - server/signv2server: HTTP endpoint validation - server/channelserver: session, semaphore, and handler tests All tests pass with race detector enabled.
This commit is contained in:
268
server/channelserver/handlers_test.go
Normal file
268
server/channelserver/handlers_test.go
Normal file
@@ -0,0 +1,268 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"erupe-ce/network"
|
||||
)
|
||||
|
||||
func TestHandlerTableInitialized(t *testing.T) {
|
||||
if handlerTable == nil {
|
||||
t.Fatal("handlerTable should be initialized by init()")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableHasEntries(t *testing.T) {
|
||||
if len(handlerTable) == 0 {
|
||||
t.Error("handlerTable should have entries")
|
||||
}
|
||||
|
||||
// Should have many handlers
|
||||
if len(handlerTable) < 100 {
|
||||
t.Errorf("handlerTable has %d entries, expected 100+", len(handlerTable))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableSystemPackets(t *testing.T) {
|
||||
// Test that key system packets have handlers
|
||||
systemPackets := []network.PacketID{
|
||||
network.MSG_HEAD,
|
||||
network.MSG_SYS_END,
|
||||
network.MSG_SYS_NOP,
|
||||
network.MSG_SYS_ACK,
|
||||
network.MSG_SYS_LOGIN,
|
||||
network.MSG_SYS_LOGOUT,
|
||||
network.MSG_SYS_PING,
|
||||
network.MSG_SYS_TIME,
|
||||
}
|
||||
|
||||
for _, opcode := range systemPackets {
|
||||
t.Run(opcode.String(), func(t *testing.T) {
|
||||
if _, ok := handlerTable[opcode]; !ok {
|
||||
t.Errorf("handler missing for %s", opcode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableStagePackets(t *testing.T) {
|
||||
// Test stage-related packet handlers
|
||||
stagePackets := []network.PacketID{
|
||||
network.MSG_SYS_CREATE_STAGE,
|
||||
network.MSG_SYS_STAGE_DESTRUCT,
|
||||
network.MSG_SYS_ENTER_STAGE,
|
||||
network.MSG_SYS_BACK_STAGE,
|
||||
network.MSG_SYS_MOVE_STAGE,
|
||||
network.MSG_SYS_LEAVE_STAGE,
|
||||
network.MSG_SYS_LOCK_STAGE,
|
||||
network.MSG_SYS_UNLOCK_STAGE,
|
||||
}
|
||||
|
||||
for _, opcode := range stagePackets {
|
||||
t.Run(opcode.String(), func(t *testing.T) {
|
||||
if _, ok := handlerTable[opcode]; !ok {
|
||||
t.Errorf("handler missing for stage packet %s", opcode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableBinaryPackets(t *testing.T) {
|
||||
// Test binary message handlers
|
||||
binaryPackets := []network.PacketID{
|
||||
network.MSG_SYS_CAST_BINARY,
|
||||
network.MSG_SYS_CASTED_BINARY,
|
||||
network.MSG_SYS_SET_STAGE_BINARY,
|
||||
network.MSG_SYS_GET_STAGE_BINARY,
|
||||
}
|
||||
|
||||
for _, opcode := range binaryPackets {
|
||||
t.Run(opcode.String(), func(t *testing.T) {
|
||||
if _, ok := handlerTable[opcode]; !ok {
|
||||
t.Errorf("handler missing for binary packet %s", opcode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableReservedPackets(t *testing.T) {
|
||||
// Reserved packets should still have handlers (usually no-ops)
|
||||
reservedPackets := []network.PacketID{
|
||||
network.MSG_SYS_reserve01,
|
||||
network.MSG_SYS_reserve02,
|
||||
network.MSG_SYS_reserve03,
|
||||
network.MSG_SYS_reserve04,
|
||||
network.MSG_SYS_reserve05,
|
||||
network.MSG_SYS_reserve06,
|
||||
network.MSG_SYS_reserve07,
|
||||
}
|
||||
|
||||
for _, opcode := range reservedPackets {
|
||||
t.Run(opcode.String(), func(t *testing.T) {
|
||||
if _, ok := handlerTable[opcode]; !ok {
|
||||
t.Errorf("handler missing for reserved packet %s", opcode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerFuncType(t *testing.T) {
|
||||
// Verify all handlers are valid functions
|
||||
for opcode, handler := range handlerTable {
|
||||
if handler == nil {
|
||||
t.Errorf("handler for %s is nil", opcode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableObjectPackets(t *testing.T) {
|
||||
objectPackets := []network.PacketID{
|
||||
network.MSG_SYS_ADD_OBJECT,
|
||||
network.MSG_SYS_DEL_OBJECT,
|
||||
network.MSG_SYS_DISP_OBJECT,
|
||||
network.MSG_SYS_HIDE_OBJECT,
|
||||
}
|
||||
|
||||
for _, opcode := range objectPackets {
|
||||
t.Run(opcode.String(), func(t *testing.T) {
|
||||
if _, ok := handlerTable[opcode]; !ok {
|
||||
t.Errorf("handler missing for object packet %s", opcode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableClientPackets(t *testing.T) {
|
||||
clientPackets := []network.PacketID{
|
||||
network.MSG_SYS_SET_STATUS,
|
||||
network.MSG_SYS_HIDE_CLIENT,
|
||||
network.MSG_SYS_ENUMERATE_CLIENT,
|
||||
}
|
||||
|
||||
for _, opcode := range clientPackets {
|
||||
t.Run(opcode.String(), func(t *testing.T) {
|
||||
if _, ok := handlerTable[opcode]; !ok {
|
||||
t.Errorf("handler missing for client packet %s", opcode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableSemaphorePackets(t *testing.T) {
|
||||
semaphorePackets := []network.PacketID{
|
||||
network.MSG_SYS_CREATE_ACQUIRE_SEMAPHORE,
|
||||
network.MSG_SYS_ACQUIRE_SEMAPHORE,
|
||||
network.MSG_SYS_RELEASE_SEMAPHORE,
|
||||
}
|
||||
|
||||
for _, opcode := range semaphorePackets {
|
||||
t.Run(opcode.String(), func(t *testing.T) {
|
||||
if _, ok := handlerTable[opcode]; !ok {
|
||||
t.Errorf("handler missing for semaphore packet %s", opcode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableMHFPackets(t *testing.T) {
|
||||
// Test some core MHF packets have handlers
|
||||
mhfPackets := []network.PacketID{
|
||||
network.MSG_MHF_SAVEDATA,
|
||||
network.MSG_MHF_LOADDATA,
|
||||
}
|
||||
|
||||
for _, opcode := range mhfPackets {
|
||||
t.Run(opcode.String(), func(t *testing.T) {
|
||||
if _, ok := handlerTable[opcode]; !ok {
|
||||
t.Errorf("handler missing for MHF packet %s", opcode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableEnumeratePackets(t *testing.T) {
|
||||
enumPackets := []network.PacketID{
|
||||
network.MSG_SYS_ENUMERATE_CLIENT,
|
||||
network.MSG_SYS_ENUMERATE_STAGE,
|
||||
}
|
||||
|
||||
for _, opcode := range enumPackets {
|
||||
t.Run(opcode.String(), func(t *testing.T) {
|
||||
if _, ok := handlerTable[opcode]; !ok {
|
||||
t.Errorf("handler missing for enumerate packet %s", opcode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableLogPackets(t *testing.T) {
|
||||
logPackets := []network.PacketID{
|
||||
network.MSG_SYS_TERMINAL_LOG,
|
||||
network.MSG_SYS_ISSUE_LOGKEY,
|
||||
network.MSG_SYS_RECORD_LOG,
|
||||
}
|
||||
|
||||
for _, opcode := range logPackets {
|
||||
t.Run(opcode.String(), func(t *testing.T) {
|
||||
if _, ok := handlerTable[opcode]; !ok {
|
||||
t.Errorf("handler missing for log packet %s", opcode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableFilePackets(t *testing.T) {
|
||||
filePackets := []network.PacketID{
|
||||
network.MSG_SYS_GET_FILE,
|
||||
}
|
||||
|
||||
for _, opcode := range filePackets {
|
||||
t.Run(opcode.String(), func(t *testing.T) {
|
||||
if _, ok := handlerTable[opcode]; !ok {
|
||||
t.Errorf("handler missing for file packet %s", opcode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableEchoPacket(t *testing.T) {
|
||||
if _, ok := handlerTable[network.MSG_SYS_ECHO]; !ok {
|
||||
t.Error("handler missing for MSG_SYS_ECHO")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableReserveStagePackets(t *testing.T) {
|
||||
reservePackets := []network.PacketID{
|
||||
network.MSG_SYS_RESERVE_STAGE,
|
||||
network.MSG_SYS_UNRESERVE_STAGE,
|
||||
network.MSG_SYS_SET_STAGE_PASS,
|
||||
network.MSG_SYS_WAIT_STAGE_BINARY,
|
||||
}
|
||||
|
||||
for _, opcode := range reservePackets {
|
||||
t.Run(opcode.String(), func(t *testing.T) {
|
||||
if _, ok := handlerTable[opcode]; !ok {
|
||||
t.Errorf("handler missing for reserve stage packet %s", opcode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableThresholdPacket(t *testing.T) {
|
||||
if _, ok := handlerTable[network.MSG_SYS_EXTEND_THRESHOLD]; !ok {
|
||||
t.Error("handler missing for MSG_SYS_EXTEND_THRESHOLD")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTableNoNilValues(t *testing.T) {
|
||||
nilCount := 0
|
||||
for opcode, handler := range handlerTable {
|
||||
if handler == nil {
|
||||
nilCount++
|
||||
t.Errorf("nil handler for opcode %s", opcode)
|
||||
}
|
||||
}
|
||||
if nilCount > 0 {
|
||||
t.Errorf("found %d nil handlers in handlerTable", nilCount)
|
||||
}
|
||||
}
|
||||
384
server/channelserver/sys_semaphore_test.go
Normal file
384
server/channelserver/sys_semaphore_test.go
Normal file
@@ -0,0 +1,384 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewSemaphore(t *testing.T) {
|
||||
server := createMockServer()
|
||||
server.semaphoreIndex = 6 // Start index (IDs 0-6 are reserved)
|
||||
|
||||
sema := NewSemaphore(server, "test_semaphore", 16)
|
||||
|
||||
if sema == nil {
|
||||
t.Fatal("NewSemaphore() returned nil")
|
||||
}
|
||||
if sema.id_semaphore != "test_semaphore" {
|
||||
t.Errorf("id_semaphore = %s, want test_semaphore", sema.id_semaphore)
|
||||
}
|
||||
if sema.maxPlayers != 16 {
|
||||
t.Errorf("maxPlayers = %d, want 16", sema.maxPlayers)
|
||||
}
|
||||
if sema.clients == nil {
|
||||
t.Error("clients map should be initialized")
|
||||
}
|
||||
if sema.reservedClientSlots == nil {
|
||||
t.Error("reservedClientSlots map should be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSemaphoreIDIncrement(t *testing.T) {
|
||||
server := createMockServer()
|
||||
server.semaphoreIndex = 6
|
||||
|
||||
sema1 := NewSemaphore(server, "sema1", 4)
|
||||
sema2 := NewSemaphore(server, "sema2", 4)
|
||||
sema3 := NewSemaphore(server, "sema3", 4)
|
||||
|
||||
// IDs should increment
|
||||
if sema1.id == sema2.id {
|
||||
t.Error("semaphore IDs should be unique")
|
||||
}
|
||||
if sema2.id == sema3.id {
|
||||
t.Error("semaphore IDs should be unique")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreClients(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
session1 := createMockSession(100, server)
|
||||
session2 := createMockSession(200, server)
|
||||
|
||||
// Add clients
|
||||
sema.clients[session1] = session1.charID
|
||||
sema.clients[session2] = session2.charID
|
||||
|
||||
if len(sema.clients) != 2 {
|
||||
t.Errorf("clients count = %d, want 2", len(sema.clients))
|
||||
}
|
||||
|
||||
// Verify client IDs
|
||||
if sema.clients[session1] != 100 {
|
||||
t.Errorf("clients[session1] = %d, want 100", sema.clients[session1])
|
||||
}
|
||||
if sema.clients[session2] != 200 {
|
||||
t.Errorf("clients[session2] = %d, want 200", sema.clients[session2])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreReservedSlots(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
// Reserve slots
|
||||
sema.reservedClientSlots[100] = nil
|
||||
sema.reservedClientSlots[200] = nil
|
||||
|
||||
if len(sema.reservedClientSlots) != 2 {
|
||||
t.Errorf("reservedClientSlots count = %d, want 2", len(sema.reservedClientSlots))
|
||||
}
|
||||
|
||||
// Check existence
|
||||
if _, ok := sema.reservedClientSlots[100]; !ok {
|
||||
t.Error("charID 100 should be reserved")
|
||||
}
|
||||
if _, ok := sema.reservedClientSlots[200]; !ok {
|
||||
t.Error("charID 200 should be reserved")
|
||||
}
|
||||
if _, ok := sema.reservedClientSlots[300]; ok {
|
||||
t.Error("charID 300 should not be reserved")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreRemoveClient(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
session := createMockSession(100, server)
|
||||
sema.clients[session] = session.charID
|
||||
|
||||
// Remove client
|
||||
delete(sema.clients, session)
|
||||
|
||||
if len(sema.clients) != 0 {
|
||||
t.Errorf("clients count = %d, want 0 after delete", len(sema.clients))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreMaxPlayers(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
maxPlayers uint16
|
||||
}{
|
||||
{"quest party", 4},
|
||||
{"small event", 16},
|
||||
{"raviente", 32},
|
||||
{"large event", 64},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, tt.name, tt.maxPlayers)
|
||||
|
||||
if sema.maxPlayers != tt.maxPlayers {
|
||||
t.Errorf("maxPlayers = %d, want %d", sema.maxPlayers, tt.maxPlayers)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreBroadcastMHF(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
session1 := createMockSession(100, server)
|
||||
session2 := createMockSession(200, server)
|
||||
session3 := createMockSession(300, server)
|
||||
|
||||
sema.clients[session1] = session1.charID
|
||||
sema.clients[session2] = session2.charID
|
||||
sema.clients[session3] = session3.charID
|
||||
|
||||
pkt := &mockPacket{opcode: 0x1234}
|
||||
|
||||
// Broadcast excluding session1
|
||||
sema.BroadcastMHF(pkt, session1)
|
||||
|
||||
// session2 and session3 should receive
|
||||
select {
|
||||
case data := <-session2.sendPackets:
|
||||
if len(data.data) == 0 {
|
||||
t.Error("session2 received empty data")
|
||||
}
|
||||
default:
|
||||
t.Error("session2 did not receive broadcast")
|
||||
}
|
||||
|
||||
select {
|
||||
case data := <-session3.sendPackets:
|
||||
if len(data.data) == 0 {
|
||||
t.Error("session3 received empty data")
|
||||
}
|
||||
default:
|
||||
t.Error("session3 did not receive broadcast")
|
||||
}
|
||||
|
||||
// session1 should NOT receive (it was ignored)
|
||||
select {
|
||||
case <-session1.sendPackets:
|
||||
t.Error("session1 should not receive broadcast (it was ignored)")
|
||||
default:
|
||||
// Expected - no data for session1
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreBroadcastRavi(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "raviente", 32)
|
||||
|
||||
session1 := createMockSession(100, server)
|
||||
session2 := createMockSession(200, server)
|
||||
|
||||
sema.clients[session1] = session1.charID
|
||||
sema.clients[session2] = session2.charID
|
||||
|
||||
pkt := &mockPacket{opcode: 0x5678}
|
||||
|
||||
// Broadcast to all (no ignored session)
|
||||
sema.BroadcastRavi(pkt)
|
||||
|
||||
// Both should receive
|
||||
select {
|
||||
case data := <-session1.sendPackets:
|
||||
if len(data.data) == 0 {
|
||||
t.Error("session1 received empty data")
|
||||
}
|
||||
default:
|
||||
t.Error("session1 did not receive Ravi broadcast")
|
||||
}
|
||||
|
||||
select {
|
||||
case data := <-session2.sendPackets:
|
||||
if len(data.data) == 0 {
|
||||
t.Error("session2 received empty data")
|
||||
}
|
||||
default:
|
||||
t.Error("session2 did not receive Ravi broadcast")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreBroadcastToAll(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
session1 := createMockSession(100, server)
|
||||
session2 := createMockSession(200, server)
|
||||
|
||||
sema.clients[session1] = session1.charID
|
||||
sema.clients[session2] = session2.charID
|
||||
|
||||
pkt := &mockPacket{opcode: 0x1234}
|
||||
|
||||
// Broadcast to all (nil ignored session)
|
||||
sema.BroadcastMHF(pkt, nil)
|
||||
|
||||
// Both should receive
|
||||
count := 0
|
||||
select {
|
||||
case <-session1.sendPackets:
|
||||
count++
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case <-session2.sendPackets:
|
||||
count++
|
||||
default:
|
||||
}
|
||||
|
||||
if count != 2 {
|
||||
t.Errorf("expected 2 broadcasts, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreRWMutex(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
// Test that RWMutex works
|
||||
sema.RLock()
|
||||
_ = len(sema.clients) // Read operation
|
||||
sema.RUnlock()
|
||||
|
||||
sema.Lock()
|
||||
sema.clients[createMockSession(100, server)] = 100 // Write operation
|
||||
sema.Unlock()
|
||||
}
|
||||
|
||||
func TestSemaphoreConcurrentAccess(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 100)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Concurrent writers
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 100; j++ {
|
||||
session := createMockSession(uint32(id*100+j), server)
|
||||
sema.Lock()
|
||||
sema.clients[session] = session.charID
|
||||
sema.Unlock()
|
||||
|
||||
sema.Lock()
|
||||
delete(sema.clients, session)
|
||||
sema.Unlock()
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Concurrent readers
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 100; j++ {
|
||||
sema.RLock()
|
||||
_ = len(sema.clients)
|
||||
sema.RUnlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestSemaphoreEmptyBroadcast(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
pkt := &mockPacket{opcode: 0x1234}
|
||||
|
||||
// Should not panic with no clients
|
||||
sema.BroadcastMHF(pkt, nil)
|
||||
sema.BroadcastRavi(pkt)
|
||||
}
|
||||
|
||||
func TestSemaphoreIDString(t *testing.T) {
|
||||
server := createMockServer()
|
||||
|
||||
tests := []string{
|
||||
"quest_001",
|
||||
"raviente_phase1",
|
||||
"tournament_round3",
|
||||
"diva_defense",
|
||||
}
|
||||
|
||||
for _, id := range tests {
|
||||
sema := NewSemaphore(server, id, 4)
|
||||
if sema.id_semaphore != id {
|
||||
t.Errorf("id_semaphore = %s, want %s", sema.id_semaphore, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreNumericID(t *testing.T) {
|
||||
server := createMockServer()
|
||||
server.semaphoreIndex = 6 // IDs 0-6 reserved
|
||||
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
// First semaphore should get ID 7
|
||||
if sema.id < 7 {
|
||||
t.Errorf("semaphore id = %d, should be >= 7", sema.id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreReserveAndRelease(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
// Reserve
|
||||
sema.reservedClientSlots[100] = nil
|
||||
if _, ok := sema.reservedClientSlots[100]; !ok {
|
||||
t.Error("slot 100 should be reserved")
|
||||
}
|
||||
|
||||
// Release
|
||||
delete(sema.reservedClientSlots, 100)
|
||||
if _, ok := sema.reservedClientSlots[100]; ok {
|
||||
t.Error("slot 100 should be released")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreClientAndReservedSeparate(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
session := createMockSession(100, server)
|
||||
|
||||
// Client in active clients
|
||||
sema.clients[session] = 100
|
||||
|
||||
// Same charID reserved
|
||||
sema.reservedClientSlots[100] = nil
|
||||
|
||||
// Both should exist independently
|
||||
if _, ok := sema.clients[session]; !ok {
|
||||
t.Error("session should be in active clients")
|
||||
}
|
||||
if _, ok := sema.reservedClientSlots[100]; !ok {
|
||||
t.Error("charID 100 should be reserved")
|
||||
}
|
||||
|
||||
// Remove from one doesn't affect other
|
||||
delete(sema.clients, session)
|
||||
if _, ok := sema.reservedClientSlots[100]; !ok {
|
||||
t.Error("charID 100 should still be reserved after removing from clients")
|
||||
}
|
||||
}
|
||||
375
server/channelserver/sys_session_test.go
Normal file
375
server/channelserver/sys_session_test.go
Normal file
@@ -0,0 +1,375 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/stringstack"
|
||||
"erupe-ce/network/clientctx"
|
||||
)
|
||||
|
||||
func TestSessionStructInitialization(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(12345, server)
|
||||
|
||||
if session.charID != 12345 {
|
||||
t.Errorf("charID = %d, want 12345", session.charID)
|
||||
}
|
||||
if session.Name != "TestPlayer" {
|
||||
t.Errorf("Name = %s, want TestPlayer", session.Name)
|
||||
}
|
||||
if session.server != server {
|
||||
t.Error("server reference not set correctly")
|
||||
}
|
||||
if session.clientContext == nil {
|
||||
t.Error("clientContext should not be nil")
|
||||
}
|
||||
if session.sendPackets == nil {
|
||||
t.Error("sendPackets channel should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionSendPacketChannel(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Test that channel can receive packets
|
||||
testData := []byte{0x01, 0x02, 0x03}
|
||||
session.sendPackets <- packet{data: testData, nonBlocking: false}
|
||||
|
||||
select {
|
||||
case pkt := <-session.sendPackets:
|
||||
if len(pkt.data) != 3 {
|
||||
t.Errorf("packet data len = %d, want 3", len(pkt.data))
|
||||
}
|
||||
if pkt.data[0] != 0x01 {
|
||||
t.Errorf("packet data[0] = %d, want 1", pkt.data[0])
|
||||
}
|
||||
default:
|
||||
t.Error("failed to receive packet from channel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionSendPacketChannelNonBlocking(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Fill the channel
|
||||
for i := 0; i < 20; i++ {
|
||||
session.sendPackets <- packet{data: []byte{byte(i)}, nonBlocking: true}
|
||||
}
|
||||
|
||||
// Non-blocking send to full channel should not block
|
||||
done := make(chan bool, 1)
|
||||
go func() {
|
||||
select {
|
||||
case session.sendPackets <- packet{data: []byte{0xFF}, nonBlocking: true}:
|
||||
// Managed to send (channel had room)
|
||||
default:
|
||||
// Channel full, this is expected
|
||||
}
|
||||
done <- true
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
// Success - non-blocking worked
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
t.Error("non-blocking send blocked")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPacketStruct(t *testing.T) {
|
||||
pkt := packet{
|
||||
data: []byte{0x01, 0x02, 0x03},
|
||||
nonBlocking: true,
|
||||
}
|
||||
|
||||
if len(pkt.data) != 3 {
|
||||
t.Errorf("packet data len = %d, want 3", len(pkt.data))
|
||||
}
|
||||
if !pkt.nonBlocking {
|
||||
t.Error("nonBlocking should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPacketStructBlocking(t *testing.T) {
|
||||
pkt := packet{
|
||||
data: []byte{0xDE, 0xAD, 0xBE, 0xEF},
|
||||
nonBlocking: false,
|
||||
}
|
||||
|
||||
if len(pkt.data) != 4 {
|
||||
t.Errorf("packet data len = %d, want 4", len(pkt.data))
|
||||
}
|
||||
if pkt.nonBlocking {
|
||||
t.Error("nonBlocking should be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionClosedFlag(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
if session.closed {
|
||||
t.Error("new session should not be closed")
|
||||
}
|
||||
|
||||
session.closed = true
|
||||
|
||||
if !session.closed {
|
||||
t.Error("session closed flag should be settable")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionStageState(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Initially should have no stage
|
||||
if session.userEnteredStage {
|
||||
t.Error("new session should not have entered stage")
|
||||
}
|
||||
if session.stageID != "" {
|
||||
t.Errorf("stageID should be empty, got %s", session.stageID)
|
||||
}
|
||||
if session.stage != nil {
|
||||
t.Error("stage should be nil initially")
|
||||
}
|
||||
|
||||
// Set stage state
|
||||
session.userEnteredStage = true
|
||||
session.stageID = "test_stage_001"
|
||||
|
||||
if !session.userEnteredStage {
|
||||
t.Error("userEnteredStage should be set")
|
||||
}
|
||||
if session.stageID != "test_stage_001" {
|
||||
t.Errorf("stageID = %s, want test_stage_001", session.stageID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionStageMoveStack(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
session.stageMoveStack = stringstack.New()
|
||||
|
||||
// Push some stages
|
||||
session.stageMoveStack.Push("stage1")
|
||||
session.stageMoveStack.Push("stage2")
|
||||
session.stageMoveStack.Push("stage3")
|
||||
|
||||
// Pop and verify order (LIFO)
|
||||
if v, err := session.stageMoveStack.Pop(); err != nil || v != "stage3" {
|
||||
t.Errorf("Pop() = %s, want stage3", v)
|
||||
}
|
||||
if v, err := session.stageMoveStack.Pop(); err != nil || v != "stage2" {
|
||||
t.Errorf("Pop() = %s, want stage2", v)
|
||||
}
|
||||
if v, err := session.stageMoveStack.Pop(); err != nil || v != "stage1" {
|
||||
t.Errorf("Pop() = %s, want stage1", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionMailState(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Initial mail state
|
||||
if session.mailAccIndex != 0 {
|
||||
t.Errorf("mailAccIndex = %d, want 0", session.mailAccIndex)
|
||||
}
|
||||
if session.mailList != nil && len(session.mailList) > 0 {
|
||||
t.Error("mailList should be empty initially")
|
||||
}
|
||||
|
||||
// Add mail
|
||||
session.mailList = []int{100, 101, 102}
|
||||
session.mailAccIndex = 3
|
||||
|
||||
if len(session.mailList) != 3 {
|
||||
t.Errorf("mailList len = %d, want 3", len(session.mailList))
|
||||
}
|
||||
if session.mailAccIndex != 3 {
|
||||
t.Errorf("mailAccIndex = %d, want 3", session.mailAccIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionToken(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
session.token = "abc123def456"
|
||||
|
||||
if session.token != "abc123def456" {
|
||||
t.Errorf("token = %s, want abc123def456", session.token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionGuildState(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
session.prevGuildID = 42
|
||||
|
||||
if session.prevGuildID != 42 {
|
||||
t.Errorf("prevGuildID = %d, want 42", session.prevGuildID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionKQF(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Set KQF data
|
||||
session.kqf = []byte{0x01, 0x02, 0x03, 0x04}
|
||||
session.kqfOverride = true
|
||||
|
||||
if len(session.kqf) != 4 {
|
||||
t.Errorf("kqf len = %d, want 4", len(session.kqf))
|
||||
}
|
||||
if !session.kqfOverride {
|
||||
t.Error("kqfOverride should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionClientContext(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
if session.clientContext == nil {
|
||||
t.Fatal("clientContext should not be nil")
|
||||
}
|
||||
|
||||
// Verify clientContext is usable
|
||||
ctx := session.clientContext
|
||||
_ = ctx // Just verify it's accessible
|
||||
}
|
||||
|
||||
func TestSessionReservationStage(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
if session.reservationStage != nil {
|
||||
t.Error("reservationStage should be nil initially")
|
||||
}
|
||||
|
||||
// Set reservation stage
|
||||
stage := NewStage("quest_stage")
|
||||
session.reservationStage = stage
|
||||
|
||||
if session.reservationStage != stage {
|
||||
t.Error("reservationStage should be set correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionStagePass(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
session.stagePass = "secret123"
|
||||
|
||||
if session.stagePass != "secret123" {
|
||||
t.Errorf("stagePass = %s, want secret123", session.stagePass)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionLogKey(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
session.logKey = []byte{0xDE, 0xAD, 0xBE, 0xEF}
|
||||
|
||||
if len(session.logKey) != 4 {
|
||||
t.Errorf("logKey len = %d, want 4", len(session.logKey))
|
||||
}
|
||||
if session.logKey[0] != 0xDE {
|
||||
t.Errorf("logKey[0] = %x, want 0xDE", session.logKey[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionSessionStart(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Set session start time
|
||||
now := time.Now().Unix()
|
||||
session.sessionStart = now
|
||||
|
||||
if session.sessionStart != now {
|
||||
t.Errorf("sessionStart = %d, want %d", session.sessionStart, now)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnoredOpcode(t *testing.T) {
|
||||
// Test that certain opcodes are ignored
|
||||
tests := []struct {
|
||||
name string
|
||||
opcode uint16
|
||||
ignored bool
|
||||
}{
|
||||
// These should be ignored based on ignoreList
|
||||
{"MSG_SYS_END is ignored", 0x0002, true}, // Assuming MSG_SYS_END value
|
||||
// We can't test exact values without importing network package constants
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Note: This test is limited since ignored() uses network.PacketID
|
||||
// which we can't easily instantiate without the exact enum values
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionMutex(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Verify session has mutex (via embedding)
|
||||
// This should not deadlock
|
||||
session.Lock()
|
||||
session.charID = 999
|
||||
session.Unlock()
|
||||
|
||||
if session.charID != 999 {
|
||||
t.Errorf("charID = %d, want 999 after lock/unlock", session.charID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionConcurrentAccess(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
done := make(chan bool, 2)
|
||||
|
||||
// Concurrent writers
|
||||
go func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
session.Lock()
|
||||
session.charID = uint32(i)
|
||||
session.Unlock()
|
||||
}
|
||||
done <- true
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
session.Lock()
|
||||
_ = session.charID
|
||||
session.Unlock()
|
||||
}
|
||||
done <- true
|
||||
}()
|
||||
|
||||
<-done
|
||||
<-done
|
||||
}
|
||||
|
||||
func TestClientContextStruct(t *testing.T) {
|
||||
ctx := &clientctx.ClientContext{}
|
||||
|
||||
// Verify the struct is usable
|
||||
if ctx == nil {
|
||||
t.Error("ClientContext should be creatable")
|
||||
}
|
||||
}
|
||||
139
server/entranceserver/make_resp_test.go
Normal file
139
server/entranceserver/make_resp_test.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package entranceserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMakeHeader(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
respType string
|
||||
entryCount uint16
|
||||
key byte
|
||||
}{
|
||||
{"empty data", []byte{}, "SV2", 0, 0x00},
|
||||
{"single byte", []byte{0x01}, "SVR", 1, 0x00},
|
||||
{"multiple bytes", []byte{0x01, 0x02, 0x03, 0x04}, "SV2", 2, 0x00},
|
||||
{"with key", []byte{0xDE, 0xAD, 0xBE, 0xEF}, "USR", 5, 0x42},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := makeHeader(tt.data, tt.respType, tt.entryCount, tt.key)
|
||||
|
||||
// Result should not be empty
|
||||
if len(result) == 0 {
|
||||
t.Error("makeHeader() returned empty result")
|
||||
}
|
||||
|
||||
// First byte should be the key
|
||||
if result[0] != tt.key {
|
||||
t.Errorf("makeHeader() first byte = %x, want %x", result[0], tt.key)
|
||||
}
|
||||
|
||||
// Result should be longer than just the key
|
||||
if len(result) <= 1 {
|
||||
t.Error("makeHeader() result too short")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeHeaderEncryption(t *testing.T) {
|
||||
data := []byte{0x01, 0x02, 0x03, 0x04}
|
||||
|
||||
result1 := makeHeader(data, "SV2", 1, 0x00)
|
||||
result2 := makeHeader(data, "SV2", 1, 0x01)
|
||||
|
||||
// Different keys should produce different encrypted output
|
||||
if bytes.Equal(result1, result2) {
|
||||
t.Error("makeHeader() with different keys should produce different output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeHeaderRespTypes(t *testing.T) {
|
||||
data := []byte{0x01}
|
||||
|
||||
// Test different response types produce valid output
|
||||
types := []string{"SV2", "SVR", "USR"}
|
||||
|
||||
for _, respType := range types {
|
||||
t.Run(respType, func(t *testing.T) {
|
||||
result := makeHeader(data, respType, 1, 0x00)
|
||||
if len(result) == 0 {
|
||||
t.Errorf("makeHeader() with type %s returned empty result", respType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeHeaderEmptyData(t *testing.T) {
|
||||
// Empty data should still produce a valid (shorter) header
|
||||
result := makeHeader([]byte{}, "SV2", 0, 0x00)
|
||||
|
||||
if len(result) == 0 {
|
||||
t.Error("makeHeader() with empty data returned empty result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeHeaderLargeData(t *testing.T) {
|
||||
// Test with larger data
|
||||
data := make([]byte, 1000)
|
||||
for i := range data {
|
||||
data[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
result := makeHeader(data, "SV2", 100, 0x55)
|
||||
|
||||
if len(result) == 0 {
|
||||
t.Error("makeHeader() with large data returned empty result")
|
||||
}
|
||||
|
||||
// Result should be data + overhead
|
||||
if len(result) <= len(data) {
|
||||
t.Error("makeHeader() result should be larger than input data due to header")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeHeaderEntryCount(t *testing.T) {
|
||||
data := []byte{0x01, 0x02}
|
||||
|
||||
// Different entry counts should work
|
||||
for _, count := range []uint16{0, 1, 10, 100, 65535} {
|
||||
result := makeHeader(data, "SV2", count, 0x00)
|
||||
if len(result) == 0 {
|
||||
t.Errorf("makeHeader() with entryCount=%d returned empty result", count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeHeaderDecryptable(t *testing.T) {
|
||||
data := []byte{0x01, 0x02, 0x03, 0x04}
|
||||
key := byte(0x00)
|
||||
|
||||
result := makeHeader(data, "SV2", 1, key)
|
||||
|
||||
// Remove key byte and decrypt
|
||||
encrypted := result[1:]
|
||||
decrypted := DecryptBin8(encrypted, key)
|
||||
|
||||
// Decrypted data should start with "SV2"
|
||||
if len(decrypted) >= 3 && string(decrypted[:3]) != "SV2" {
|
||||
t.Errorf("makeHeader() decrypted data should start with SV2, got %s", string(decrypted[:3]))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeHeaderConsistency(t *testing.T) {
|
||||
data := []byte{0x01, 0x02, 0x03}
|
||||
key := byte(0x10)
|
||||
|
||||
// Same input should produce same output
|
||||
result1 := makeHeader(data, "SV2", 5, key)
|
||||
result2 := makeHeader(data, "SV2", 5, key)
|
||||
|
||||
if !bytes.Equal(result1, result2) {
|
||||
t.Error("makeHeader() with same input should produce same output")
|
||||
}
|
||||
}
|
||||
212
server/signserver/sign_server_test.go
Normal file
212
server/signserver/sign_server_test.go
Normal file
@@ -0,0 +1,212 @@
|
||||
package signserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRespIDConstants(t *testing.T) {
|
||||
tests := []struct {
|
||||
respID RespID
|
||||
value uint16
|
||||
}{
|
||||
{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 uint16(tt.respID) != tt.value {
|
||||
t.Errorf("RespID = %d, want %d", uint16(tt.respID), tt.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRespIDType(t *testing.T) {
|
||||
// Verify RespID is based on uint16
|
||||
var r RespID = 0xFFFF
|
||||
if uint16(r) != 0xFFFF {
|
||||
t.Errorf("RespID max value = %d, want %d", uint16(r), 0xFFFF)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
// Test all possible RespID values 0-39
|
||||
for i := uint16(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] != uint8(i) {
|
||||
t.Errorf("makeSignInFailureResp(%d) = %d", i, resp[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignSuccessIsOne(t *testing.T) {
|
||||
// SIGN_SUCCESS must be 1 for the protocol to work correctly
|
||||
if SIGN_SUCCESS != 1 {
|
||||
t.Errorf("SIGN_SUCCESS = %d, must be 1", SIGN_SUCCESS)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignUnknownIsZero(t *testing.T) {
|
||||
// SIGN_UNKNOWN must be 0 as the zero value
|
||||
if SIGN_UNKNOWN != 0 {
|
||||
t.Errorf("SIGN_UNKNOWN = %d, must be 0", SIGN_UNKNOWN)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRespIDValues(t *testing.T) {
|
||||
// Test specific RespID values are correct
|
||||
tests := []struct {
|
||||
name string
|
||||
respID RespID
|
||||
value uint16
|
||||
}{
|
||||
{"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 uint16(tt.respID) != tt.value {
|
||||
t.Errorf("%s = %d, want %d", tt.name, uint16(tt.respID), tt.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnknownRespIDRange(t *testing.T) {
|
||||
// Test the unknown IDs 32-35
|
||||
unknownIDs := []RespID{UNK_32, UNK_33, UNK_34, UNK_35}
|
||||
expectedValues := []uint16{32, 33, 34, 35}
|
||||
|
||||
for i, id := range unknownIDs {
|
||||
if uint16(id) != expectedValues[i] {
|
||||
t.Errorf("Unknown ID %d = %d, want %d", i, uint16(id), expectedValues[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpecialRespIDs(t *testing.T) {
|
||||
// Test platform-specific IDs
|
||||
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) {
|
||||
// Test boundary values
|
||||
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) {
|
||||
// Ensure all error codes are unique
|
||||
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) {
|
||||
// Failure response should be exactly 1 byte for efficiency
|
||||
for i := RespID(0); i <= SIGN_EMBID_PSI; i++ {
|
||||
if i == SIGN_SUCCESS {
|
||||
continue // Success has different format
|
||||
}
|
||||
resp := makeSignInFailureResp(i)
|
||||
if len(resp) != 1 {
|
||||
t.Errorf("makeSignInFailureResp(%d) should be 1 byte, got %d", i, len(resp))
|
||||
}
|
||||
}
|
||||
}
|
||||
349
server/signv2server/endpoints_test.go
Normal file
349
server/signv2server/endpoints_test.go
Normal file
@@ -0,0 +1,349 @@
|
||||
package signv2server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// mockServer creates a Server with minimal dependencies for testing
|
||||
func mockServer() *Server {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
return &Server{
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func TestLauncherEndpoint(t *testing.T) {
|
||||
s := mockServer()
|
||||
|
||||
req := httptest.NewRequest("GET", "/launcher", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.Launcher(w, req)
|
||||
|
||||
resp := w.Result()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Launcher() status = %d, want %d", resp.StatusCode, http.StatusOK)
|
||||
}
|
||||
|
||||
var data struct {
|
||||
Important []LauncherMessage `json:"important"`
|
||||
Normal []LauncherMessage `json:"normal"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||
t.Fatalf("Failed to decode response: %v", err)
|
||||
}
|
||||
|
||||
// Should have important messages
|
||||
if len(data.Important) == 0 {
|
||||
t.Error("Launcher() should return important messages")
|
||||
}
|
||||
|
||||
// Should have normal messages
|
||||
if len(data.Normal) == 0 {
|
||||
t.Error("Launcher() should return normal messages")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLauncherMessageStructure(t *testing.T) {
|
||||
s := mockServer()
|
||||
|
||||
req := httptest.NewRequest("GET", "/launcher", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.Launcher(w, req)
|
||||
|
||||
var data struct {
|
||||
Important []LauncherMessage `json:"important"`
|
||||
Normal []LauncherMessage `json:"normal"`
|
||||
}
|
||||
json.NewDecoder(w.Result().Body).Decode(&data)
|
||||
|
||||
// Check important messages have required fields
|
||||
for _, msg := range data.Important {
|
||||
if msg.Message == "" {
|
||||
t.Error("LauncherMessage.Message should not be empty")
|
||||
}
|
||||
if msg.Date == 0 {
|
||||
t.Error("LauncherMessage.Date should not be zero")
|
||||
}
|
||||
if msg.Link == "" {
|
||||
t.Error("LauncherMessage.Link should not be empty")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginEndpointInvalidJSON(t *testing.T) {
|
||||
s := mockServer()
|
||||
|
||||
// Send invalid JSON
|
||||
req := httptest.NewRequest("POST", "/login", bytes.NewReader([]byte("not json")))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.Login(w, req)
|
||||
|
||||
resp := w.Result()
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Errorf("Login() with invalid JSON status = %d, want %d", resp.StatusCode, http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterEndpointInvalidJSON(t *testing.T) {
|
||||
s := mockServer()
|
||||
|
||||
req := httptest.NewRequest("POST", "/register", bytes.NewReader([]byte("invalid")))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.Register(w, req)
|
||||
|
||||
resp := w.Result()
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Errorf("Register() with invalid JSON status = %d, want %d", resp.StatusCode, http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateCharacterEndpointInvalidJSON(t *testing.T) {
|
||||
s := mockServer()
|
||||
|
||||
req := httptest.NewRequest("POST", "/character/create", bytes.NewReader([]byte("invalid")))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.CreateCharacter(w, req)
|
||||
|
||||
resp := w.Result()
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Errorf("CreateCharacter() with invalid JSON status = %d, want %d", resp.StatusCode, http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteCharacterEndpointInvalidJSON(t *testing.T) {
|
||||
s := mockServer()
|
||||
|
||||
req := httptest.NewRequest("POST", "/character/delete", bytes.NewReader([]byte("invalid")))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.DeleteCharacter(w, req)
|
||||
|
||||
resp := w.Result()
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Errorf("DeleteCharacter() with invalid JSON status = %d, want %d", resp.StatusCode, http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLauncherMessageStruct(t *testing.T) {
|
||||
msg := LauncherMessage{
|
||||
Message: "Test Message",
|
||||
Date: 1234567890,
|
||||
Link: "https://example.com",
|
||||
}
|
||||
|
||||
if msg.Message != "Test Message" {
|
||||
t.Errorf("Message = %s, want Test Message", msg.Message)
|
||||
}
|
||||
if msg.Date != 1234567890 {
|
||||
t.Errorf("Date = %d, want 1234567890", msg.Date)
|
||||
}
|
||||
if msg.Link != "https://example.com" {
|
||||
t.Errorf("Link = %s, want https://example.com", msg.Link)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterStruct(t *testing.T) {
|
||||
char := Character{
|
||||
ID: 1,
|
||||
Name: "TestHunter",
|
||||
IsFemale: true,
|
||||
Weapon: 5,
|
||||
HR: 999,
|
||||
GR: 100,
|
||||
LastLogin: 1234567890,
|
||||
}
|
||||
|
||||
if char.ID != 1 {
|
||||
t.Errorf("ID = %d, want 1", char.ID)
|
||||
}
|
||||
if char.Name != "TestHunter" {
|
||||
t.Errorf("Name = %s, want TestHunter", char.Name)
|
||||
}
|
||||
if char.IsFemale != true {
|
||||
t.Error("IsFemale should be true")
|
||||
}
|
||||
if char.Weapon != 5 {
|
||||
t.Errorf("Weapon = %d, want 5", char.Weapon)
|
||||
}
|
||||
if char.HR != 999 {
|
||||
t.Errorf("HR = %d, want 999", char.HR)
|
||||
}
|
||||
if char.GR != 100 {
|
||||
t.Errorf("GR = %d, want 100", char.GR)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLauncherMessageJSONTags(t *testing.T) {
|
||||
msg := LauncherMessage{
|
||||
Message: "Test",
|
||||
Date: 12345,
|
||||
Link: "http://test.com",
|
||||
}
|
||||
|
||||
data, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal error: %v", err)
|
||||
}
|
||||
|
||||
var decoded map[string]interface{}
|
||||
json.Unmarshal(data, &decoded)
|
||||
|
||||
if _, ok := decoded["message"]; !ok {
|
||||
t.Error("JSON should have 'message' key")
|
||||
}
|
||||
if _, ok := decoded["date"]; !ok {
|
||||
t.Error("JSON should have 'date' key")
|
||||
}
|
||||
if _, ok := decoded["link"]; !ok {
|
||||
t.Error("JSON should have 'link' key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterJSONTags(t *testing.T) {
|
||||
char := Character{
|
||||
ID: 1,
|
||||
Name: "Test",
|
||||
IsFemale: true,
|
||||
Weapon: 3,
|
||||
HR: 50,
|
||||
GR: 10,
|
||||
LastLogin: 9999,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(char)
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal error: %v", err)
|
||||
}
|
||||
|
||||
var decoded map[string]interface{}
|
||||
json.Unmarshal(data, &decoded)
|
||||
|
||||
if _, ok := decoded["id"]; !ok {
|
||||
t.Error("JSON should have 'id' key")
|
||||
}
|
||||
if _, ok := decoded["name"]; !ok {
|
||||
t.Error("JSON should have 'name' key")
|
||||
}
|
||||
if _, ok := decoded["isFemale"]; !ok {
|
||||
t.Error("JSON should have 'isFemale' key")
|
||||
}
|
||||
if _, ok := decoded["weapon"]; !ok {
|
||||
t.Error("JSON should have 'weapon' key")
|
||||
}
|
||||
if _, ok := decoded["hr"]; !ok {
|
||||
t.Error("JSON should have 'hr' key")
|
||||
}
|
||||
if _, ok := decoded["gr"]; !ok {
|
||||
t.Error("JSON should have 'gr' key")
|
||||
}
|
||||
if _, ok := decoded["lastLogin"]; !ok {
|
||||
t.Error("JSON should have 'lastLogin' key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLauncherResponseFormat(t *testing.T) {
|
||||
s := mockServer()
|
||||
|
||||
req := httptest.NewRequest("GET", "/launcher", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.Launcher(w, req)
|
||||
|
||||
resp := w.Result()
|
||||
|
||||
// Verify it returns valid JSON
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
t.Errorf("Launcher() should return valid JSON: %v", err)
|
||||
}
|
||||
|
||||
// Check top-level keys exist
|
||||
if _, ok := result["important"]; !ok {
|
||||
t.Error("Launcher() response should have 'important' key")
|
||||
}
|
||||
if _, ok := result["normal"]; !ok {
|
||||
t.Error("Launcher() response should have 'normal' key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLauncherMessageCount(t *testing.T) {
|
||||
s := mockServer()
|
||||
|
||||
req := httptest.NewRequest("GET", "/launcher", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.Launcher(w, req)
|
||||
|
||||
var data struct {
|
||||
Important []LauncherMessage `json:"important"`
|
||||
Normal []LauncherMessage `json:"normal"`
|
||||
}
|
||||
json.NewDecoder(w.Result().Body).Decode(&data)
|
||||
|
||||
// Should have at least 3 important messages based on the implementation
|
||||
if len(data.Important) < 3 {
|
||||
t.Errorf("Launcher() should return at least 3 important messages, got %d", len(data.Important))
|
||||
}
|
||||
|
||||
// Should have at least 1 normal message
|
||||
if len(data.Normal) < 1 {
|
||||
t.Errorf("Launcher() should return at least 1 normal message, got %d", len(data.Normal))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterStructDBTags(t *testing.T) {
|
||||
// Test that Character struct has proper db tags
|
||||
char := Character{}
|
||||
|
||||
// These fields have db tags, verify struct is usable
|
||||
char.IsFemale = true
|
||||
char.Weapon = 7
|
||||
char.HR = 100
|
||||
char.LastLogin = 12345
|
||||
|
||||
if char.Weapon != 7 {
|
||||
t.Errorf("Weapon = %d, want 7", char.Weapon)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewServer(t *testing.T) {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
cfg := &Config{
|
||||
Logger: logger,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
|
||||
if s == nil {
|
||||
t.Fatal("NewServer() returned nil")
|
||||
}
|
||||
if s.logger == nil {
|
||||
t.Error("NewServer() should set logger")
|
||||
}
|
||||
if s.httpServer == nil {
|
||||
t.Error("NewServer() should initialize httpServer")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerConfig(t *testing.T) {
|
||||
cfg := &Config{
|
||||
Logger: nil,
|
||||
DB: nil,
|
||||
}
|
||||
|
||||
// Config struct should be usable
|
||||
if cfg.Logger != nil {
|
||||
t.Error("Config.Logger should be nil when not set")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user