From 2a5cd50e3fd93b9093bec0ca46fc1c14ad389aa8 Mon Sep 17 00:00:00 2001 From: Houmgaor Date: Sat, 21 Feb 2026 18:10:19 +0100 Subject: [PATCH] test(channelserver): add handler tests for guild info, alliance, cooking, adventure, and treasure Cover 5 more handler files with mock-based unit tests, bringing package coverage from 43.7% to 47.7%. Extend mockGuildRepoOps with alliance, cooking, adventure, treasure hunt, and hunt data methods. --- .../handlers_guild_adventure_test.go | 200 +++++++++++ .../handlers_guild_alliance_test.go | 320 ++++++++++++++++++ .../handlers_guild_cooking_test.go | 306 +++++++++++++++++ .../channelserver/handlers_guild_info_test.go | 227 +++++++++++++ .../handlers_guild_tresure_test.go | 204 +++++++++++ server/channelserver/repo_mocks_test.go | 139 ++++++++ 6 files changed, 1396 insertions(+) create mode 100644 server/channelserver/handlers_guild_adventure_test.go create mode 100644 server/channelserver/handlers_guild_alliance_test.go create mode 100644 server/channelserver/handlers_guild_cooking_test.go create mode 100644 server/channelserver/handlers_guild_info_test.go create mode 100644 server/channelserver/handlers_guild_tresure_test.go diff --git a/server/channelserver/handlers_guild_adventure_test.go b/server/channelserver/handlers_guild_adventure_test.go new file mode 100644 index 000000000..a353adad6 --- /dev/null +++ b/server/channelserver/handlers_guild_adventure_test.go @@ -0,0 +1,200 @@ +package channelserver + +import ( + "testing" + + "erupe-ce/network/mhfpacket" +) + +// --- handleMsgMhfLoadGuildAdventure tests --- + +func TestLoadGuildAdventure_NoAdventures(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + adventures: []*GuildAdventure{}, + } + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfLoadGuildAdventure{AckHandle: 100} + + handleMsgMhfLoadGuildAdventure(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +func TestLoadGuildAdventure_WithAdventures(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + adventures: []*GuildAdventure{ + {ID: 1, Destination: 5, Charge: 0, Depart: 1000, Return: 2000, CollectedBy: ""}, + {ID: 2, Destination: 8, Charge: 100, Depart: 1000, Return: 2000, CollectedBy: "1"}, + }, + } + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfLoadGuildAdventure{AckHandle: 100} + + handleMsgMhfLoadGuildAdventure(session, pkt) + + select { + case p := <-session.sendPackets: + if len(p.data) < 10 { + t.Errorf("Response too short for 2 adventures: %d bytes", len(p.data)) + } + default: + t.Error("No response packet queued") + } +} + +func TestLoadGuildAdventure_DBError(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + listAdvErr: errNotFound, + } + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfLoadGuildAdventure{AckHandle: 100} + + handleMsgMhfLoadGuildAdventure(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +// --- handleMsgMhfRegistGuildAdventure tests --- + +func TestRegistGuildAdventure_Success(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{} + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfRegistGuildAdventure{ + AckHandle: 100, + Destination: 5, + } + + handleMsgMhfRegistGuildAdventure(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +func TestRegistGuildAdventure_Error(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{createAdvErr: errNotFound} + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfRegistGuildAdventure{ + AckHandle: 100, + Destination: 5, + } + + // Should not panic; error is logged + handleMsgMhfRegistGuildAdventure(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +// --- handleMsgMhfAcquireGuildAdventure tests --- + +func TestAcquireGuildAdventure_Success(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfAcquireGuildAdventure{ + AckHandle: 100, + ID: 42, + } + + handleMsgMhfAcquireGuildAdventure(session, pkt) + + if guildMock.collectAdvID != 42 { + t.Errorf("CollectAdventure ID = %d, want 42", guildMock.collectAdvID) + } + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +// --- handleMsgMhfChargeGuildAdventure tests --- + +func TestChargeGuildAdventure_Success(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfChargeGuildAdventure{ + AckHandle: 100, + ID: 42, + Amount: 500, + } + + handleMsgMhfChargeGuildAdventure(session, pkt) + + if guildMock.chargeAdvID != 42 { + t.Errorf("ChargeAdventure ID = %d, want 42", guildMock.chargeAdvID) + } + if guildMock.chargeAdvAmount != 500 { + t.Errorf("ChargeAdventure Amount = %d, want 500", guildMock.chargeAdvAmount) + } + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +// --- handleMsgMhfRegistGuildAdventureDiva tests --- + +func TestRegistGuildAdventureDiva_Success(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{} + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfRegistGuildAdventureDiva{ + AckHandle: 100, + Destination: 3, + Charge: 200, + } + + handleMsgMhfRegistGuildAdventureDiva(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} diff --git a/server/channelserver/handlers_guild_alliance_test.go b/server/channelserver/handlers_guild_alliance_test.go new file mode 100644 index 000000000..cac865ad4 --- /dev/null +++ b/server/channelserver/handlers_guild_alliance_test.go @@ -0,0 +1,320 @@ +package channelserver + +import ( + "testing" + "time" + + "erupe-ce/common/byteframe" + "erupe-ce/network/mhfpacket" +) + +// --- handleMsgMhfCreateJoint tests --- + +func TestCreateJoint_Success(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfCreateJoint{ + AckHandle: 100, + GuildID: 10, + Name: "TestAlliance", + } + + handleMsgMhfCreateJoint(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +func TestCreateJoint_Error(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{createAllianceErr: errNotFound} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfCreateJoint{ + AckHandle: 100, + GuildID: 10, + Name: "TestAlliance", + } + + // Should not panic; error is logged + handleMsgMhfCreateJoint(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +// --- handleMsgMhfOperateJoint tests --- + +func TestOperateJoint_Disband_AsOwner(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + alliance: &GuildAlliance{ + ID: 5, + ParentGuildID: 10, + }, + } + guildMock.guild = &Guild{ID: 10} + guildMock.guild.LeaderCharID = 1 // session charID + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfOperateJoint{ + AckHandle: 100, + AllianceID: 5, + GuildID: 10, + Action: mhfpacket.OPERATE_JOINT_DISBAND, + } + + handleMsgMhfOperateJoint(session, pkt) + + if guildMock.deletedAllianceID != 5 { + t.Errorf("DeleteAlliance called with %d, want 5", guildMock.deletedAllianceID) + } + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +func TestOperateJoint_Disband_NotOwner(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + alliance: &GuildAlliance{ + ID: 5, + ParentGuildID: 99, // different guild + }, + } + guildMock.guild = &Guild{ID: 10} + guildMock.guild.LeaderCharID = 1 + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfOperateJoint{ + AckHandle: 100, + AllianceID: 5, + GuildID: 10, + Action: mhfpacket.OPERATE_JOINT_DISBAND, + } + + handleMsgMhfOperateJoint(session, pkt) + + if guildMock.deletedAllianceID != 0 { + t.Error("Should not disband when not alliance owner") + } +} + +func TestOperateJoint_Leave_AsLeader(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + alliance: &GuildAlliance{ + ID: 5, + ParentGuildID: 99, + SubGuild1ID: 10, + }, + } + guildMock.guild = &Guild{ID: 10} + guildMock.guild.LeaderCharID = 1 + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfOperateJoint{ + AckHandle: 100, + AllianceID: 5, + GuildID: 10, + Action: mhfpacket.OPERATE_JOINT_LEAVE, + } + + handleMsgMhfOperateJoint(session, pkt) + + if guildMock.removedAllyArgs == nil { + t.Fatal("RemoveGuildFromAlliance should be called") + } + if guildMock.removedAllyArgs[1] != 10 { + t.Errorf("Removed guildID = %d, want 10", guildMock.removedAllyArgs[1]) + } +} + +func TestOperateJoint_Leave_NotLeader(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + alliance: &GuildAlliance{ID: 5, ParentGuildID: 99}, + } + guildMock.guild = &Guild{ID: 10} + guildMock.guild.LeaderCharID = 999 // not session char + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfOperateJoint{ + AckHandle: 100, + AllianceID: 5, + GuildID: 10, + Action: mhfpacket.OPERATE_JOINT_LEAVE, + } + + handleMsgMhfOperateJoint(session, pkt) + + if guildMock.removedAllyArgs != nil { + t.Error("Non-leader should not be able to leave alliance") + } +} + +func TestOperateJoint_Kick_AsAllianceOwner(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + alliance: &GuildAlliance{ + ID: 5, + ParentGuildID: 10, + ParentGuild: Guild{}, + SubGuild1ID: 20, + }, + } + guildMock.alliance.ParentGuild.LeaderCharID = 1 // session char owns alliance + guildMock.guild = &Guild{ID: 10} + guildMock.guild.LeaderCharID = 1 + + data1 := byteframe.NewByteFrame() + data1.WriteUint32(20) // guildID to kick + _, _ = data1.Seek(0, 0) + + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfOperateJoint{ + AckHandle: 100, + AllianceID: 5, + GuildID: 10, + Action: mhfpacket.OPERATE_JOINT_KICK, + Data1: data1, + } + + handleMsgMhfOperateJoint(session, pkt) + + if guildMock.removedAllyArgs == nil { + t.Fatal("RemoveGuildFromAlliance should be called for kick") + } + if guildMock.removedAllyArgs[1] != 20 { + t.Errorf("Kicked guildID = %d, want 20", guildMock.removedAllyArgs[1]) + } +} + +func TestOperateJoint_Kick_NotOwner(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + alliance: &GuildAlliance{ + ID: 5, + ParentGuildID: 99, + ParentGuild: Guild{}, + }, + } + guildMock.alliance.ParentGuild.LeaderCharID = 999 // not session char + guildMock.guild = &Guild{ID: 10} + guildMock.guild.LeaderCharID = 1 + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfOperateJoint{ + AckHandle: 100, + AllianceID: 5, + GuildID: 10, + Action: mhfpacket.OPERATE_JOINT_KICK, + } + + handleMsgMhfOperateJoint(session, pkt) + + if guildMock.removedAllyArgs != nil { + t.Error("Non-owner should not kick from alliance") + } +} + +// --- handleMsgMhfInfoJoint tests --- + +func TestInfoJoint_Success(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + alliance: &GuildAlliance{ + ID: 5, + Name: "TestAlliance", + CreatedAt: time.Now(), + TotalMembers: 15, + ParentGuildID: 10, + ParentGuild: Guild{Name: "ParentGuild", MemberCount: 5}, + }, + } + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfInfoJoint{AckHandle: 100, AllianceID: 5} + + handleMsgMhfInfoJoint(session, pkt) + + select { + case p := <-session.sendPackets: + if len(p.data) < 10 { + t.Errorf("Response too short: %d bytes", len(p.data)) + } + default: + t.Error("No response packet queued") + } +} + +func TestInfoJoint_WithSubGuilds(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + alliance: &GuildAlliance{ + ID: 5, + Name: "BigAlliance", + CreatedAt: time.Now(), + TotalMembers: 30, + ParentGuildID: 10, + ParentGuild: Guild{Name: "Parent", MemberCount: 10}, + SubGuild1ID: 20, + SubGuild1: Guild{Name: "Sub1", MemberCount: 10}, + SubGuild2ID: 30, + SubGuild2: Guild{Name: "Sub2", MemberCount: 10}, + }, + } + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfInfoJoint{AckHandle: 100, AllianceID: 5} + + handleMsgMhfInfoJoint(session, pkt) + + select { + case p := <-session.sendPackets: + if len(p.data) < 30 { + t.Errorf("Response too short for alliance with sub guilds: %d bytes", len(p.data)) + } + default: + t.Error("No response packet queued") + } +} + +func TestInfoJoint_NotFound(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{getAllianceErr: errNotFound} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfInfoJoint{AckHandle: 100, AllianceID: 999} + + handleMsgMhfInfoJoint(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} diff --git a/server/channelserver/handlers_guild_cooking_test.go b/server/channelserver/handlers_guild_cooking_test.go new file mode 100644 index 000000000..568a9ca94 --- /dev/null +++ b/server/channelserver/handlers_guild_cooking_test.go @@ -0,0 +1,306 @@ +package channelserver + +import ( + "testing" + "time" + + "erupe-ce/network/mhfpacket" +) + +// --- handleMsgMhfLoadGuildCooking tests --- + +func TestLoadGuildCooking_NoMeals(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + meals: []*GuildMeal{}, + } + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfLoadGuildCooking{AckHandle: 100} + + handleMsgMhfLoadGuildCooking(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +func TestLoadGuildCooking_WithActiveMeals(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + meals: []*GuildMeal{ + {ID: 1, MealID: 100, Level: 3, CreatedAt: TimeAdjusted()}, // active (within 60 min) + {ID: 2, MealID: 200, Level: 1, CreatedAt: TimeAdjusted().Add(-2 * time.Hour)}, // expired + }, + } + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfLoadGuildCooking{AckHandle: 100} + + handleMsgMhfLoadGuildCooking(session, pkt) + + select { + case p := <-session.sendPackets: + if len(p.data) < 4 { + t.Fatal("Response too short") + } + default: + t.Error("No response packet queued") + } +} + +func TestLoadGuildCooking_DBError(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + listMealsErr: errNotFound, + } + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfLoadGuildCooking{AckHandle: 100} + + handleMsgMhfLoadGuildCooking(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +// --- handleMsgMhfRegistGuildCooking tests --- + +func TestRegistGuildCooking_NewMeal(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + createdMealID: 42, + } + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfRegistGuildCooking{ + AckHandle: 100, + OverwriteID: 0, // New meal + MealID: 5, + Success: 1, + } + + handleMsgMhfRegistGuildCooking(session, pkt) + + select { + case p := <-session.sendPackets: + if len(p.data) < 8 { + t.Errorf("Response too short: %d bytes", len(p.data)) + } + default: + t.Error("No response packet queued") + } +} + +func TestRegistGuildCooking_UpdateMeal(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{} + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfRegistGuildCooking{ + AckHandle: 100, + OverwriteID: 42, // Update existing + MealID: 5, + Success: 2, + } + + handleMsgMhfRegistGuildCooking(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +func TestRegistGuildCooking_CreateError(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + createMealErr: errNotFound, + } + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfRegistGuildCooking{ + AckHandle: 100, + OverwriteID: 0, + MealID: 5, + Success: 1, + } + + handleMsgMhfRegistGuildCooking(session, pkt) + + // Should return fail ack + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +// --- handleMsgMhfGuildHuntdata tests --- + +func TestGuildHuntdata_Acquire(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{} + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfGuildHuntdata{ + AckHandle: 100, + Operation: 0, // Acquire + GuildID: 10, + } + + handleMsgMhfGuildHuntdata(session, pkt) + + if !guildMock.claimBoxCalled { + t.Error("ClaimHuntBox should be called") + } + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +func TestGuildHuntdata_Enumerate(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + guildKills: []*GuildKill{ + {ID: 1, Monster: 100}, + {ID: 2, Monster: 200}, + }, + } + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfGuildHuntdata{ + AckHandle: 100, + Operation: 1, // Enumerate + GuildID: 10, + } + + handleMsgMhfGuildHuntdata(session, pkt) + + select { + case p := <-session.sendPackets: + if len(p.data) < 1 { + t.Fatal("Response too short") + } + default: + t.Error("No response packet queued") + } +} + +func TestGuildHuntdata_Check_HasKills(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + countKills: 5, + } + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfGuildHuntdata{ + AckHandle: 100, + Operation: 2, // Check + GuildID: 10, + } + + handleMsgMhfGuildHuntdata(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +func TestGuildHuntdata_Check_NoKills(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + countKills: 0, + } + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfGuildHuntdata{ + AckHandle: 100, + Operation: 2, + GuildID: 10, + } + + handleMsgMhfGuildHuntdata(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +// --- handleMsgMhfAddGuildWeeklyBonusExceptionalUser tests --- + +func TestAddGuildWeeklyBonusExceptionalUser_Success(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{} + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfAddGuildWeeklyBonusExceptionalUser{ + AckHandle: 100, + NumUsers: 3, + } + + handleMsgMhfAddGuildWeeklyBonusExceptionalUser(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +func TestAddGuildWeeklyBonusExceptionalUser_NoGuild(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{} + guildMock.getErr = errNotFound + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfAddGuildWeeklyBonusExceptionalUser{ + AckHandle: 100, + NumUsers: 3, + } + + // Should not panic; just skips the bonus + handleMsgMhfAddGuildWeeklyBonusExceptionalUser(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} diff --git a/server/channelserver/handlers_guild_info_test.go b/server/channelserver/handlers_guild_info_test.go new file mode 100644 index 000000000..b0d8c1fe2 --- /dev/null +++ b/server/channelserver/handlers_guild_info_test.go @@ -0,0 +1,227 @@ +package channelserver + +import ( + "testing" + "time" + + "erupe-ce/common/byteframe" + "erupe-ce/network/mhfpacket" +) + +// guildInfoServer creates a mock server with ClanMemberLimits set, +// which handleMsgMhfInfoGuild requires. +func guildInfoServer() *Server { + s := createMockServer() + s.erupeConfig.GameplayOptions.ClanMemberLimits = [][]uint8{{0, 30}} + return s +} + +// --- handleMsgMhfInfoGuild tests --- + +func TestInfoGuild_ByGuildID(t *testing.T) { + server := guildInfoServer() + guildMock := &mockGuildRepoOps{ + membership: &GuildMember{GuildID: 10, CharID: 1, OrderIndex: 1, IsLeader: true}, + } + joined := time.Now() + guildMock.membership.JoinedAt = &joined + guildMock.guild = &Guild{ + ID: 10, + Name: "Test", + Comment: "Hello", + MemberCount: 5, + CreatedAt: time.Now(), + RoomExpiry: time.Now().Add(time.Hour), + } + guildMock.guild.LeaderCharID = 1 + guildMock.guild.LeaderName = "Leader" + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfInfoGuild{AckHandle: 100, GuildID: 10} + + handleMsgMhfInfoGuild(session, pkt) + + select { + case p := <-session.sendPackets: + if len(p.data) < 20 { + t.Errorf("Response too short: %d bytes", len(p.data)) + } + default: + t.Error("No response packet queued") + } + + if session.prevGuildID != 10 { + t.Errorf("prevGuildID = %d, want 10", session.prevGuildID) + } +} + +func TestInfoGuild_ByCharID(t *testing.T) { + server := guildInfoServer() + guildMock := &mockGuildRepoOps{ + membership: &GuildMember{GuildID: 10, CharID: 1, OrderIndex: 5}, + } + guildMock.guild = &Guild{ + ID: 10, + Name: "MyGuild", + CreatedAt: time.Now(), + RoomExpiry: time.Now(), + } + guildMock.guild.LeaderCharID = 99 + guildMock.guild.LeaderName = "Boss" + server.guildRepo = guildMock + session := createMockSession(1, server) + + // GuildID=0 means look up by charID + pkt := &mhfpacket.MsgMhfInfoGuild{AckHandle: 100, GuildID: 0} + + handleMsgMhfInfoGuild(session, pkt) + + select { + case p := <-session.sendPackets: + if len(p.data) < 20 { + t.Errorf("Response too short: %d bytes", len(p.data)) + } + default: + t.Error("No response packet queued") + } +} + +func TestInfoGuild_NotFound(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{} + guildMock.getErr = errNotFound + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfInfoGuild{AckHandle: 100, GuildID: 999} + + handleMsgMhfInfoGuild(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +func TestInfoGuild_MembershipError(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + getMemberErr: errNotFound, + } + guildMock.guild = &Guild{ + ID: 10, + Name: "Test", + CreatedAt: time.Now(), + RoomExpiry: time.Now(), + } + guildMock.guild.LeaderCharID = 1 + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfInfoGuild{AckHandle: 100, GuildID: 10} + + handleMsgMhfInfoGuild(session, pkt) + + // Should return early with count=0 response + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +func TestInfoGuild_WithAlliance(t *testing.T) { + server := guildInfoServer() + guildMock := &mockGuildRepoOps{ + membership: &GuildMember{GuildID: 10, CharID: 1, OrderIndex: 1, IsLeader: true}, + alliance: &GuildAlliance{ + ID: 5, + Name: "TestAlliance", + CreatedAt: time.Now(), + TotalMembers: 15, + ParentGuildID: 10, + ParentGuild: Guild{Name: "Test", MemberCount: 5}, + }, + } + guildMock.guild = &Guild{ + ID: 10, + Name: "Test", + CreatedAt: time.Now(), + RoomExpiry: time.Now(), + AllianceID: 5, + } + guildMock.guild.LeaderCharID = 1 + guildMock.guild.LeaderName = "Leader" + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfInfoGuild{AckHandle: 100, GuildID: 10} + + handleMsgMhfInfoGuild(session, pkt) + + select { + case p := <-session.sendPackets: + if len(p.data) < 50 { + t.Errorf("Alliance response too short: %d bytes", len(p.data)) + } + default: + t.Error("No response packet queued") + } +} + +// --- handleMsgMhfEnumerateGuild tests --- + +func TestEnumerateGuild_ByName(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{} + guildMock.guild = nil + server.guildRepo = guildMock + session := createMockSession(1, server) + + // Simulate search term in Data2 + data2 := byteframe.NewByteFrame() + data2.WriteBytes([]byte("Test\x00")) + _, _ = data2.Seek(0, 0) + + pkt := &mhfpacket.MsgMhfEnumerateGuild{ + AckHandle: 100, + Type: mhfpacket.ENUMERATE_GUILD_TYPE_GUILD_NAME, + Data2: data2, + } + + handleMsgMhfEnumerateGuild(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +func TestEnumerateGuild_NoResults(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{} + guildMock.getErr = errNotFound + server.guildRepo = guildMock + session := createMockSession(1, server) + + data2 := byteframe.NewByteFrame() + data2.WriteBytes([]byte("NonExistent\x00")) + _, _ = data2.Seek(0, 0) + + pkt := &mhfpacket.MsgMhfEnumerateGuild{ + AckHandle: 100, + Type: mhfpacket.ENUMERATE_GUILD_TYPE_GUILD_NAME, + Data2: data2, + } + + handleMsgMhfEnumerateGuild(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} diff --git a/server/channelserver/handlers_guild_tresure_test.go b/server/channelserver/handlers_guild_tresure_test.go new file mode 100644 index 000000000..bbf200c3b --- /dev/null +++ b/server/channelserver/handlers_guild_tresure_test.go @@ -0,0 +1,204 @@ +package channelserver + +import ( + "testing" + "time" + + "erupe-ce/network/mhfpacket" +) + +// --- handleMsgMhfEnumerateGuildTresure tests --- + +func TestEnumerateGuildTresure_NoGuild(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{} + guildMock.getErr = errNotFound + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfEnumerateGuildTresure{AckHandle: 100, MaxHunts: 30} + + handleMsgMhfEnumerateGuildTresure(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +func TestEnumerateGuildTresure_PendingHunt(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + pendingHunt: &TreasureHunt{ + HuntID: 1, + Destination: 5, + Level: 3, + Start: time.Now(), + HuntData: make([]byte, 10), + }, + } + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfEnumerateGuildTresure{AckHandle: 100, MaxHunts: 1} + + handleMsgMhfEnumerateGuildTresure(session, pkt) + + select { + case p := <-session.sendPackets: + if len(p.data) < 8 { + t.Errorf("Response too short: %d bytes", len(p.data)) + } + default: + t.Error("No response packet queued") + } +} + +func TestEnumerateGuildTresure_GuildHunts(t *testing.T) { + server := createMockServer() + // Set a large expiry so hunts are considered active + server.erupeConfig.GameplayOptions.TreasureHuntExpiry = 86400 + guildMock := &mockGuildRepoOps{ + guildHunts: []*TreasureHunt{ + {HuntID: 1, Destination: 5, Level: 2, Start: TimeAdjusted(), HuntData: make([]byte, 10)}, + {HuntID: 2, Destination: 8, Level: 3, Start: TimeAdjusted(), HuntData: make([]byte, 10)}, + }, + } + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfEnumerateGuildTresure{AckHandle: 100, MaxHunts: 30} + + handleMsgMhfEnumerateGuildTresure(session, pkt) + + select { + case p := <-session.sendPackets: + if len(p.data) < 8 { + t.Errorf("Response too short for 2 hunts: %d bytes", len(p.data)) + } + default: + t.Error("No response packet queued") + } +} + +func TestEnumerateGuildTresure_ListError(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{ + listHuntsErr: errNotFound, + } + guildMock.guild = &Guild{ID: 10} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfEnumerateGuildTresure{AckHandle: 100, MaxHunts: 30} + + handleMsgMhfEnumerateGuildTresure(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +// --- handleMsgMhfAcquireGuildTresure tests --- + +func TestAcquireGuildTresure_Success(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfAcquireGuildTresure{AckHandle: 100, HuntID: 42} + + handleMsgMhfAcquireGuildTresure(session, pkt) + + if guildMock.acquireHuntID != 42 { + t.Errorf("AcquireHunt ID = %d, want 42", guildMock.acquireHuntID) + } + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} + +// --- handleMsgMhfOperateGuildTresureReport tests --- + +func TestOperateGuildTresureReport_Register(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfOperateGuildTresureReport{ + AckHandle: 100, + HuntID: 42, + State: 0, // Register + } + + handleMsgMhfOperateGuildTresureReport(session, pkt) + + if guildMock.reportHuntID != 42 { + t.Errorf("RegisterHuntReport ID = %d, want 42", guildMock.reportHuntID) + } +} + +func TestOperateGuildTresureReport_Collect(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfOperateGuildTresureReport{ + AckHandle: 100, + HuntID: 42, + State: 1, // Collect + } + + handleMsgMhfOperateGuildTresureReport(session, pkt) + + if guildMock.collectHuntID != 42 { + t.Errorf("CollectHunt ID = %d, want 42", guildMock.collectHuntID) + } +} + +func TestOperateGuildTresureReport_Claim(t *testing.T) { + server := createMockServer() + guildMock := &mockGuildRepoOps{} + server.guildRepo = guildMock + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfOperateGuildTresureReport{ + AckHandle: 100, + HuntID: 42, + State: 2, // Claim + } + + handleMsgMhfOperateGuildTresureReport(session, pkt) + + if guildMock.claimHuntID != 42 { + t.Errorf("ClaimHuntReward ID = %d, want 42", guildMock.claimHuntID) + } +} + +// --- handleMsgMhfGetGuildTresureSouvenir tests --- + +func TestGetGuildTresureSouvenir_Empty(t *testing.T) { + server := createMockServer() + session := createMockSession(1, server) + + pkt := &mhfpacket.MsgMhfGetGuildTresureSouvenir{AckHandle: 100} + + handleMsgMhfGetGuildTresureSouvenir(session, pkt) + + select { + case <-session.sendPackets: + default: + t.Error("No response packet queued") + } +} diff --git a/server/channelserver/repo_mocks_test.go b/server/channelserver/repo_mocks_test.go index 311bef9b2..e1cf43600 100644 --- a/server/channelserver/repo_mocks_test.go +++ b/server/channelserver/repo_mocks_test.go @@ -363,6 +363,47 @@ type mockGuildRepoOps struct { createdPost []interface{} deletedPostID uint32 + // Alliance + alliance *GuildAlliance + getAllianceErr error + createAllianceErr error + deleteAllianceErr error + removeAllyErr error + deletedAllianceID uint32 + removedAllyArgs []uint32 + + // Cooking + meals []*GuildMeal + listMealsErr error + createdMealID uint32 + createMealErr error + updateMealErr error + + // Adventure + adventures []*GuildAdventure + listAdvErr error + createAdvErr error + collectAdvID uint32 + chargeAdvID uint32 + chargeAdvAmount uint32 + + // Treasure hunt + pendingHunt *TreasureHunt + guildHunts []*TreasureHunt + listHuntsErr error + acquireHuntID uint32 + reportHuntID uint32 + collectHuntID uint32 + claimHuntID uint32 + createHuntErr error + + // Hunt data + guildKills []*GuildKill + listKillsErr error + countKills int + countKillsErr error + claimBoxCalled bool + // Data membership *GuildMember application *GuildApplication @@ -460,6 +501,104 @@ func (m *mockGuildRepoOps) DeletePost(postID uint32) error { return m.deletePostErr } +func (m *mockGuildRepoOps) GetAllianceByID(_ uint32) (*GuildAlliance, error) { + return m.alliance, m.getAllianceErr +} + +func (m *mockGuildRepoOps) CreateAlliance(_ string, _ uint32) error { + return m.createAllianceErr +} + +func (m *mockGuildRepoOps) DeleteAlliance(id uint32) error { + m.deletedAllianceID = id + return m.deleteAllianceErr +} + +func (m *mockGuildRepoOps) RemoveGuildFromAlliance(allyID, guildID, sub1, sub2 uint32) error { + m.removedAllyArgs = []uint32{allyID, guildID, sub1, sub2} + return m.removeAllyErr +} + +func (m *mockGuildRepoOps) ListMeals(_ uint32) ([]*GuildMeal, error) { + return m.meals, m.listMealsErr +} + +func (m *mockGuildRepoOps) CreateMeal(_, _, _ uint32, _ time.Time) (uint32, error) { + return m.createdMealID, m.createMealErr +} + +func (m *mockGuildRepoOps) UpdateMeal(_, _, _ uint32, _ time.Time) error { + return m.updateMealErr +} + +func (m *mockGuildRepoOps) ListAdventures(_ uint32) ([]*GuildAdventure, error) { + return m.adventures, m.listAdvErr +} + +func (m *mockGuildRepoOps) CreateAdventure(_, _ uint32, _, _ int64) error { + return m.createAdvErr +} + +func (m *mockGuildRepoOps) CreateAdventureWithCharge(_, _, _ uint32, _, _ int64) error { + return m.createAdvErr +} + +func (m *mockGuildRepoOps) CollectAdventure(id uint32, _ uint32) error { + m.collectAdvID = id + return nil +} + +func (m *mockGuildRepoOps) ChargeAdventure(id uint32, amount uint32) error { + m.chargeAdvID = id + m.chargeAdvAmount = amount + return nil +} + +func (m *mockGuildRepoOps) GetPendingHunt(_ uint32) (*TreasureHunt, error) { + return m.pendingHunt, nil +} + +func (m *mockGuildRepoOps) ListGuildHunts(_, _ uint32) ([]*TreasureHunt, error) { + return m.guildHunts, m.listHuntsErr +} + +func (m *mockGuildRepoOps) CreateHunt(_, _, _, _ uint32, _ []byte, _ string) error { + return m.createHuntErr +} + +func (m *mockGuildRepoOps) AcquireHunt(id uint32) error { + m.acquireHuntID = id + return nil +} + +func (m *mockGuildRepoOps) RegisterHuntReport(id, _ uint32) error { + m.reportHuntID = id + return nil +} + +func (m *mockGuildRepoOps) CollectHunt(id uint32) error { + m.collectHuntID = id + return nil +} + +func (m *mockGuildRepoOps) ClaimHuntReward(id, _ uint32) error { + m.claimHuntID = id + return nil +} + +func (m *mockGuildRepoOps) ClaimHuntBox(_ uint32, _ time.Time) error { + m.claimBoxCalled = true + return nil +} + +func (m *mockGuildRepoOps) ListGuildKills(_, _ uint32) ([]*GuildKill, error) { + return m.guildKills, m.listKillsErr +} + +func (m *mockGuildRepoOps) CountGuildKills(_, _ uint32) (int, error) { + return m.countKills, m.countKillsErr +} + // --- mockUserRepoForItems --- type mockUserRepoForItems struct {