mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
Add comprehensive tests for channelserver package: - handlers_character_test.go: CharacterSaveData, pointer constants - handlers_data_test.go: grpToGR function with boundary tests - handlers_quest_test.go: findSubSliceIndices, equal functions - handlers_simple_test.go: simple handlers, ack responses - handlers_util_test.go: stub handlers, ack helpers - sys_channel_server_test.go: Server, Raviente, stages, semaphores - sys_object_test.go: Object, Stage, stageBinaryKey structs All tests pass with race detection enabled.
485 lines
10 KiB
Go
485 lines
10 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"net"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"erupe-ce/config"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func TestNewServer(t *testing.T) {
|
|
logger, _ := zap.NewDevelopment()
|
|
cfg := &Config{
|
|
ID: 1,
|
|
Logger: logger,
|
|
DB: nil,
|
|
DiscordBot: nil,
|
|
ErupeConfig: &config.Config{DevMode: true},
|
|
Name: "TestServer",
|
|
Enable: true,
|
|
}
|
|
|
|
s := NewServer(cfg)
|
|
|
|
if s == nil {
|
|
t.Fatal("NewServer returned nil")
|
|
}
|
|
|
|
// Check ID assignment
|
|
if s.ID != 1 {
|
|
t.Errorf("Server ID = %d, want 1", s.ID)
|
|
}
|
|
|
|
// Check name assignment
|
|
if s.name != "TestServer" {
|
|
t.Errorf("Server name = %s, want TestServer", s.name)
|
|
}
|
|
|
|
// Check channels are created
|
|
if s.acceptConns == nil {
|
|
t.Error("acceptConns channel is nil")
|
|
}
|
|
if s.deleteConns == nil {
|
|
t.Error("deleteConns channel is nil")
|
|
}
|
|
|
|
// Check maps are initialized
|
|
if s.sessions == nil {
|
|
t.Error("sessions map is nil")
|
|
}
|
|
if s.stages == nil {
|
|
t.Error("stages map is nil")
|
|
}
|
|
if s.userBinaryParts == nil {
|
|
t.Error("userBinaryParts map is nil")
|
|
}
|
|
if s.semaphore == nil {
|
|
t.Error("semaphore map is nil")
|
|
}
|
|
|
|
// Check semaphore index starts at 7 (skips reserved IDs)
|
|
if s.semaphoreIndex != 7 {
|
|
t.Errorf("semaphoreIndex = %d, want 7", s.semaphoreIndex)
|
|
}
|
|
|
|
// Check Raviente is initialized
|
|
if s.raviente == nil {
|
|
t.Error("raviente is nil")
|
|
}
|
|
}
|
|
|
|
func TestNewServer_DefaultStages(t *testing.T) {
|
|
logger, _ := zap.NewDevelopment()
|
|
cfg := &Config{
|
|
ID: 1,
|
|
Logger: logger,
|
|
ErupeConfig: &config.Config{DevMode: true},
|
|
}
|
|
|
|
s := NewServer(cfg)
|
|
|
|
// Check persistent stages are created
|
|
expectedStages := []string{
|
|
"sl1Ns200p0a0u0", // Mezeporta
|
|
"sl1Ns211p0a0u0", // Rasta bar
|
|
"sl1Ns260p0a0u0", // Pallone Caravan
|
|
"sl1Ns262p0a0u0", // Pallone Guest House 1st Floor
|
|
"sl1Ns263p0a0u0", // Pallone Guest House 2nd Floor
|
|
"sl2Ns379p0a0u0", // Diva fountain
|
|
"sl1Ns462p0a0u0", // MezFes
|
|
}
|
|
|
|
for _, stageID := range expectedStages {
|
|
if _, ok := s.stages[stageID]; !ok {
|
|
t.Errorf("Expected default stage %s not found", stageID)
|
|
}
|
|
}
|
|
|
|
if len(s.stages) != len(expectedStages) {
|
|
t.Errorf("Server has %d stages, expected %d", len(s.stages), len(expectedStages))
|
|
}
|
|
}
|
|
|
|
func TestNewRaviente(t *testing.T) {
|
|
r := NewRaviente()
|
|
|
|
if r == nil {
|
|
t.Fatal("NewRaviente returned nil")
|
|
}
|
|
|
|
// Check register initialization
|
|
if r.register == nil {
|
|
t.Fatal("Raviente register is nil")
|
|
}
|
|
if r.register.nextTime != 0 {
|
|
t.Errorf("nextTime = %d, want 0", r.register.nextTime)
|
|
}
|
|
if r.register.maxPlayers != 0 {
|
|
t.Errorf("maxPlayers = %d, want 0", r.register.maxPlayers)
|
|
}
|
|
if len(r.register.register) != 5 {
|
|
t.Errorf("register array length = %d, want 5", len(r.register.register))
|
|
}
|
|
|
|
// Check state initialization
|
|
if r.state == nil {
|
|
t.Fatal("Raviente state is nil")
|
|
}
|
|
if len(r.state.stateData) != 29 {
|
|
t.Errorf("stateData length = %d, want 29", len(r.state.stateData))
|
|
}
|
|
|
|
// Check support initialization
|
|
if r.support == nil {
|
|
t.Fatal("Raviente support is nil")
|
|
}
|
|
if len(r.support.supportData) != 25 {
|
|
t.Errorf("supportData length = %d, want 25", len(r.support.supportData))
|
|
}
|
|
}
|
|
|
|
func TestRavienteRegister_InitialValues(t *testing.T) {
|
|
r := NewRaviente()
|
|
|
|
// All register slots should be 0 initially
|
|
for i, v := range r.register.register {
|
|
if v != 0 {
|
|
t.Errorf("register[%d] = %d, want 0", i, v)
|
|
}
|
|
}
|
|
|
|
// All state data should be 0 initially
|
|
for i, v := range r.state.stateData {
|
|
if v != 0 {
|
|
t.Errorf("stateData[%d] = %d, want 0", i, v)
|
|
}
|
|
}
|
|
|
|
// All support data should be 0 initially
|
|
for i, v := range r.support.supportData {
|
|
if v != 0 {
|
|
t.Errorf("supportData[%d] = %d, want 0", i, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestServerMutex(t *testing.T) {
|
|
logger, _ := zap.NewDevelopment()
|
|
cfg := &Config{
|
|
ID: 1,
|
|
Logger: logger,
|
|
ErupeConfig: &config.Config{DevMode: true},
|
|
}
|
|
|
|
s := NewServer(cfg)
|
|
|
|
// Test that mutex works and doesn't deadlock
|
|
s.Lock()
|
|
s.isShuttingDown = true
|
|
s.Unlock()
|
|
|
|
s.Lock()
|
|
if !s.isShuttingDown {
|
|
t.Error("isShuttingDown should be true")
|
|
}
|
|
s.Unlock()
|
|
}
|
|
|
|
func TestServerStagesLock(t *testing.T) {
|
|
logger, _ := zap.NewDevelopment()
|
|
cfg := &Config{
|
|
ID: 1,
|
|
Logger: logger,
|
|
ErupeConfig: &config.Config{DevMode: true},
|
|
}
|
|
|
|
s := NewServer(cfg)
|
|
|
|
// Test RWMutex for stages
|
|
s.stagesLock.RLock()
|
|
count := len(s.stages)
|
|
s.stagesLock.RUnlock()
|
|
|
|
if count < 7 {
|
|
t.Errorf("Expected at least 7 default stages, got %d", count)
|
|
}
|
|
|
|
// Test write lock
|
|
s.stagesLock.Lock()
|
|
s.stages["test_stage"] = NewStage("test_stage")
|
|
s.stagesLock.Unlock()
|
|
|
|
s.stagesLock.RLock()
|
|
if _, ok := s.stages["test_stage"]; !ok {
|
|
t.Error("test_stage not found after adding")
|
|
}
|
|
s.stagesLock.RUnlock()
|
|
}
|
|
|
|
func TestServerConcurrentStageAccess(t *testing.T) {
|
|
logger, _ := zap.NewDevelopment()
|
|
cfg := &Config{
|
|
ID: 1,
|
|
Logger: logger,
|
|
ErupeConfig: &config.Config{DevMode: true},
|
|
}
|
|
|
|
s := NewServer(cfg)
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
// Multiple concurrent readers
|
|
for i := 0; i < 10; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for j := 0; j < 100; j++ {
|
|
s.stagesLock.RLock()
|
|
_ = len(s.stages)
|
|
s.stagesLock.RUnlock()
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Concurrent writer
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for j := 0; j < 50; j++ {
|
|
s.stagesLock.Lock()
|
|
stageID := "concurrent_test_" + string(rune('A'+j%26))
|
|
s.stages[stageID] = NewStage(stageID)
|
|
s.stagesLock.Unlock()
|
|
}
|
|
}()
|
|
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestNextSemaphoreID(t *testing.T) {
|
|
logger, _ := zap.NewDevelopment()
|
|
cfg := &Config{
|
|
ID: 1,
|
|
Logger: logger,
|
|
ErupeConfig: &config.Config{DevMode: true},
|
|
}
|
|
|
|
s := NewServer(cfg)
|
|
|
|
// Initial index should be 7
|
|
if s.semaphoreIndex != 7 {
|
|
t.Errorf("Initial semaphoreIndex = %d, want 7", s.semaphoreIndex)
|
|
}
|
|
|
|
// Get next IDs
|
|
id1 := s.NextSemaphoreID()
|
|
id2 := s.NextSemaphoreID()
|
|
id3 := s.NextSemaphoreID()
|
|
|
|
// IDs should be unique and incrementing
|
|
if id1 == id2 || id2 == id3 || id1 == id3 {
|
|
t.Errorf("Semaphore IDs should be unique: %d, %d, %d", id1, id2, id3)
|
|
}
|
|
|
|
if id2 <= id1 || id3 <= id2 {
|
|
t.Errorf("Semaphore IDs should be incrementing: %d, %d, %d", id1, id2, id3)
|
|
}
|
|
}
|
|
|
|
func TestNextSemaphoreID_SkipsExisting(t *testing.T) {
|
|
logger, _ := zap.NewDevelopment()
|
|
cfg := &Config{
|
|
ID: 1,
|
|
Logger: logger,
|
|
ErupeConfig: &config.Config{DevMode: true},
|
|
}
|
|
|
|
s := NewServer(cfg)
|
|
|
|
// Pre-populate some semaphores
|
|
s.semaphore["test1"] = &Semaphore{id: 8}
|
|
s.semaphore["test2"] = &Semaphore{id: 9}
|
|
|
|
id := s.NextSemaphoreID()
|
|
|
|
// Should skip 8 and 9 since they exist
|
|
if id == 8 || id == 9 {
|
|
t.Errorf("NextSemaphoreID should skip existing IDs, got %d", id)
|
|
}
|
|
}
|
|
|
|
func TestUserBinaryPartID(t *testing.T) {
|
|
id1 := userBinaryPartID{charID: 100, index: 1}
|
|
id2 := userBinaryPartID{charID: 100, index: 2}
|
|
id3 := userBinaryPartID{charID: 200, index: 1}
|
|
|
|
// Same char, different index should be different keys
|
|
if id1 == id2 {
|
|
t.Error("Different indices should produce different keys")
|
|
}
|
|
|
|
// Different char, same index should be different keys
|
|
if id1 == id3 {
|
|
t.Error("Different charIDs should produce different keys")
|
|
}
|
|
|
|
// Same values should be equal
|
|
id1copy := userBinaryPartID{charID: 100, index: 1}
|
|
if id1 != id1copy {
|
|
t.Error("Same values should be equal")
|
|
}
|
|
}
|
|
|
|
func TestServerUserBinaryParts(t *testing.T) {
|
|
logger, _ := zap.NewDevelopment()
|
|
cfg := &Config{
|
|
ID: 1,
|
|
Logger: logger,
|
|
ErupeConfig: &config.Config{DevMode: true},
|
|
}
|
|
|
|
s := NewServer(cfg)
|
|
|
|
testData := []byte{0x01, 0x02, 0x03}
|
|
partID := userBinaryPartID{charID: 12345, index: 1}
|
|
|
|
// Store data
|
|
s.userBinaryPartsLock.Lock()
|
|
s.userBinaryParts[partID] = testData
|
|
s.userBinaryPartsLock.Unlock()
|
|
|
|
// Retrieve data
|
|
s.userBinaryPartsLock.RLock()
|
|
data, ok := s.userBinaryParts[partID]
|
|
s.userBinaryPartsLock.RUnlock()
|
|
|
|
if !ok {
|
|
t.Error("Failed to retrieve stored binary part")
|
|
}
|
|
if len(data) != 3 || data[0] != 0x01 {
|
|
t.Errorf("Retrieved data doesn't match: %v", data)
|
|
}
|
|
}
|
|
|
|
func TestServerShutdown(t *testing.T) {
|
|
logger, _ := zap.NewDevelopment()
|
|
cfg := &Config{
|
|
ID: 1,
|
|
Logger: logger,
|
|
ErupeConfig: &config.Config{DevMode: true},
|
|
}
|
|
|
|
s := NewServer(cfg)
|
|
|
|
// Create a test listener
|
|
listener, err := net.Listen("tcp", ":0")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test listener: %v", err)
|
|
}
|
|
s.listener = listener
|
|
|
|
// Shutdown should not panic
|
|
s.Shutdown()
|
|
|
|
// Check shutdown flag is set
|
|
s.Lock()
|
|
if !s.isShuttingDown {
|
|
t.Error("isShuttingDown should be true after Shutdown()")
|
|
}
|
|
s.Unlock()
|
|
}
|
|
|
|
func TestServerFindSessionByCharID_NotFound(t *testing.T) {
|
|
logger, _ := zap.NewDevelopment()
|
|
cfg := &Config{
|
|
ID: 1,
|
|
Logger: logger,
|
|
ErupeConfig: &config.Config{DevMode: true},
|
|
}
|
|
|
|
s := NewServer(cfg)
|
|
s.Channels = []*Server{s}
|
|
|
|
// Search for non-existent character
|
|
session := s.FindSessionByCharID(99999)
|
|
if session != nil {
|
|
t.Error("Expected nil for non-existent character")
|
|
}
|
|
}
|
|
|
|
func TestServerFindObjectByChar_NotFound(t *testing.T) {
|
|
logger, _ := zap.NewDevelopment()
|
|
cfg := &Config{
|
|
ID: 1,
|
|
Logger: logger,
|
|
ErupeConfig: &config.Config{DevMode: true},
|
|
}
|
|
|
|
s := NewServer(cfg)
|
|
|
|
// Search for non-existent object
|
|
obj := s.FindObjectByChar(99999)
|
|
if obj != nil {
|
|
t.Error("Expected nil for non-existent object owner")
|
|
}
|
|
}
|
|
|
|
func TestServerStartAndShutdown(t *testing.T) {
|
|
logger, _ := zap.NewDevelopment()
|
|
cfg := &Config{
|
|
ID: 1,
|
|
Logger: logger,
|
|
ErupeConfig: &config.Config{DevMode: true},
|
|
}
|
|
|
|
s := NewServer(cfg)
|
|
s.Port = 0 // Use any available port
|
|
|
|
err := s.Start()
|
|
if err != nil {
|
|
t.Fatalf("Server.Start() failed: %v", err)
|
|
}
|
|
|
|
// Give goroutines time to start
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Verify listener is created
|
|
if s.listener == nil {
|
|
t.Error("Listener should be created after Start()")
|
|
}
|
|
|
|
// Shutdown
|
|
s.Shutdown()
|
|
|
|
// Give time for cleanup
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
|
|
func TestConfigStruct(t *testing.T) {
|
|
logger, _ := zap.NewDevelopment()
|
|
|
|
cfg := &Config{
|
|
ID: 42,
|
|
Logger: logger,
|
|
DB: nil,
|
|
DiscordBot: nil,
|
|
ErupeConfig: &config.Config{},
|
|
Name: "Test Channel",
|
|
Enable: true,
|
|
}
|
|
|
|
if cfg.ID != 42 {
|
|
t.Errorf("Config ID = %d, want 42", cfg.ID)
|
|
}
|
|
if cfg.Name != "Test Channel" {
|
|
t.Errorf("Config Name = %s, want 'Test Channel'", cfg.Name)
|
|
}
|
|
if !cfg.Enable {
|
|
t.Error("Config Enable should be true")
|
|
}
|
|
}
|