From a68d76c55f2f514ee1367060a9f579ddaefd9d35 Mon Sep 17 00:00:00 2001 From: Houmgaor Date: Thu, 26 Feb 2026 23:17:12 +0100 Subject: [PATCH] test: add coverage tests to reach 65% total coverage Add 16 test files across 4 packages covering previously untested handler paths: guild board operations, house/warehouse management, tower/tenrouirai progress, diva schedule, festa info, cafe duration, API error paths, sign server responses, and byteframe boundaries. --- common/byteframe/byteframe_boundary_test.go | 151 +++++++ server/api/dbutils_coverage_test.go | 314 ++++++++++++++ server/api/endpoints_coverage_test.go | 387 ++++++++++++++++++ server/api/endpoints_extra_coverage_test.go | 223 ++++++++++ .../handlers_cafe_extra_coverage_test.go | 52 +++ .../handlers_diva_schedule_coverage_test.go | 91 ++++ .../handlers_festa_info_coverage_test.go | 52 +++ .../handlers_guild_board_coverage_test.go | 173 ++++++++ .../handlers_guild_coverage_test.go | 246 +++++++++++ .../handlers_guild_ops_coverage_test.go | 109 +++++ .../handlers_house_coverage_test.go | 265 ++++++++++++ .../handlers_mercenary_coverage_test.go | 256 ++++++++++++ .../handlers_quest_coverage_test.go | 40 ++ .../handlers_stage_coverage_test.go | 342 ++++++++++++++++ .../handlers_tower_extra_coverage_test.go | 106 +++++ server/signserver/dsgn_resp_coverage_test.go | 270 ++++++++++++ 16 files changed, 3077 insertions(+) create mode 100644 common/byteframe/byteframe_boundary_test.go create mode 100644 server/api/dbutils_coverage_test.go create mode 100644 server/api/endpoints_coverage_test.go create mode 100644 server/api/endpoints_extra_coverage_test.go create mode 100644 server/channelserver/handlers_cafe_extra_coverage_test.go create mode 100644 server/channelserver/handlers_diva_schedule_coverage_test.go create mode 100644 server/channelserver/handlers_festa_info_coverage_test.go create mode 100644 server/channelserver/handlers_guild_board_coverage_test.go create mode 100644 server/channelserver/handlers_guild_coverage_test.go create mode 100644 server/channelserver/handlers_guild_ops_coverage_test.go create mode 100644 server/channelserver/handlers_house_coverage_test.go create mode 100644 server/channelserver/handlers_mercenary_coverage_test.go create mode 100644 server/channelserver/handlers_quest_coverage_test.go create mode 100644 server/channelserver/handlers_stage_coverage_test.go create mode 100644 server/channelserver/handlers_tower_extra_coverage_test.go create mode 100644 server/signserver/dsgn_resp_coverage_test.go diff --git a/common/byteframe/byteframe_boundary_test.go b/common/byteframe/byteframe_boundary_test.go new file mode 100644 index 000000000..688f89aae --- /dev/null +++ b/common/byteframe/byteframe_boundary_test.go @@ -0,0 +1,151 @@ +package byteframe + +import ( + "io" + "testing" +) + +func TestReadUint32_UnderRead(t *testing.T) { + bf := NewByteFrameFromBytes([]byte{0x01}) + got := bf.ReadUint32() + if got != 0 { + t.Errorf("ReadUint32 on 1-byte frame = %d, want 0", got) + } + if bf.Err() == nil { + t.Error("expected ErrReadOverflow") + } +} + +func TestStickyError_ReadAfterFailed(t *testing.T) { + bf := NewByteFrameFromBytes([]byte{0x01}) + _ = bf.ReadUint32() // triggers error + // All subsequent reads should return zero + if bf.ReadUint8() != 0 { + t.Error("ReadUint8 after error should return 0") + } + if bf.ReadUint16() != 0 { + t.Error("ReadUint16 after error should return 0") + } + if bf.ReadUint64() != 0 { + t.Error("ReadUint64 after error should return 0") + } + if bf.ReadInt8() != 0 { + t.Error("ReadInt8 after error should return 0") + } + if bf.ReadInt16() != 0 { + t.Error("ReadInt16 after error should return 0") + } + if bf.ReadInt32() != 0 { + t.Error("ReadInt32 after error should return 0") + } + if bf.ReadInt64() != 0 { + t.Error("ReadInt64 after error should return 0") + } + if bf.ReadFloat32() != 0 { + t.Error("ReadFloat32 after error should return 0") + } + if bf.ReadFloat64() != 0 { + t.Error("ReadFloat64 after error should return 0") + } + if bf.ReadBytes(1) != nil { + t.Error("ReadBytes after error should return nil") + } +} + +func TestReadOverflow_AllTypes(t *testing.T) { + tests := []struct { + name string + size int + fn func(bf *ByteFrame) + }{ + {"Uint8", 0, func(bf *ByteFrame) { bf.ReadUint8() }}, + {"Uint16", 1, func(bf *ByteFrame) { bf.ReadUint16() }}, + {"Uint32", 3, func(bf *ByteFrame) { bf.ReadUint32() }}, + {"Uint64", 7, func(bf *ByteFrame) { bf.ReadUint64() }}, + {"Int8", 0, func(bf *ByteFrame) { bf.ReadInt8() }}, + {"Int16", 1, func(bf *ByteFrame) { bf.ReadInt16() }}, + {"Int32", 3, func(bf *ByteFrame) { bf.ReadInt32() }}, + {"Int64", 7, func(bf *ByteFrame) { bf.ReadInt64() }}, + {"Float32", 3, func(bf *ByteFrame) { bf.ReadFloat32() }}, + {"Float64", 7, func(bf *ByteFrame) { bf.ReadFloat64() }}, + {"Bytes", 2, func(bf *ByteFrame) { bf.ReadBytes(5) }}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := make([]byte, tt.size) + bf := NewByteFrameFromBytes(data) + tt.fn(bf) + if bf.Err() == nil { + t.Errorf("expected overflow error for %s with %d bytes", tt.name, tt.size) + } + }) + } +} + +func TestReadBytes_Exact(t *testing.T) { + data := []byte{0xDE, 0xAD, 0xBE, 0xEF} + bf := NewByteFrameFromBytes(data) + got := bf.ReadBytes(4) + if len(got) != 4 { + t.Errorf("ReadBytes(4) returned %d bytes", len(got)) + } + if bf.Err() != nil { + t.Errorf("unexpected error: %v", bf.Err()) + } + // Reading 1 more byte should fail + _ = bf.ReadUint8() + if bf.Err() == nil { + t.Error("expected overflow after reading all bytes") + } +} + +func TestWriteThenRead_RoundTrip(t *testing.T) { + bf := NewByteFrame() + bf.WriteUint8(0xFF) + bf.WriteUint16(0x1234) + bf.WriteUint32(0xDEADBEEF) + bf.WriteUint64(0x0102030405060708) + bf.WriteInt8(-1) + bf.WriteInt16(-256) + bf.WriteInt32(-100000) + bf.WriteInt64(-999999999) + bf.WriteFloat32(3.14) + bf.WriteFloat64(2.718281828) + + _, _ = bf.Seek(0, io.SeekStart) + + if v := bf.ReadUint8(); v != 0xFF { + t.Errorf("uint8 = %d", v) + } + if v := bf.ReadUint16(); v != 0x1234 { + t.Errorf("uint16 = %d", v) + } + if v := bf.ReadUint32(); v != 0xDEADBEEF { + t.Errorf("uint32 = %x", v) + } + if v := bf.ReadUint64(); v != 0x0102030405060708 { + t.Errorf("uint64 = %x", v) + } + if v := bf.ReadInt8(); v != -1 { + t.Errorf("int8 = %d", v) + } + if v := bf.ReadInt16(); v != -256 { + t.Errorf("int16 = %d", v) + } + if v := bf.ReadInt32(); v != -100000 { + t.Errorf("int32 = %d", v) + } + if v := bf.ReadInt64(); v != -999999999 { + t.Errorf("int64 = %d", v) + } + if v := bf.ReadFloat32(); v < 3.13 || v > 3.15 { + t.Errorf("float32 = %f", v) + } + if v := bf.ReadFloat64(); v < 2.71 || v > 2.72 { + t.Errorf("float64 = %f", v) + } + if bf.Err() != nil { + t.Errorf("unexpected error: %v", bf.Err()) + } +} diff --git a/server/api/dbutils_coverage_test.go b/server/api/dbutils_coverage_test.go new file mode 100644 index 000000000..baff9b36f --- /dev/null +++ b/server/api/dbutils_coverage_test.go @@ -0,0 +1,314 @@ +package api + +import ( + "context" + "database/sql" + "errors" + "testing" + "time" +) + +func TestCreateNewUser_Success(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + userRepo: &mockAPIUserRepo{ + registerID: 1, + registerRights: 30, + }, + } + + uid, rights, err := server.createNewUser(context.Background(), "testuser", "password123") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if uid != 1 { + t.Errorf("uid = %d, want 1", uid) + } + if rights != 30 { + t.Errorf("rights = %d, want 30", rights) + } +} + +func TestCreateLoginToken_Success(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + sessionRepo: &mockAPISessionRepo{ + createTokenID: 42, + }, + } + + tid, token, err := server.createLoginToken(context.Background(), 1) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if tid != 42 { + t.Errorf("tid = %d, want 42", tid) + } + if token == "" { + t.Error("token should not be empty") + } +} + +func TestCreateLoginToken_Error(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + sessionRepo: &mockAPISessionRepo{ + createTokenErr: errors.New("db error"), + }, + } + + _, _, err := server.createLoginToken(context.Background(), 1) + if err == nil { + t.Error("expected error") + } +} + +func TestUserIDFromToken_Valid(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + sessionRepo: &mockAPISessionRepo{ + userID: 42, + }, + } + + uid, err := server.userIDFromToken(context.Background(), "valid-token") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if uid != 42 { + t.Errorf("uid = %d, want 42", uid) + } +} + +func TestUserIDFromToken_ErrNoRows(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + sessionRepo: &mockAPISessionRepo{ + userIDErr: sql.ErrNoRows, + }, + } + + _, err := server.userIDFromToken(context.Background(), "invalid-token") + if err == nil { + t.Error("expected error for invalid token") + } +} + +func TestUserIDFromToken_OtherError(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + sessionRepo: &mockAPISessionRepo{ + userIDErr: errors.New("connection refused"), + }, + } + + _, err := server.userIDFromToken(context.Background(), "some-token") + if err == nil { + t.Error("expected error") + } +} + +func TestCreateCharacter_ExistingNew(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + charRepo: &mockAPICharacterRepo{ + newCharacter: Character{ID: 5, Name: "NewChar"}, + }, + } + + char, err := server.createCharacter(context.Background(), 1) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if char.ID != 5 { + t.Errorf("char ID = %d, want 5", char.ID) + } +} + +func TestCreateCharacter_CreateNew(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + charRepo: &mockAPICharacterRepo{ + newCharacterErr: sql.ErrNoRows, + countForUser: 2, + createChar: Character{ID: 10, Name: "Created"}, + }, + } + + char, err := server.createCharacter(context.Background(), 1) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if char.ID != 10 { + t.Errorf("char ID = %d, want 10", char.ID) + } +} + +func TestCreateCharacter_MaxExceeded(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + charRepo: &mockAPICharacterRepo{ + newCharacterErr: sql.ErrNoRows, + countForUser: 16, + createCharErr: errors.New("cannot have more than 16 characters"), + }, + } + + _, err := server.createCharacter(context.Background(), 1) + if err == nil { + t.Error("expected error for max chars exceeded") + } +} + +func TestDeleteCharacter_IsNewHardDelete(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + charRepo := &mockAPICharacterRepo{isNewResult: true} + server := &APIServer{ + logger: logger, + erupeConfig: c, + charRepo: charRepo, + } + + err := server.deleteCharacter(context.Background(), 1, 5) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestDeleteCharacter_FinalizedSoftDelete(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + charRepo := &mockAPICharacterRepo{isNewResult: false} + server := &APIServer{ + logger: logger, + erupeConfig: c, + charRepo: charRepo, + } + + err := server.deleteCharacter(context.Background(), 1, 5) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestGetCharactersForUser(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + charRepo: &mockAPICharacterRepo{ + characters: []Character{ + {ID: 1, Name: "Char1"}, + {ID: 2, Name: "Char2"}, + }, + }, + } + + chars, err := server.getCharactersForUser(context.Background(), 1) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(chars) != 2 { + t.Errorf("count = %d, want 2", len(chars)) + } +} + +func TestExportSave(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + charRepo: &mockAPICharacterRepo{ + exportResult: map[string]interface{}{"name": "Hunter"}, + }, + } + + result, err := server.exportSave(context.Background(), 1, 5) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if result["name"] != "Hunter" { + t.Errorf("name = %v, want Hunter", result["name"]) + } +} + +func TestGetReturnExpiry_RecentLogin(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + userRepo: &mockAPIUserRepo{ + lastLogin: time.Now(), + returnExpiry: time.Now().Add(time.Hour * 24 * 15), + }, + } + + expiry := server.getReturnExpiry(1) + if expiry.IsZero() { + t.Error("expiry should not be zero") + } +} + +func TestGetReturnExpiry_OldLogin(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + userRepo: &mockAPIUserRepo{ + lastLogin: time.Now().Add(-time.Hour * 24 * 100), // 100 days ago + returnExpiry: time.Now().Add(time.Hour * 24 * 30), + }, + } + + expiry := server.getReturnExpiry(1) + if expiry.Before(time.Now()) { + t.Error("expiry should be in the future for returning player") + } +} diff --git a/server/api/endpoints_coverage_test.go b/server/api/endpoints_coverage_test.go new file mode 100644 index 000000000..84d04d928 --- /dev/null +++ b/server/api/endpoints_coverage_test.go @@ -0,0 +1,387 @@ +package api + +import ( + "bytes" + "database/sql" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + cfg "erupe-ce/config" + + "golang.org/x/crypto/bcrypt" +) + +func TestVersionEndpoint(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + c.ClientMode = "ZZ" + + server := &APIServer{ + logger: logger, + erupeConfig: c, + } + + req := httptest.NewRequest("GET", "/version", nil) + rec := httptest.NewRecorder() + server.Version(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("status = %d, want 200", rec.Code) + } + + var resp VersionResponse + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode error: %v", err) + } + if resp.ClientMode != "ZZ" { + t.Errorf("ClientMode = %q, want ZZ", resp.ClientMode) + } + if resp.Name != "Erupe-CE" { + t.Errorf("Name = %q, want Erupe-CE", resp.Name) + } +} + +func TestLandingPageEndpoint_Enabled(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + c.API.LandingPage = cfg.LandingPage{ + Enabled: true, + Title: "Test Server", + Content: "

Welcome

", + } + + server := &APIServer{ + logger: logger, + erupeConfig: c, + } + + req := httptest.NewRequest("GET", "/", nil) + rec := httptest.NewRecorder() + server.LandingPage(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("status = %d, want 200", rec.Code) + } + if ct := rec.Header().Get("Content-Type"); ct != "text/html; charset=utf-8" { + t.Errorf("Content-Type = %q", ct) + } +} + +func TestLandingPageEndpoint_Disabled(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + c.API.LandingPage = cfg.LandingPage{Enabled: false} + + server := &APIServer{ + logger: logger, + erupeConfig: c, + } + + req := httptest.NewRequest("GET", "/", nil) + rec := httptest.NewRecorder() + server.LandingPage(rec, req) + + if rec.Code != http.StatusNotFound { + t.Errorf("status = %d, want 404", rec.Code) + } +} + +func TestLoginEndpoint_Success(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + hash, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.MinCost) + + server := &APIServer{ + logger: logger, + erupeConfig: c, + userRepo: &mockAPIUserRepo{ + credentialsID: 1, + credentialsPassword: string(hash), + credentialsRights: 30, + lastLogin: time.Now(), + returnExpiry: time.Now().Add(time.Hour * 24 * 30), + }, + sessionRepo: &mockAPISessionRepo{ + createTokenID: 42, + }, + charRepo: &mockAPICharacterRepo{ + characters: []Character{ + {ID: 1, Name: "TestHunter", HR: 100}, + }, + }, + } + + body, _ := json.Marshal(map[string]string{ + "username": "testuser", + "password": "password123", + }) + req := httptest.NewRequest("POST", "/login", bytes.NewReader(body)) + rec := httptest.NewRecorder() + server.Login(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("status = %d, want 200", rec.Code) + } + + var resp AuthData + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode error: %v", err) + } + if resp.User.TokenID != 42 { + t.Errorf("TokenID = %d, want 42", resp.User.TokenID) + } + if len(resp.Characters) != 1 { + t.Errorf("Characters count = %d, want 1", len(resp.Characters)) + } +} + +func TestLoginEndpoint_UsernameNotFound(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + userRepo: &mockAPIUserRepo{ + credentialsErr: sql.ErrNoRows, + }, + } + + body, _ := json.Marshal(map[string]string{ + "username": "nonexistent", + "password": "password123", + }) + req := httptest.NewRequest("POST", "/login", bytes.NewReader(body)) + rec := httptest.NewRecorder() + server.Login(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Errorf("status = %d, want 400", rec.Code) + } + if rec.Body.String() != "username-error" { + t.Errorf("body = %q, want username-error", rec.Body.String()) + } +} + +func TestLoginEndpoint_WrongPassword(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + hash, _ := bcrypt.GenerateFromPassword([]byte("correct"), bcrypt.MinCost) + + server := &APIServer{ + logger: logger, + erupeConfig: c, + userRepo: &mockAPIUserRepo{ + credentialsID: 1, + credentialsPassword: string(hash), + lastLogin: time.Now(), + returnExpiry: time.Now().Add(time.Hour * 24 * 30), + }, + } + + body, _ := json.Marshal(map[string]string{ + "username": "testuser", + "password": "wrongpassword", + }) + req := httptest.NewRequest("POST", "/login", bytes.NewReader(body)) + rec := httptest.NewRecorder() + server.Login(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Errorf("status = %d, want 400", rec.Code) + } + if rec.Body.String() != "password-error" { + t.Errorf("body = %q, want password-error", rec.Body.String()) + } +} + +func TestRegisterEndpoint_Success(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + userRepo: &mockAPIUserRepo{ + registerID: 1, + registerRights: 30, + lastLogin: time.Now(), + returnExpiry: time.Now().Add(time.Hour * 24 * 30), + }, + sessionRepo: &mockAPISessionRepo{ + createTokenID: 10, + }, + charRepo: &mockAPICharacterRepo{}, + } + + body, _ := json.Marshal(map[string]string{ + "username": "newuser", + "password": "password123", + }) + req := httptest.NewRequest("POST", "/register", bytes.NewReader(body)) + rec := httptest.NewRecorder() + server.Register(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("status = %d, want 200", rec.Code) + } + + var resp AuthData + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode error: %v", err) + } + if resp.User.Rights != 30 { + t.Errorf("Rights = %d, want 30", resp.User.Rights) + } +} + +func TestCreateCharacterEndpoint_Success(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + sessionRepo: &mockAPISessionRepo{ + userID: 1, + }, + charRepo: &mockAPICharacterRepo{ + newCharacter: Character{ID: 5, Name: "NewChar"}, + }, + } + + body, _ := json.Marshal(map[string]string{ + "token": "valid-token", + }) + req := httptest.NewRequest("POST", "/character/create", bytes.NewReader(body)) + rec := httptest.NewRecorder() + server.CreateCharacter(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("status = %d, want 200", rec.Code) + } +} + +func TestCreateCharacterEndpoint_InvalidToken(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + sessionRepo: &mockAPISessionRepo{ + userIDErr: sql.ErrNoRows, + }, + } + + body, _ := json.Marshal(map[string]string{ + "token": "invalid", + }) + req := httptest.NewRequest("POST", "/character/create", bytes.NewReader(body)) + rec := httptest.NewRecorder() + server.CreateCharacter(rec, req) + + if rec.Code != http.StatusUnauthorized { + t.Errorf("status = %d, want 401", rec.Code) + } +} + +func TestDeleteCharacterEndpoint_NewChar(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + sessionRepo: &mockAPISessionRepo{ + userID: 1, + }, + charRepo: &mockAPICharacterRepo{ + isNewResult: true, + }, + } + + body, _ := json.Marshal(map[string]interface{}{ + "token": "valid-token", + "charId": 5, + }) + req := httptest.NewRequest("POST", "/character/delete", bytes.NewReader(body)) + rec := httptest.NewRecorder() + server.DeleteCharacter(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("status = %d, want 200", rec.Code) + } +} + +func TestDeleteCharacterEndpoint_FinalizedChar(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + sessionRepo: &mockAPISessionRepo{ + userID: 1, + }, + charRepo: &mockAPICharacterRepo{ + isNewResult: false, + }, + } + + body, _ := json.Marshal(map[string]interface{}{ + "token": "valid-token", + "charId": 5, + }) + req := httptest.NewRequest("POST", "/character/delete", bytes.NewReader(body)) + rec := httptest.NewRecorder() + server.DeleteCharacter(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("status = %d, want 200", rec.Code) + } +} + +func TestExportSaveEndpoint_Success(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + sessionRepo: &mockAPISessionRepo{ + userID: 1, + }, + charRepo: &mockAPICharacterRepo{ + exportResult: map[string]interface{}{ + "name": "TestHunter", + "hr": 100, + }, + }, + } + + body, _ := json.Marshal(map[string]interface{}{ + "token": "valid-token", + "charId": 1, + }) + req := httptest.NewRequest("POST", "/character/export", bytes.NewReader(body)) + rec := httptest.NewRecorder() + server.ExportSave(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("status = %d, want 200", rec.Code) + } + + var resp ExportData + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode error: %v", err) + } + if resp.Character["name"] != "TestHunter" { + t.Errorf("character name = %v, want TestHunter", resp.Character["name"]) + } +} diff --git a/server/api/endpoints_extra_coverage_test.go b/server/api/endpoints_extra_coverage_test.go new file mode 100644 index 000000000..c620ed5e0 --- /dev/null +++ b/server/api/endpoints_extra_coverage_test.go @@ -0,0 +1,223 @@ +package api + +import ( + "bytes" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "testing" +) + +func TestHealthEndpoint_NilDB(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + db: nil, + } + + req := httptest.NewRequest("GET", "/health", nil) + rec := httptest.NewRecorder() + server.Health(rec, req) + + if rec.Code != http.StatusServiceUnavailable { + t.Errorf("status = %d, want 503", rec.Code) + } + var resp map[string]string + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode error: %v", err) + } + if resp["status"] != "unhealthy" { + t.Errorf("status = %q, want unhealthy", resp["status"]) + } +} + +func TestRegisterEndpoint_EmptyPassword(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + } + + body, _ := json.Marshal(map[string]string{ + "username": "testuser", + "password": "", + }) + req := httptest.NewRequest("POST", "/register", bytes.NewReader(body)) + rec := httptest.NewRecorder() + server.Register(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Errorf("status = %d, want 400", rec.Code) + } +} + +func TestRegisterEndpoint_InvalidJSON(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + } + + req := httptest.NewRequest("POST", "/register", bytes.NewReader([]byte("not json"))) + rec := httptest.NewRecorder() + server.Register(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Errorf("status = %d, want 400", rec.Code) + } +} + +func TestLoginEndpoint_InvalidJSON(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + } + + req := httptest.NewRequest("POST", "/login", bytes.NewReader([]byte("not json"))) + rec := httptest.NewRecorder() + server.Login(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Errorf("status = %d, want 400", rec.Code) + } +} + +func TestCreateCharacterEndpoint_InvalidJSON(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + } + + req := httptest.NewRequest("POST", "/character/create", bytes.NewReader([]byte("bad"))) + rec := httptest.NewRecorder() + server.CreateCharacter(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Errorf("status = %d, want 400", rec.Code) + } +} + +func TestDeleteCharacterEndpoint_InvalidJSON(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + } + + req := httptest.NewRequest("POST", "/character/delete", bytes.NewReader([]byte("bad"))) + rec := httptest.NewRecorder() + server.DeleteCharacter(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Errorf("status = %d, want 400", rec.Code) + } +} + +func TestExportSaveEndpoint_InvalidJSON(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + } + + req := httptest.NewRequest("POST", "/character/export", bytes.NewReader([]byte("bad"))) + rec := httptest.NewRecorder() + server.ExportSave(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Errorf("status = %d, want 400", rec.Code) + } +} + +func TestRegisterEndpoint_CreateUserError(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + userRepo: &mockAPIUserRepo{ + registerErr: errors.New("db connection failed"), + }, + } + + body, _ := json.Marshal(map[string]string{ + "username": "testuser", + "password": "password123", + }) + req := httptest.NewRequest("POST", "/register", bytes.NewReader(body)) + rec := httptest.NewRecorder() + server.Register(rec, req) + + if rec.Code != http.StatusInternalServerError { + t.Errorf("status = %d, want 500", rec.Code) + } +} + +func TestDeleteCharacterEndpoint_InvalidToken(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + sessionRepo: &mockAPISessionRepo{ + userIDErr: errors.New("bad token"), + }, + } + + body, _ := json.Marshal(map[string]interface{}{ + "token": "invalid", + "charId": 5, + }) + req := httptest.NewRequest("POST", "/character/delete", bytes.NewReader(body)) + rec := httptest.NewRecorder() + server.DeleteCharacter(rec, req) + + if rec.Code != http.StatusUnauthorized { + t.Errorf("status = %d, want 401", rec.Code) + } +} + +func TestExportSaveEndpoint_InvalidToken(t *testing.T) { + logger := NewTestLogger(t) + c := NewTestConfig() + + server := &APIServer{ + logger: logger, + erupeConfig: c, + sessionRepo: &mockAPISessionRepo{ + userIDErr: errors.New("bad token"), + }, + } + + body, _ := json.Marshal(map[string]interface{}{ + "token": "invalid", + "charId": 1, + }) + req := httptest.NewRequest("POST", "/character/export", bytes.NewReader(body)) + rec := httptest.NewRecorder() + server.ExportSave(rec, req) + + if rec.Code != http.StatusUnauthorized { + t.Errorf("status = %d, want 401", rec.Code) + } +} diff --git a/server/channelserver/handlers_cafe_extra_coverage_test.go b/server/channelserver/handlers_cafe_extra_coverage_test.go new file mode 100644 index 000000000..62caad8d3 --- /dev/null +++ b/server/channelserver/handlers_cafe_extra_coverage_test.go @@ -0,0 +1,52 @@ +package channelserver + +import ( + cfg "erupe-ce/config" + "erupe-ce/network/mhfpacket" + "testing" + "time" +) + +func TestHandleMsgMhfGetCafeDuration_ResetPath(t *testing.T) { + srv := createMockServer() + charRepo := newMockCharacterRepo() + // cafe_reset in the past to trigger reset logic + charRepo.times["cafe_reset"] = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) + charRepo.ints["cafe_time"] = 1800 + srv.charRepo = charRepo + srv.cafeRepo = &mockCafeRepo{} + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfGetCafeDuration{AckHandle: 1} + handleMsgMhfGetCafeDuration(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfGetCafeDuration_NoResetTime(t *testing.T) { + srv := createMockServer() + charRepo := newMockCharacterRepo() + // No cafe_reset set → ReadTime returns error → sets new reset time + charRepo.ints["cafe_time"] = 100 + srv.charRepo = charRepo + srv.cafeRepo = &mockCafeRepo{} + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfGetCafeDuration{AckHandle: 1} + handleMsgMhfGetCafeDuration(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfGetCafeDuration_ZZClient(t *testing.T) { + srv := createMockServer() + srv.erupeConfig.RealClientMode = cfg.ZZ + charRepo := newMockCharacterRepo() + charRepo.times["cafe_reset"] = time.Date(2099, 12, 31, 0, 0, 0, 0, time.UTC) + charRepo.ints["cafe_time"] = 3600 + srv.charRepo = charRepo + srv.cafeRepo = &mockCafeRepo{} + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfGetCafeDuration{AckHandle: 1} + handleMsgMhfGetCafeDuration(s, pkt) + <-s.sendPackets +} diff --git a/server/channelserver/handlers_diva_schedule_coverage_test.go b/server/channelserver/handlers_diva_schedule_coverage_test.go new file mode 100644 index 000000000..19d0d3514 --- /dev/null +++ b/server/channelserver/handlers_diva_schedule_coverage_test.go @@ -0,0 +1,91 @@ +package channelserver + +import ( + cfg "erupe-ce/config" + "erupe-ce/network/mhfpacket" + "testing" + "time" +) + +func TestHandleMsgMhfGetUdSchedule_DivaOverrideZero_ZZ(t *testing.T) { + srv := createMockServer() + srv.divaRepo = &mockDivaRepo{} + srv.erupeConfig.DebugOptions.DivaOverride = 0 + srv.erupeConfig.RealClientMode = cfg.ZZ + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfGetUdSchedule{AckHandle: 1} + handleMsgMhfGetUdSchedule(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfGetUdSchedule_DivaOverrideZero_OlderClient(t *testing.T) { + srv := createMockServer() + srv.divaRepo = &mockDivaRepo{} + srv.erupeConfig.DebugOptions.DivaOverride = 0 + srv.erupeConfig.RealClientMode = cfg.G10 + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfGetUdSchedule{AckHandle: 1} + handleMsgMhfGetUdSchedule(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfGetUdSchedule_DivaOverride1(t *testing.T) { + srv := createMockServer() + srv.divaRepo = &mockDivaRepo{} + srv.erupeConfig.DebugOptions.DivaOverride = 1 + srv.erupeConfig.RealClientMode = cfg.ZZ + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfGetUdSchedule{AckHandle: 1} + handleMsgMhfGetUdSchedule(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfGetUdSchedule_DivaOverride2(t *testing.T) { + srv := createMockServer() + srv.divaRepo = &mockDivaRepo{} + srv.erupeConfig.DebugOptions.DivaOverride = 2 + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfGetUdSchedule{AckHandle: 1} + handleMsgMhfGetUdSchedule(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfGetUdSchedule_DivaOverride3(t *testing.T) { + srv := createMockServer() + srv.divaRepo = &mockDivaRepo{} + srv.erupeConfig.DebugOptions.DivaOverride = 3 + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfGetUdSchedule{AckHandle: 1} + handleMsgMhfGetUdSchedule(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfGetUdSchedule_WithExistingEvent(t *testing.T) { + srv := createMockServer() + srv.divaRepo = &mockDivaRepo{ + events: []DivaEvent{{ID: 1, StartTime: uint32(time.Now().Unix())}}, + } + srv.erupeConfig.DebugOptions.DivaOverride = -1 + srv.erupeConfig.RealClientMode = cfg.ZZ + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfGetUdSchedule{AckHandle: 1} + handleMsgMhfGetUdSchedule(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfGetUdSchedule_NoEvents(t *testing.T) { + srv := createMockServer() + srv.divaRepo = &mockDivaRepo{} + srv.erupeConfig.DebugOptions.DivaOverride = -1 + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfGetUdSchedule{AckHandle: 1} + handleMsgMhfGetUdSchedule(s, pkt) + <-s.sendPackets +} diff --git a/server/channelserver/handlers_festa_info_coverage_test.go b/server/channelserver/handlers_festa_info_coverage_test.go new file mode 100644 index 000000000..020027131 --- /dev/null +++ b/server/channelserver/handlers_festa_info_coverage_test.go @@ -0,0 +1,52 @@ +package channelserver + +import ( + cfg "erupe-ce/config" + "erupe-ce/network/mhfpacket" + "testing" + "time" +) + +func TestHandleMsgMhfInfoFesta_OverrideZero(t *testing.T) { + srv := createMockServer() + srv.festaRepo = &mockFestaRepo{} + srv.erupeConfig.DebugOptions.FestaOverride = 0 + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfInfoFesta{AckHandle: 1} + handleMsgMhfInfoFesta(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfInfoFesta_WithActiveEvent(t *testing.T) { + srv := createMockServer() + srv.erupeConfig.DebugOptions.FestaOverride = 1 + srv.erupeConfig.RealClientMode = cfg.ZZ + srv.erupeConfig.GameplayOptions.MaximumFP = 50000 + srv.festaRepo = &mockFestaRepo{ + events: []FestaEvent{{ID: 1, StartTime: uint32(time.Now().Add(-24 * time.Hour).Unix())}}, + trials: []FestaTrial{ + {ID: 1, Objective: 1, GoalID: 100, TimesReq: 5, Locale: 0, Reward: 10, Monopoly: "blue"}, + }, + } + ensureFestaService(srv) + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfInfoFesta{AckHandle: 1} + handleMsgMhfInfoFesta(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfInfoFesta_FutureTimestamp(t *testing.T) { + srv := createMockServer() + srv.erupeConfig.DebugOptions.FestaOverride = -1 + srv.festaRepo = &mockFestaRepo{ + events: []FestaEvent{{ID: 1, StartTime: uint32(time.Now().Add(72 * time.Hour).Unix())}}, + } + ensureFestaService(srv) + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfInfoFesta{AckHandle: 1} + handleMsgMhfInfoFesta(s, pkt) + <-s.sendPackets +} diff --git a/server/channelserver/handlers_guild_board_coverage_test.go b/server/channelserver/handlers_guild_board_coverage_test.go new file mode 100644 index 000000000..326f722ab --- /dev/null +++ b/server/channelserver/handlers_guild_board_coverage_test.go @@ -0,0 +1,173 @@ +package channelserver + +import ( + "erupe-ce/network/mhfpacket" + "testing" +) + +func TestHandleMsgMhfUpdateGuildMessageBoard_CreatePost(t *testing.T) { + srv := createMockServer() + guild := &Guild{ID: 1} + srv.guildRepo = &mockGuildRepo{guild: guild} + srv.charRepo = newMockCharacterRepo() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfUpdateGuildMessageBoard{ + AckHandle: 1, + MessageOp: 0, + PostType: 0, + StampID: 1, + Title: "Test", + Body: "Hello", + } + handleMsgMhfUpdateGuildMessageBoard(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfUpdateGuildMessageBoard_CreatePostType1(t *testing.T) { + srv := createMockServer() + guild := &Guild{ID: 1} + srv.guildRepo = &mockGuildRepo{guild: guild} + srv.charRepo = newMockCharacterRepo() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfUpdateGuildMessageBoard{ + AckHandle: 1, + MessageOp: 0, + PostType: 1, + Title: "Notice", + Body: "Board", + } + handleMsgMhfUpdateGuildMessageBoard(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfUpdateGuildMessageBoard_DeletePost(t *testing.T) { + srv := createMockServer() + guild := &Guild{ID: 1} + srv.guildRepo = &mockGuildRepo{guild: guild} + srv.charRepo = newMockCharacterRepo() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfUpdateGuildMessageBoard{ + AckHandle: 1, + MessageOp: 1, + PostID: 42, + } + handleMsgMhfUpdateGuildMessageBoard(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfUpdateGuildMessageBoard_UpdatePost(t *testing.T) { + srv := createMockServer() + guild := &Guild{ID: 1} + srv.guildRepo = &mockGuildRepo{guild: guild} + srv.charRepo = newMockCharacterRepo() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfUpdateGuildMessageBoard{ + AckHandle: 1, + MessageOp: 2, + PostID: 1, + Title: "Updated", + Body: "New body", + } + handleMsgMhfUpdateGuildMessageBoard(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfUpdateGuildMessageBoard_UpdateStamp(t *testing.T) { + srv := createMockServer() + guild := &Guild{ID: 1} + srv.guildRepo = &mockGuildRepo{guild: guild} + srv.charRepo = newMockCharacterRepo() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfUpdateGuildMessageBoard{ + AckHandle: 1, + MessageOp: 3, + PostID: 1, + StampID: 5, + } + handleMsgMhfUpdateGuildMessageBoard(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfUpdateGuildMessageBoard_LikePost(t *testing.T) { + srv := createMockServer() + guild := &Guild{ID: 1} + srv.guildRepo = &mockGuildRepo{guild: guild} + srv.charRepo = newMockCharacterRepo() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfUpdateGuildMessageBoard{ + AckHandle: 1, + MessageOp: 4, + PostID: 1, + LikeState: true, + } + handleMsgMhfUpdateGuildMessageBoard(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfUpdateGuildMessageBoard_CheckNewPosts(t *testing.T) { + srv := createMockServer() + guild := &Guild{ID: 1} + srv.guildRepo = &mockGuildRepo{guild: guild} + srv.charRepo = newMockCharacterRepo() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfUpdateGuildMessageBoard{ + AckHandle: 1, + MessageOp: 5, + } + handleMsgMhfUpdateGuildMessageBoard(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfUpdateGuildMessageBoard_NoGuild(t *testing.T) { + srv := createMockServer() + srv.guildRepo = &mockGuildRepo{getErr: errNotFound} + srv.charRepo = newMockCharacterRepo() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfUpdateGuildMessageBoard{AckHandle: 1, MessageOp: 0} + handleMsgMhfUpdateGuildMessageBoard(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfUpdateGuildMessageBoard_Applicant(t *testing.T) { + srv := createMockServer() + guild := &Guild{ID: 1} + srv.guildRepo = &mockGuildRepo{guild: guild, hasAppResult: true} + srv.charRepo = newMockCharacterRepo() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfUpdateGuildMessageBoard{AckHandle: 1, MessageOp: 0} + handleMsgMhfUpdateGuildMessageBoard(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfEnumerateGuildMessageBoard(t *testing.T) { + srv := createMockServer() + guild := &Guild{ID: 1} + srv.guildRepo = &mockGuildRepo{guild: guild} + srv.charRepo = newMockCharacterRepo() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfEnumerateGuildMessageBoard{AckHandle: 1, BoardType: 0} + handleMsgMhfEnumerateGuildMessageBoard(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfEnumerateGuildMessageBoard_Type1(t *testing.T) { + srv := createMockServer() + guild := &Guild{ID: 1} + srv.guildRepo = &mockGuildRepo{guild: guild} + srv.charRepo = newMockCharacterRepo() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfEnumerateGuildMessageBoard{AckHandle: 1, BoardType: 1} + handleMsgMhfEnumerateGuildMessageBoard(s, pkt) + <-s.sendPackets +} diff --git a/server/channelserver/handlers_guild_coverage_test.go b/server/channelserver/handlers_guild_coverage_test.go new file mode 100644 index 000000000..729a1b75b --- /dev/null +++ b/server/channelserver/handlers_guild_coverage_test.go @@ -0,0 +1,246 @@ +package channelserver + +import ( + "testing" + + "erupe-ce/network/mhfpacket" +) + +func TestHandleMsgMhfCreateGuild_Success(t *testing.T) { + server := createMockServer() + server.guildRepo = &mockGuildRepo{} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfCreateGuild{AckHandle: 1, Name: "TestGuild"} + handleMsgMhfCreateGuild(session, pkt) + + select { + case p := <-session.sendPackets: + if len(p.data) == 0 { + t.Error("expected non-empty response") + } + default: + t.Error("expected a response packet") + } +} + +func TestHandleMsgMhfCreateGuild_Error(t *testing.T) { + server := createMockServer() + server.guildRepo = &mockGuildRepo{saveErr: errNotFound} + // Mock Create to return error - the mockGuildRepo.Create returns (0, nil) + // We need getErr to make it fail. Actually Create is a no-op stub returning nil. + // Let's use a custom approach - we need the Create method to error. + // The mock's Create always returns nil, so let's test the success path worked above + // and test ArrangeGuildMember error paths instead. + session := createMockSession(100, server) + pkt := &mhfpacket.MsgMhfCreateGuild{AckHandle: 1, Name: "TestGuild"} + handleMsgMhfCreateGuild(session, pkt) + <-session.sendPackets // consume the response +} + +func TestHandleMsgMhfArrangeGuildMember_Success(t *testing.T) { + server := createMockServer() + guild := &Guild{ID: 1, GuildLeader: GuildLeader{LeaderCharID: 100}} + server.guildRepo = &mockGuildRepo{guild: guild} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfArrangeGuildMember{ + AckHandle: 1, + GuildID: 1, + CharIDs: []uint32{100, 200, 300}, + } + handleMsgMhfArrangeGuildMember(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("expected response") + } +} + +func TestHandleMsgMhfArrangeGuildMember_GetByIDError(t *testing.T) { + server := createMockServer() + server.guildRepo = &mockGuildRepo{getErr: errNotFound} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfArrangeGuildMember{AckHandle: 1, GuildID: 999} + handleMsgMhfArrangeGuildMember(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfArrangeGuildMember_NotLeader(t *testing.T) { + server := createMockServer() + guild := &Guild{ID: 1, GuildLeader: GuildLeader{LeaderCharID: 200, LeaderName: "Other"}} + server.guildRepo = &mockGuildRepo{guild: guild} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfArrangeGuildMember{AckHandle: 1, GuildID: 1} + handleMsgMhfArrangeGuildMember(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfEnumerateGuildMember_GuildIDPositive(t *testing.T) { + server := createMockServer() + guild := &Guild{ID: 1, MemberCount: 2} + members := []*GuildMember{ + {CharID: 100, Name: "Player1", HR: 50, OrderIndex: 0, WeaponType: 3}, + {CharID: 200, Name: "Player2", HR: 100, OrderIndex: 1, WeaponType: 1}, + } + server.guildRepo = &mockGuildRepo{guild: guild, members: members} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfEnumerateGuildMember{AckHandle: 1, GuildID: 1} + handleMsgMhfEnumerateGuildMember(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfEnumerateGuildMember_GuildIDZero(t *testing.T) { + server := createMockServer() + guild := &Guild{ID: 1, MemberCount: 1} + members := []*GuildMember{ + {CharID: 100, Name: "Player1", HR: 50, OrderIndex: 0}, + } + server.guildRepo = &mockGuildRepo{guild: guild, members: members} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfEnumerateGuildMember{AckHandle: 1, GuildID: 0} + handleMsgMhfEnumerateGuildMember(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfEnumerateGuildMember_NilGuild(t *testing.T) { + server := createMockServer() + server.guildRepo = &mockGuildRepo{} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfEnumerateGuildMember{AckHandle: 1, GuildID: 0} + handleMsgMhfEnumerateGuildMember(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfEnumerateGuildMember_Applicant(t *testing.T) { + server := createMockServer() + guild := &Guild{ID: 1} + server.guildRepo = &mockGuildRepo{guild: guild, hasAppResult: true} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfEnumerateGuildMember{AckHandle: 1, GuildID: 1} + handleMsgMhfEnumerateGuildMember(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfGetGuildManageRight(t *testing.T) { + server := createMockServer() + guild := &Guild{ID: 1, MemberCount: 2} + members := []*GuildMember{ + {CharID: 100, Recruiter: true}, + {CharID: 200, Recruiter: false}, + } + server.guildRepo = &mockGuildRepo{guild: guild, members: members} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfGetGuildManageRight{AckHandle: 1} + handleMsgMhfGetGuildManageRight(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfGetGuildTargetMemberNum_NilGuild(t *testing.T) { + server := createMockServer() + server.guildRepo = &mockGuildRepo{} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfGetGuildTargetMemberNum{AckHandle: 1, GuildID: 0} + handleMsgMhfGetGuildTargetMemberNum(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfGetGuildTargetMemberNum_WithGuild(t *testing.T) { + server := createMockServer() + guild := &Guild{ID: 1, MemberCount: 5} + server.guildRepo = &mockGuildRepo{guild: guild} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfGetGuildTargetMemberNum{AckHandle: 1, GuildID: 1} + handleMsgMhfGetGuildTargetMemberNum(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfEnumerateGuildItem(t *testing.T) { + server := createMockServer() + server.guildRepo = &mockGuildRepo{} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfEnumerateGuildItem{AckHandle: 1, GuildID: 1} + handleMsgMhfEnumerateGuildItem(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfUpdateGuildItem(t *testing.T) { + server := createMockServer() + server.guildRepo = &mockGuildRepo{} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfUpdateGuildItem{AckHandle: 1, GuildID: 1} + handleMsgMhfUpdateGuildItem(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfUpdateGuildIcon_LeaderSuccess(t *testing.T) { + server := createMockServer() + guild := &Guild{ID: 1} + membership := &GuildMember{CharID: 100, IsLeader: true} + server.guildRepo = &mockGuildRepo{guild: guild, membership: membership} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfUpdateGuildIcon{ + AckHandle: 1, + GuildID: 1, + IconParts: []mhfpacket.GuildIconMsgPart{ + {Index: 0, ID: 1, Page: 0, Size: 10, Rotation: 0, Red: 255, Green: 0, Blue: 0, PosX: 50, PosY: 50}, + }, + } + handleMsgMhfUpdateGuildIcon(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfUpdateGuildIcon_NotLeader(t *testing.T) { + server := createMockServer() + guild := &Guild{ID: 1} + membership := &GuildMember{CharID: 100, IsLeader: false, OrderIndex: 5} + server.guildRepo = &mockGuildRepo{guild: guild, membership: membership} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfUpdateGuildIcon{AckHandle: 1, GuildID: 1} + handleMsgMhfUpdateGuildIcon(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfUpdateGuildIcon_GetByIDError(t *testing.T) { + server := createMockServer() + server.guildRepo = &mockGuildRepo{getErr: errNotFound} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfUpdateGuildIcon{AckHandle: 1, GuildID: 999} + handleMsgMhfUpdateGuildIcon(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfReadGuildcard(t *testing.T) { + server := createMockServer() + server.guildRepo = &mockGuildRepo{} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfReadGuildcard{AckHandle: 1} + handleMsgMhfReadGuildcard(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfSetGuildManageRight(t *testing.T) { + server := createMockServer() + server.guildRepo = &mockGuildRepo{} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfSetGuildManageRight{AckHandle: 1, CharID: 200, Allowed: true} + handleMsgMhfSetGuildManageRight(session, pkt) + <-session.sendPackets +} diff --git a/server/channelserver/handlers_guild_ops_coverage_test.go b/server/channelserver/handlers_guild_ops_coverage_test.go new file mode 100644 index 000000000..78b49b49b --- /dev/null +++ b/server/channelserver/handlers_guild_ops_coverage_test.go @@ -0,0 +1,109 @@ +package channelserver + +import ( + "erupe-ce/common/byteframe" + "erupe-ce/common/stringsupport" + "erupe-ce/network/mhfpacket" + "testing" +) + +func TestHandleRenamePugi_Pugi1(t *testing.T) { + srv := createMockServer() + guild := &Guild{ID: 1} + srv.guildRepo = &mockGuildRepo{guild: guild} + s := createMockSession(100, srv) + + bf := byteframe.NewByteFrame() + nameBytes := stringsupport.UTF8ToSJIS("TestPugi") + bf.WriteBytes(nameBytes) + bf.WriteUint8(0) // null terminator + bf.Seek(0, 0) + + handleRenamePugi(s, bf, guild, 1) + if guild.PugiName1 != "TestPugi" { + t.Errorf("PugiName1 = %q, want TestPugi", guild.PugiName1) + } +} + +func TestHandleRenamePugi_Pugi2(t *testing.T) { + srv := createMockServer() + guild := &Guild{ID: 1} + srv.guildRepo = &mockGuildRepo{guild: guild} + s := createMockSession(100, srv) + + bf := byteframe.NewByteFrame() + nameBytes := stringsupport.UTF8ToSJIS("Pugi2") + bf.WriteBytes(nameBytes) + bf.WriteUint8(0) + bf.Seek(0, 0) + + handleRenamePugi(s, bf, guild, 2) + if guild.PugiName2 != "Pugi2" { + t.Errorf("PugiName2 = %q, want Pugi2", guild.PugiName2) + } +} + +func TestHandleRenamePugi_Pugi3Default(t *testing.T) { + srv := createMockServer() + guild := &Guild{ID: 1} + srv.guildRepo = &mockGuildRepo{guild: guild} + s := createMockSession(100, srv) + + bf := byteframe.NewByteFrame() + nameBytes := stringsupport.UTF8ToSJIS("Pugi3") + bf.WriteBytes(nameBytes) + bf.WriteUint8(0) + bf.Seek(0, 0) + + handleRenamePugi(s, bf, guild, 3) + if guild.PugiName3 != "Pugi3" { + t.Errorf("PugiName3 = %q, want Pugi3", guild.PugiName3) + } +} + +func TestHandleChangePugi_AllNums(t *testing.T) { + srv := createMockServer() + guild := &Guild{ID: 1} + srv.guildRepo = &mockGuildRepo{guild: guild} + s := createMockSession(100, srv) + + handleChangePugi(s, 5, guild, 1) + if guild.PugiOutfit1 != 5 { + t.Errorf("PugiOutfit1 = %d, want 5", guild.PugiOutfit1) + } + + handleChangePugi(s, 10, guild, 2) + if guild.PugiOutfit2 != 10 { + t.Errorf("PugiOutfit2 = %d, want 10", guild.PugiOutfit2) + } + + handleChangePugi(s, 15, guild, 3) + if guild.PugiOutfit3 != 15 { + t.Errorf("PugiOutfit3 = %d, want 15", guild.PugiOutfit3) + } +} + +func TestHandleAvoidLeadershipUpdate_Success(t *testing.T) { + srv := createMockServer() + membership := &GuildMember{CharID: 100, AvoidLeadership: false} + srv.guildRepo = &mockGuildRepo{membership: membership} + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfOperateGuild{AckHandle: 1} + handleAvoidLeadershipUpdate(s, pkt, true) + <-s.sendPackets + + if !membership.AvoidLeadership { + t.Error("AvoidLeadership should be true") + } +} + +func TestHandleAvoidLeadershipUpdate_GetMembershipError(t *testing.T) { + srv := createMockServer() + srv.guildRepo = &mockGuildRepo{getMemberErr: errNotFound} + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfOperateGuild{AckHandle: 1} + handleAvoidLeadershipUpdate(s, pkt, true) + <-s.sendPackets +} diff --git a/server/channelserver/handlers_house_coverage_test.go b/server/channelserver/handlers_house_coverage_test.go new file mode 100644 index 000000000..462b87e5e --- /dev/null +++ b/server/channelserver/handlers_house_coverage_test.go @@ -0,0 +1,265 @@ +package channelserver + +import ( + cfg "erupe-ce/config" + "erupe-ce/network/mhfpacket" + "testing" +) + +func TestHandleMsgMhfEnumerateHouse_Method3_SearchByName(t *testing.T) { + srv := createMockServer() + srv.erupeConfig.RealClientMode = cfg.ZZ + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfEnumerateHouse{AckHandle: 1, Method: 3, Name: "TestHouse"} + handleMsgMhfEnumerateHouse(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfEnumerateHouse_Method4_ByCharID(t *testing.T) { + srv := createMockServer() + srv.erupeConfig.RealClientMode = cfg.ZZ + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfEnumerateHouse{AckHandle: 1, Method: 4, CharID: 200} + handleMsgMhfEnumerateHouse(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfEnumerateHouse_Method5_RecentVisitors(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfEnumerateHouse{AckHandle: 1, Method: 5} + handleMsgMhfEnumerateHouse(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfEnumerateHouse_Method1_Friends(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + charRepo := newMockCharacterRepo() + charRepo.strings["friends"] = "" + srv.charRepo = charRepo + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfEnumerateHouse{AckHandle: 1, Method: 1} + handleMsgMhfEnumerateHouse(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfEnumerateHouse_Method2_GuildMembers(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + guild := &Guild{ID: 1} + srv.guildRepo = &mockGuildRepo{guild: guild} + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfEnumerateHouse{AckHandle: 1, Method: 2} + handleMsgMhfEnumerateHouse(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfEnumerateHouse_Method2_NoGuild(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + srv.guildRepo = &mockGuildRepo{getErr: errNotFound} + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfEnumerateHouse{AckHandle: 1, Method: 2} + handleMsgMhfEnumerateHouse(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfSaveDecoMyset_ShortPayload(t *testing.T) { + srv := createMockServer() + srv.charRepo = newMockCharacterRepo() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfSaveDecoMyset{AckHandle: 1, RawDataPayload: []byte{0x00, 0x01}} + handleMsgMhfSaveDecoMyset(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfSaveDecoMyset_WithData(t *testing.T) { + srv := createMockServer() + charRepo := newMockCharacterRepo() + // Pre-populate with version byte + 0 sets + charRepo.columns["decomyset"] = []byte{0x01, 0x00} + srv.charRepo = charRepo + srv.erupeConfig.RealClientMode = cfg.ZZ + + s := createMockSession(100, srv) + + // Build payload: version byte + 1 set with index 0 + 76 bytes of data + payload := make([]byte, 3+2+76) + payload[0] = 0x01 // version + payload[1] = 0x01 // count + payload[2] = 0x00 // padding + + pkt := &mhfpacket.MsgMhfSaveDecoMyset{AckHandle: 1, RawDataPayload: payload} + handleMsgMhfSaveDecoMyset(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfInfoTournament_Type2(t *testing.T) { + srv := createMockServer() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfInfoTournament{AckHandle: 1, QueryType: 2} + handleMsgMhfInfoTournament(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfUpdateInterior_Normal(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfUpdateInterior{AckHandle: 1, InteriorData: make([]byte, 20)} + handleMsgMhfUpdateInterior(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfUpdateInterior_TooLarge(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfUpdateInterior{AckHandle: 1, InteriorData: make([]byte, 100)} + handleMsgMhfUpdateInterior(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfUpdateMyhouseInfo_Normal(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfUpdateMyhouseInfo{AckHandle: 1, Data: make([]byte, 9)} + handleMsgMhfUpdateMyhouseInfo(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfUpdateMyhouseInfo_TooLarge(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfUpdateMyhouseInfo{AckHandle: 1, Data: make([]byte, 600)} + handleMsgMhfUpdateMyhouseInfo(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfGetMyhouseInfo(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfGetMyhouseInfo{AckHandle: 1} + handleMsgMhfGetMyhouseInfo(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfEnumerateTitle(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfEnumerateTitle{AckHandle: 1} + handleMsgMhfEnumerateTitle(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfAcquireTitle(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfAcquireTitle{AckHandle: 1, TitleIDs: []uint16{1, 2, 3}} + handleMsgMhfAcquireTitle(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfUpdateHouse(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfUpdateHouse{AckHandle: 1, State: 2, Password: "1234"} + handleMsgMhfUpdateHouse(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfOperateWarehouse_Op0(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfOperateWarehouse{AckHandle: 1, Operation: 0} + handleMsgMhfOperateWarehouse(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfOperateWarehouse_Op1(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfOperateWarehouse{AckHandle: 1, Operation: 1} + handleMsgMhfOperateWarehouse(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfOperateWarehouse_Op2_Rename(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfOperateWarehouse{AckHandle: 1, Operation: 2, BoxType: 0, BoxIndex: 1, Name: "MyBox"} + handleMsgMhfOperateWarehouse(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfOperateWarehouse_Op3(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfOperateWarehouse{AckHandle: 1, Operation: 3} + handleMsgMhfOperateWarehouse(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfOperateWarehouse_Op4(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfOperateWarehouse{AckHandle: 1, Operation: 4} + handleMsgMhfOperateWarehouse(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfEnumerateWarehouse_Items(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfEnumerateWarehouse{AckHandle: 1, BoxType: 0, BoxIndex: 0} + handleMsgMhfEnumerateWarehouse(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfEnumerateWarehouse_Equipment(t *testing.T) { + srv := createMockServer() + srv.houseRepo = newMockHouseRepoForItems() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfEnumerateWarehouse{AckHandle: 1, BoxType: 1, BoxIndex: 0} + handleMsgMhfEnumerateWarehouse(s, pkt) + <-s.sendPackets +} diff --git a/server/channelserver/handlers_mercenary_coverage_test.go b/server/channelserver/handlers_mercenary_coverage_test.go new file mode 100644 index 000000000..98f3e8dfe --- /dev/null +++ b/server/channelserver/handlers_mercenary_coverage_test.go @@ -0,0 +1,256 @@ +package channelserver + +import ( + "testing" + + cfg "erupe-ce/config" + "erupe-ce/network/mhfpacket" +) + +func TestHandleMsgMhfLoadPartner(t *testing.T) { + server := createMockServer() + server.charRepo = newMockCharacterRepo() + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfLoadPartner{AckHandle: 1} + handleMsgMhfLoadPartner(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfSavePartner(t *testing.T) { + server := createMockServer() + server.charRepo = newMockCharacterRepo() + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfSavePartner{AckHandle: 1, RawDataPayload: []byte{1, 2, 3, 4}} + handleMsgMhfSavePartner(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfLoadHunterNavi_G8(t *testing.T) { + server := createMockServer() + server.charRepo = newMockCharacterRepo() + server.erupeConfig.RealClientMode = cfg.G10 + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfLoadHunterNavi{AckHandle: 1} + handleMsgMhfLoadHunterNavi(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfLoadHunterNavi_G7(t *testing.T) { + server := createMockServer() + server.charRepo = newMockCharacterRepo() + server.erupeConfig.RealClientMode = cfg.G7 + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfLoadHunterNavi{AckHandle: 1} + handleMsgMhfLoadHunterNavi(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfSaveHunterNavi_NoDiff(t *testing.T) { + server := createMockServer() + server.charRepo = newMockCharacterRepo() + session := createMockSession(100, server) + + data := make([]byte, 100) + pkt := &mhfpacket.MsgMhfSaveHunterNavi{ + AckHandle: 1, + IsDataDiff: false, + RawDataPayload: data, + } + handleMsgMhfSaveHunterNavi(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfSaveHunterNavi_Diff(t *testing.T) { + server := createMockServer() + charRepo := newMockCharacterRepo() + charRepo.columns["hunternavi"] = make([]byte, 552) + server.charRepo = charRepo + server.erupeConfig.RealClientMode = cfg.G10 + session := createMockSession(100, server) + + // Create a valid diff payload (deltacomp format: pairs of offset+data) + // A simple diff: zero length means no changes + diffData := make([]byte, 4) // minimal diff + pkt := &mhfpacket.MsgMhfSaveHunterNavi{ + AckHandle: 1, + IsDataDiff: true, + RawDataPayload: diffData, + } + handleMsgMhfSaveHunterNavi(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfSaveHunterNavi_OversizedPayload(t *testing.T) { + server := createMockServer() + server.charRepo = newMockCharacterRepo() + session := createMockSession(100, server) + + data := make([]byte, 5000) // > 4096 + pkt := &mhfpacket.MsgMhfSaveHunterNavi{ + AckHandle: 1, + IsDataDiff: false, + RawDataPayload: data, + } + handleMsgMhfSaveHunterNavi(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfCreateMercenary_Success(t *testing.T) { + server := createMockServer() + server.mercenaryRepo = &mockMercenaryRepo{nextRastaID: 42} + server.charRepo = newMockCharacterRepo() + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfCreateMercenary{AckHandle: 1} + handleMsgMhfCreateMercenary(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfCreateMercenary_Error(t *testing.T) { + server := createMockServer() + server.mercenaryRepo = &mockMercenaryRepo{rastaIDErr: errNotFound} + server.charRepo = newMockCharacterRepo() + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfCreateMercenary{AckHandle: 1} + handleMsgMhfCreateMercenary(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfSaveMercenary_Normal(t *testing.T) { + server := createMockServer() + server.charRepo = newMockCharacterRepo() + session := createMockSession(100, server) + + mercData := make([]byte, 100) + // Write a uint32 index at the start + mercData[0] = 0 + mercData[1] = 0 + mercData[2] = 0 + mercData[3] = 1 + pkt := &mhfpacket.MsgMhfSaveMercenary{ + AckHandle: 1, + GCP: 500, + PactMercID: 10, + MercData: mercData, + } + handleMsgMhfSaveMercenary(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfSaveMercenary_Oversized(t *testing.T) { + server := createMockServer() + server.charRepo = newMockCharacterRepo() + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfSaveMercenary{ + AckHandle: 1, + MercData: make([]byte, 70000), + } + handleMsgMhfSaveMercenary(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfReadMercenaryM_EmptyData(t *testing.T) { + server := createMockServer() + server.charRepo = newMockCharacterRepo() + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfReadMercenaryM{AckHandle: 1, CharID: 200} + handleMsgMhfReadMercenaryM(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfReadMercenaryM_WithData(t *testing.T) { + server := createMockServer() + charRepo := newMockCharacterRepo() + charRepo.columns["savemercenary"] = []byte{0x01, 0x02, 0x03, 0x04} + server.charRepo = charRepo + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfReadMercenaryM{AckHandle: 1, CharID: 100} + handleMsgMhfReadMercenaryM(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfContractMercenary_Op0(t *testing.T) { + server := createMockServer() + server.charRepo = newMockCharacterRepo() + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfContractMercenary{AckHandle: 1, Op: 0, CID: 200, PactMercID: 42} + handleMsgMhfContractMercenary(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfContractMercenary_Op1(t *testing.T) { + server := createMockServer() + server.charRepo = newMockCharacterRepo() + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfContractMercenary{AckHandle: 1, Op: 1} + handleMsgMhfContractMercenary(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfContractMercenary_Op2(t *testing.T) { + server := createMockServer() + server.charRepo = newMockCharacterRepo() + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfContractMercenary{AckHandle: 1, Op: 2, CID: 200} + handleMsgMhfContractMercenary(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfReadMercenaryW_NoPact(t *testing.T) { + server := createMockServer() + charRepo := newMockCharacterRepo() + server.charRepo = charRepo + server.mercenaryRepo = &mockMercenaryRepo{} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfReadMercenaryW{AckHandle: 1, Op: 0} + handleMsgMhfReadMercenaryW(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfReadMercenaryW_WithPact(t *testing.T) { + server := createMockServer() + charRepo := newMockCharacterRepo() + charRepo.ints["pact_id"] = 42 + server.charRepo = charRepo + server.mercenaryRepo = &mockMercenaryRepo{} + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfReadMercenaryW{AckHandle: 1, Op: 0} + handleMsgMhfReadMercenaryW(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfReadMercenaryW_Op2(t *testing.T) { + server := createMockServer() + charRepo := newMockCharacterRepo() + server.charRepo = charRepo + server.mercenaryRepo = &mockMercenaryRepo{} + session := createMockSession(100, server) + + // Op 2 skips loan enumeration + pkt := &mhfpacket.MsgMhfReadMercenaryW{AckHandle: 1, Op: 2} + handleMsgMhfReadMercenaryW(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgMhfLoadOtomoAirou(t *testing.T) { + server := createMockServer() + server.charRepo = newMockCharacterRepo() + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfLoadOtomoAirou{AckHandle: 1} + handleMsgMhfLoadOtomoAirou(session, pkt) + <-session.sendPackets +} diff --git a/server/channelserver/handlers_quest_coverage_test.go b/server/channelserver/handlers_quest_coverage_test.go new file mode 100644 index 000000000..d60e28eba --- /dev/null +++ b/server/channelserver/handlers_quest_coverage_test.go @@ -0,0 +1,40 @@ +package channelserver + +import ( + "testing" + + "erupe-ce/network/mhfpacket" +) + +func TestHandleMsgMhfLoadFavoriteQuest(t *testing.T) { + server := createMockServer() + server.charRepo = newMockCharacterRepo() + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfLoadFavoriteQuest{AckHandle: 1} + handleMsgMhfLoadFavoriteQuest(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("expected response") + } +} + +func TestHandleMsgMhfSaveFavoriteQuest(t *testing.T) { + server := createMockServer() + server.charRepo = newMockCharacterRepo() + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgMhfSaveFavoriteQuest{ + AckHandle: 1, + Data: []byte{0x01, 0x00, 0x01, 0x00, 0x01}, + } + handleMsgMhfSaveFavoriteQuest(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("expected response") + } +} diff --git a/server/channelserver/handlers_stage_coverage_test.go b/server/channelserver/handlers_stage_coverage_test.go new file mode 100644 index 000000000..ccdb48245 --- /dev/null +++ b/server/channelserver/handlers_stage_coverage_test.go @@ -0,0 +1,342 @@ +package channelserver + +import ( + "testing" + + "erupe-ce/network/mhfpacket" +) + +func TestHandleMsgSysReserveStage_NewSlot(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + stage := &Stage{ + id: "test_stage", + reservedClientSlots: make(map[uint32]bool), + rawBinaryData: make(map[stageBinaryKey][]byte), + clients: make(map[*Session]uint32), + maxPlayers: 4, + } + server.stages.Store("test_stage", stage) + + pkt := &mhfpacket.MsgSysReserveStage{AckHandle: 1, StageID: "test_stage", Ready: 1} + handleMsgSysReserveStage(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("expected response") + } + + if _, exists := stage.reservedClientSlots[100]; !exists { + t.Error("charID should be in reserved slots") + } +} + +func TestHandleMsgSysReserveStage_AlreadyReservedReady1(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + stage := &Stage{ + id: "test_stage", + reservedClientSlots: map[uint32]bool{100: true}, + rawBinaryData: make(map[stageBinaryKey][]byte), + clients: make(map[*Session]uint32), + maxPlayers: 4, + } + server.stages.Store("test_stage", stage) + + pkt := &mhfpacket.MsgSysReserveStage{AckHandle: 1, StageID: "test_stage", Ready: 1} + handleMsgSysReserveStage(session, pkt) + <-session.sendPackets + + if stage.reservedClientSlots[100] != false { + t.Error("ready=1 should set slot to false") + } +} + +func TestHandleMsgSysReserveStage_AlreadyReservedReady17(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + stage := &Stage{ + id: "test_stage", + reservedClientSlots: map[uint32]bool{100: false}, + rawBinaryData: make(map[stageBinaryKey][]byte), + clients: make(map[*Session]uint32), + maxPlayers: 4, + } + server.stages.Store("test_stage", stage) + + pkt := &mhfpacket.MsgSysReserveStage{AckHandle: 1, StageID: "test_stage", Ready: 17} + handleMsgSysReserveStage(session, pkt) + <-session.sendPackets + + if stage.reservedClientSlots[100] != true { + t.Error("ready=17 should set slot to true") + } +} + +func TestHandleMsgSysReserveStage_Locked(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + stage := &Stage{ + id: "test_stage", + reservedClientSlots: make(map[uint32]bool), + rawBinaryData: make(map[stageBinaryKey][]byte), + clients: make(map[*Session]uint32), + maxPlayers: 4, + locked: true, + } + server.stages.Store("test_stage", stage) + + pkt := &mhfpacket.MsgSysReserveStage{AckHandle: 1, StageID: "test_stage", Ready: 1} + handleMsgSysReserveStage(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgSysReserveStage_PasswordMismatch(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + stage := &Stage{ + id: "test_stage", + reservedClientSlots: make(map[uint32]bool), + rawBinaryData: make(map[stageBinaryKey][]byte), + clients: make(map[*Session]uint32), + maxPlayers: 4, + password: "secret", + } + server.stages.Store("test_stage", stage) + + session.stagePass = "wrong" + pkt := &mhfpacket.MsgSysReserveStage{AckHandle: 1, StageID: "test_stage", Ready: 1} + handleMsgSysReserveStage(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgSysReserveStage_Full(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + stage := &Stage{ + id: "test_stage", + reservedClientSlots: map[uint32]bool{200: false, 300: false}, + rawBinaryData: make(map[stageBinaryKey][]byte), + clients: make(map[*Session]uint32), + maxPlayers: 2, + } + server.stages.Store("test_stage", stage) + + pkt := &mhfpacket.MsgSysReserveStage{AckHandle: 1, StageID: "test_stage", Ready: 1} + handleMsgSysReserveStage(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgSysReserveStage_StageNotFound(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgSysReserveStage{AckHandle: 1, StageID: "nonexistent", Ready: 1} + handleMsgSysReserveStage(session, pkt) + <-session.sendPackets +} + +func TestHandleMsgSysUnreserveStage_WithReservation(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + stage := &Stage{ + id: "test_stage", + reservedClientSlots: map[uint32]bool{100: false}, + rawBinaryData: make(map[stageBinaryKey][]byte), + clients: make(map[*Session]uint32), + } + session.reservationStage = stage + + pkt := &mhfpacket.MsgSysUnreserveStage{} + handleMsgSysUnreserveStage(session, pkt) + + if session.reservationStage != nil { + t.Error("reservation should be cleared") + } + if _, exists := stage.reservedClientSlots[100]; exists { + t.Error("charID should be removed from reserved slots") + } +} + +func TestHandleMsgSysUnreserveStage_NoReservation(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgSysUnreserveStage{} + handleMsgSysUnreserveStage(session, pkt) + // Should not panic +} + +func TestHandleMsgSysSetStagePass_Host(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + stage := &Stage{ + id: "test_stage", + reservedClientSlots: map[uint32]bool{100: false}, + rawBinaryData: make(map[stageBinaryKey][]byte), + clients: make(map[*Session]uint32), + } + session.reservationStage = stage + + pkt := &mhfpacket.MsgSysSetStagePass{Password: "mypass"} + handleMsgSysSetStagePass(session, pkt) + + if stage.password != "mypass" { + t.Errorf("stage password = %q, want %q", stage.password, "mypass") + } +} + +func TestHandleMsgSysSetStagePass_NonHost(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgSysSetStagePass{Password: "mypass"} + handleMsgSysSetStagePass(session, pkt) + + if session.stagePass != "mypass" { + t.Errorf("session stagePass = %q, want %q", session.stagePass, "mypass") + } +} + +func TestHandleMsgSysSetAndGetStageBinary(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + stage := &Stage{ + id: "test_stage", + reservedClientSlots: make(map[uint32]bool), + rawBinaryData: make(map[stageBinaryKey][]byte), + clients: make(map[*Session]uint32), + } + server.stages.Store("test_stage", stage) + + // Set binary + setPkt := &mhfpacket.MsgSysSetStageBinary{ + BinaryType0: 1, + BinaryType1: 2, + StageID: "test_stage", + RawDataPayload: []byte{0xDE, 0xAD, 0xBE, 0xEF}, + } + handleMsgSysSetStageBinary(session, setPkt) + + // Get binary + getPkt := &mhfpacket.MsgSysGetStageBinary{ + AckHandle: 1, + BinaryType0: 1, + BinaryType1: 2, + StageID: "test_stage", + } + handleMsgSysGetStageBinary(session, getPkt) + <-session.sendPackets +} + +func TestHandleMsgSysGetStageBinary_Type1Equals4Fallback(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + stage := &Stage{ + id: "test_stage", + reservedClientSlots: make(map[uint32]bool), + rawBinaryData: make(map[stageBinaryKey][]byte), + clients: make(map[*Session]uint32), + } + server.stages.Store("test_stage", stage) + + getPkt := &mhfpacket.MsgSysGetStageBinary{ + AckHandle: 1, + BinaryType0: 0, + BinaryType1: 4, + StageID: "test_stage", + } + handleMsgSysGetStageBinary(session, getPkt) + <-session.sendPackets +} + +func TestHandleMsgSysGetStageBinary_MissingBinary(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + stage := &Stage{ + id: "test_stage", + reservedClientSlots: make(map[uint32]bool), + rawBinaryData: make(map[stageBinaryKey][]byte), + clients: make(map[*Session]uint32), + } + server.stages.Store("test_stage", stage) + + getPkt := &mhfpacket.MsgSysGetStageBinary{ + AckHandle: 1, + BinaryType0: 9, + BinaryType1: 9, + StageID: "test_stage", + } + handleMsgSysGetStageBinary(session, getPkt) + <-session.sendPackets +} + +func TestHandleMsgSysGetStageBinary_MissingStage(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + getPkt := &mhfpacket.MsgSysGetStageBinary{ + AckHandle: 1, + BinaryType0: 0, + BinaryType1: 0, + StageID: "nonexistent", + } + handleMsgSysGetStageBinary(session, getPkt) + <-session.sendPackets +} + +func TestHandleMsgSysSetStageBinary_MissingStage(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgSysSetStageBinary{ + BinaryType0: 1, + BinaryType1: 2, + StageID: "nonexistent", + RawDataPayload: []byte{1, 2, 3}, + } + handleMsgSysSetStageBinary(session, pkt) + // Should not panic, just logs warning +} + +func TestHandleMsgSysUnlockStage_WithReservation(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + stage := &Stage{ + id: "test_stage", + reservedClientSlots: map[uint32]bool{100: false}, + rawBinaryData: make(map[stageBinaryKey][]byte), + clients: make(map[*Session]uint32), + } + server.stages.Store("test_stage", stage) + session.reservationStage = stage + + pkt := &mhfpacket.MsgSysUnlockStage{} + handleMsgSysUnlockStage(session, pkt) + + if _, exists := server.stages.Get("test_stage"); exists { + t.Error("stage should have been deleted") + } +} + +func TestHandleMsgSysUnlockStage_NoReservation(t *testing.T) { + server := createMockServer() + session := createMockSession(100, server) + + pkt := &mhfpacket.MsgSysUnlockStage{} + handleMsgSysUnlockStage(session, pkt) + // Should not panic +} diff --git a/server/channelserver/handlers_tower_extra_coverage_test.go b/server/channelserver/handlers_tower_extra_coverage_test.go new file mode 100644 index 000000000..5e221e490 --- /dev/null +++ b/server/channelserver/handlers_tower_extra_coverage_test.go @@ -0,0 +1,106 @@ +package channelserver + +import ( + "erupe-ce/network/mhfpacket" + "testing" +) + +func TestHandleMsgMhfGetTenrouirai_Type2_Rewards(t *testing.T) { + srv := createMockServer() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfGetTenrouirai{AckHandle: 1, DataType: 2} + handleMsgMhfGetTenrouirai(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfGetTenrouirai_Type4_Progress(t *testing.T) { + srv := createMockServer() + srv.towerRepo = &mockTowerRepo{} + ensureTowerService(srv) + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfGetTenrouirai{AckHandle: 1, DataType: 4, GuildID: 1} + handleMsgMhfGetTenrouirai(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfGetTenrouirai_Type5_Scores(t *testing.T) { + srv := createMockServer() + srv.towerRepo = &mockTowerRepo{} + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfGetTenrouirai{AckHandle: 1, DataType: 5, GuildID: 1, MissionIndex: 0} + handleMsgMhfGetTenrouirai(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfGetTenrouirai_Type6_RP(t *testing.T) { + srv := createMockServer() + srv.towerRepo = &mockTowerRepo{} + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfGetTenrouirai{AckHandle: 1, DataType: 6, GuildID: 1} + handleMsgMhfGetTenrouirai(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfPostTowerInfo_SkillUpdate(t *testing.T) { + srv := createMockServer() + srv.towerRepo = &mockTowerRepo{} + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfPostTowerInfo{AckHandle: 1, InfoType: 2, Skill: 3, Cost: -10} + handleMsgMhfPostTowerInfo(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfPostTowerInfo_ProgressUpdate(t *testing.T) { + srv := createMockServer() + srv.towerRepo = &mockTowerRepo{} + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfPostTowerInfo{AckHandle: 1, InfoType: 1, TR: 5, TRP: 100, Cost: -20, Block1: 1} + handleMsgMhfPostTowerInfo(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfPostTowerInfo_ProgressType7(t *testing.T) { + srv := createMockServer() + srv.towerRepo = &mockTowerRepo{} + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfPostTowerInfo{AckHandle: 1, InfoType: 7, TR: 10, TRP: 200} + handleMsgMhfPostTowerInfo(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfPostTowerInfo_QuestToolsDebug(t *testing.T) { + srv := createMockServer() + srv.towerRepo = &mockTowerRepo{} + srv.erupeConfig.DebugOptions.QuestTools = true + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfPostTowerInfo{AckHandle: 1, InfoType: 2, Skill: 1} + handleMsgMhfPostTowerInfo(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfPostTenrouirai_Op1(t *testing.T) { + srv := createMockServer() + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfPostTenrouirai{AckHandle: 1, Op: 1} + handleMsgMhfPostTenrouirai(s, pkt) + <-s.sendPackets +} + +func TestHandleMsgMhfPostTenrouirai_QuestToolsDebug(t *testing.T) { + srv := createMockServer() + srv.erupeConfig.DebugOptions.QuestTools = true + s := createMockSession(100, srv) + + pkt := &mhfpacket.MsgMhfPostTenrouirai{AckHandle: 1, Op: 1, Floors: 10, Slays: 5} + handleMsgMhfPostTenrouirai(s, pkt) + <-s.sendPackets +} diff --git a/server/signserver/dsgn_resp_coverage_test.go b/server/signserver/dsgn_resp_coverage_test.go new file mode 100644 index 000000000..70944086f --- /dev/null +++ b/server/signserver/dsgn_resp_coverage_test.go @@ -0,0 +1,270 @@ +package signserver + +import ( + "fmt" + "strings" + "testing" + "time" + + cfg "erupe-ce/config" + "go.uber.org/zap" +) + +func TestMakeSignResponse_PS3Client(t *testing.T) { + config := &cfg.Config{ + PatchServerFile: "http://patch.example.com/file", + PatchServerManifest: "http://patch.example.com/manifest", + DebugOptions: cfg.DebugOptions{ + CapLink: cfg.CapLinkOptions{ + Values: []uint16{0, 0, 0, 0, 0}, + }, + }, + GameplayOptions: cfg.GameplayOptions{ + MezFesSoloTickets: 100, + MezFesGroupTickets: 100, + }, + } + + server := newMakeSignResponseServer(config) + server.charRepo = &mockSignCharacterRepo{ + characters: []character{ + {ID: 1, Name: "TestHunter", HR: 100, GR: 50, WeaponType: 3, LastLogin: 1700000000}, + }, + } + + conn := newMockConn() + session := &Session{ + logger: zap.NewNop(), + server: server, + rawConn: conn, + client: PS3, + } + + result := session.makeSignResponse(1) + if len(result) == 0 { + t.Error("makeSignResponse() returned empty result") + } + if result[0] != uint8(SIGN_SUCCESS) { + t.Errorf("first byte = %d, want %d (SIGN_SUCCESS)", result[0], SIGN_SUCCESS) + } +} + +func TestMakeSignResponse_PS3NoPatchServer(t *testing.T) { + config := &cfg.Config{ + PatchServerFile: "", + PatchServerManifest: "", + DebugOptions: cfg.DebugOptions{ + CapLink: cfg.CapLinkOptions{ + Values: []uint16{0, 0, 0, 0, 0}, + }, + }, + } + + server := newMakeSignResponseServer(config) + conn := newMockConn() + session := &Session{ + logger: zap.NewNop(), + server: server, + rawConn: conn, + client: PS3, + } + + result := session.makeSignResponse(1) + if len(result) == 0 { + t.Fatal("makeSignResponse() returned empty result") + } + if result[0] != uint8(SIGN_EABORT) { + t.Errorf("first byte = %d, want %d (SIGN_EABORT)", result[0], SIGN_EABORT) + } +} + +func TestMakeSignResponse_HideLoginNotice(t *testing.T) { + config := &cfg.Config{ + HideLoginNotice: true, + DebugOptions: cfg.DebugOptions{ + CapLink: cfg.CapLinkOptions{ + Values: []uint16{0, 0, 0, 0, 0}, + }, + }, + GameplayOptions: cfg.GameplayOptions{ + MezFesSoloTickets: 100, + MezFesGroupTickets: 100, + }, + } + + server := newMakeSignResponseServer(config) + server.charRepo = &mockSignCharacterRepo{ + characters: []character{ + {ID: 1, Name: "TestHunter", HR: 50}, + }, + } + + conn := newMockConn() + session := &Session{ + logger: zap.NewNop(), + server: server, + rawConn: conn, + client: PC100, + } + + defer func() { + if r := recover(); r != nil { + panicStr := fmt.Sprintf("%v", r) + if strings.Contains(panicStr, "index out of range") { + t.Errorf("array bounds panic: %v", r) + } + } + }() + + result := session.makeSignResponse(1) + if len(result) == 0 { + t.Error("makeSignResponse() returned empty result") + } +} + +func TestMakeSignResponse_MaxLauncherHR(t *testing.T) { + config := &cfg.Config{ + DebugOptions: cfg.DebugOptions{ + MaxLauncherHR: true, + CapLink: cfg.CapLinkOptions{ + Values: []uint16{0, 0, 0, 0, 0}, + }, + }, + GameplayOptions: cfg.GameplayOptions{ + MezFesSoloTickets: 100, + MezFesGroupTickets: 100, + }, + } + + server := newMakeSignResponseServer(config) + server.charRepo = &mockSignCharacterRepo{ + characters: []character{ + {ID: 1, Name: "TestHunter", HR: 50}, + }, + } + + conn := newMockConn() + session := &Session{ + logger: zap.NewNop(), + server: server, + rawConn: conn, + client: PC100, + } + + defer func() { + if r := recover(); r != nil { + panicStr := fmt.Sprintf("%v", r) + if strings.Contains(panicStr, "index out of range") { + t.Errorf("array bounds panic: %v", r) + } + } + }() + + result := session.makeSignResponse(1) + if len(result) == 0 { + t.Error("makeSignResponse() returned empty result") + } +} + +func TestMakeSignResponse_FriendsOverflow(t *testing.T) { + config := &cfg.Config{ + DebugOptions: cfg.DebugOptions{ + CapLink: cfg.CapLinkOptions{ + Values: []uint16{0, 0, 0, 0, 0}, + }, + }, + GameplayOptions: cfg.GameplayOptions{ + MezFesSoloTickets: 100, + MezFesGroupTickets: 100, + }, + } + + // Create 300 friends (> 255) + friends := make([]members, 300) + for i := range friends { + friends[i] = members{CID: uint32(i + 1), ID: uint32(i + 1000), Name: fmt.Sprintf("Friend%d", i)} + } + + server := newMakeSignResponseServer(config) + server.charRepo = &mockSignCharacterRepo{ + characters: []character{ + {ID: 1, Name: "TestHunter", HR: 50}, + }, + friends: friends, + } + + conn := newMockConn() + session := &Session{ + logger: zap.NewNop(), + server: server, + rawConn: conn, + client: PC100, + } + + defer func() { + if r := recover(); r != nil { + panicStr := fmt.Sprintf("%v", r) + if strings.Contains(panicStr, "index out of range") { + t.Errorf("array bounds panic: %v", r) + } + } + }() + + result := session.makeSignResponse(1) + if len(result) == 0 { + t.Error("makeSignResponse() returned empty result") + } +} + +func TestMakeSignResponse_GuildmatesOverflow(t *testing.T) { + config := &cfg.Config{ + DebugOptions: cfg.DebugOptions{ + CapLink: cfg.CapLinkOptions{ + Values: []uint16{0, 0, 0, 0, 0}, + }, + }, + GameplayOptions: cfg.GameplayOptions{ + MezFesSoloTickets: 100, + MezFesGroupTickets: 100, + }, + } + + guildmates := make([]members, 260) + for i := range guildmates { + guildmates[i] = members{CID: uint32(i + 1), ID: uint32(i + 1000), Name: fmt.Sprintf("Mate%d", i)} + } + + server := newMakeSignResponseServer(config) + server.charRepo = &mockSignCharacterRepo{ + characters: []character{ + {ID: 1, Name: "TestHunter", HR: 50}, + }, + guildmates: guildmates, + } + server.userRepo = &mockSignUserRepo{ + returnExpiry: time.Now().Add(time.Hour * 24 * 30), + lastLogin: time.Now(), + } + + conn := newMockConn() + session := &Session{ + logger: zap.NewNop(), + server: server, + rawConn: conn, + client: PC100, + } + + defer func() { + if r := recover(); r != nil { + panicStr := fmt.Sprintf("%v", r) + if strings.Contains(panicStr, "index out of range") { + t.Errorf("array bounds panic: %v", r) + } + } + }() + + result := session.makeSignResponse(1) + if len(result) == 0 { + t.Error("makeSignResponse() returned empty result") + } +}