mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-21 23:22:34 +01:00
test(channelserver): add mock-based handler unit tests
Leverage the new repository interfaces to test handler logic without a database. Adds shared mock implementations (achievement, mail, character, goocoo, guild) and 32 new handler tests covering achievement, mail, cafe/boost, and goocoo handlers.
This commit is contained in:
@@ -452,3 +452,142 @@ func TestGetAchData_UpdatedAlwaysFalse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Mock-based handler tests ---
|
||||||
|
|
||||||
|
func TestHandleMsgMhfGetAchievement_Success(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockAchievementRepo{
|
||||||
|
scores: [33]int32{5, 0, 20, 0, 0, 0, 0, 1}, // A few non-zero scores
|
||||||
|
}
|
||||||
|
server.achievementRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfGetAchievement{
|
||||||
|
AckHandle: 100,
|
||||||
|
CharID: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfGetAchievement(session, pkt)
|
||||||
|
|
||||||
|
if !mock.ensureCalled {
|
||||||
|
t.Error("EnsureExists should have been called")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
// Response should contain: 16 bytes header + 3 bytes unk + 1 byte count + 33 entries
|
||||||
|
// Each entry: 1+1+2+4+1+1+2+4 = 16 bytes, so 33*16 = 528 + 20 header = 548
|
||||||
|
if len(p.data) < 100 {
|
||||||
|
t.Errorf("Response too short: %d bytes", len(p.data))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfGetAchievement_DBError(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockAchievementRepo{
|
||||||
|
getScoresErr: errNotFound,
|
||||||
|
}
|
||||||
|
server.achievementRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfGetAchievement{
|
||||||
|
AckHandle: 100,
|
||||||
|
CharID: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfGetAchievement(session, pkt)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
// On error, should return 20 zero bytes
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("Response should have fallback data")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfGetAchievement_AllZeroScores(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockAchievementRepo{} // All scores default to 0
|
||||||
|
server.achievementRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfGetAchievement{
|
||||||
|
AckHandle: 200,
|
||||||
|
CharID: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfGetAchievement(session, pkt)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) < 100 {
|
||||||
|
t.Errorf("Response too short: %d bytes", len(p.data))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfAddAchievement_Valid(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockAchievementRepo{}
|
||||||
|
server.achievementRepo = mock
|
||||||
|
session := createMockSession(42, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfAddAchievement{
|
||||||
|
AchievementID: 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfAddAchievement(session, pkt)
|
||||||
|
|
||||||
|
if !mock.ensureCalled {
|
||||||
|
t.Error("EnsureExists should have been called")
|
||||||
|
}
|
||||||
|
if mock.incrementedID != 5 {
|
||||||
|
t.Errorf("IncrementScore called with ID %d, want 5", mock.incrementedID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfAddAchievement_OutOfRange(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockAchievementRepo{}
|
||||||
|
server.achievementRepo = mock
|
||||||
|
session := createMockSession(42, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfAddAchievement{
|
||||||
|
AchievementID: 33, // > 32, should be rejected
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfAddAchievement(session, pkt)
|
||||||
|
|
||||||
|
if mock.ensureCalled {
|
||||||
|
t.Error("EnsureExists should NOT be called for out-of-range ID")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfAddAchievement_BoundaryID32(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockAchievementRepo{}
|
||||||
|
server.achievementRepo = mock
|
||||||
|
session := createMockSession(42, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfAddAchievement{
|
||||||
|
AchievementID: 32, // Exactly at boundary, should be accepted
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfAddAchievement(session, pkt)
|
||||||
|
|
||||||
|
if !mock.ensureCalled {
|
||||||
|
t.Error("EnsureExists should be called for ID 32")
|
||||||
|
}
|
||||||
|
if mock.incrementedID != 32 {
|
||||||
|
t.Errorf("IncrementScore called with ID %d, want 32", mock.incrementedID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package channelserver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"erupe-ce/network/mhfpacket"
|
"erupe-ce/network/mhfpacket"
|
||||||
)
|
)
|
||||||
@@ -108,3 +109,228 @@ func TestCafeBonusStruct(t *testing.T) {
|
|||||||
t.Error("Claimed should be false")
|
t.Error("Claimed should be false")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Mock-based handler tests ---
|
||||||
|
|
||||||
|
func TestHandleMsgMhfUpdateCafepoint(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
charMock := newMockCharacterRepo()
|
||||||
|
charMock.ints["netcafe_points"] = 150
|
||||||
|
server.charRepo = charMock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfUpdateCafepoint{AckHandle: 100}
|
||||||
|
|
||||||
|
handleMsgMhfUpdateCafepoint(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 TestHandleMsgMhfAcquireCafeItem(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
charMock := newMockCharacterRepo()
|
||||||
|
charMock.ints["netcafe_points"] = 500
|
||||||
|
server.charRepo = charMock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfAcquireCafeItem{
|
||||||
|
AckHandle: 100,
|
||||||
|
PointCost: 200,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfAcquireCafeItem(session, pkt)
|
||||||
|
|
||||||
|
if charMock.ints["netcafe_points"] != 300 {
|
||||||
|
t.Errorf("netcafe_points = %d, want 300 (500-200)", charMock.ints["netcafe_points"])
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) < 4 {
|
||||||
|
t.Fatal("Response too short")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfStartBoostTime_Disabled(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
server.erupeConfig.GameplayOptions.DisableBoostTime = true
|
||||||
|
charMock := newMockCharacterRepo()
|
||||||
|
server.charRepo = charMock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfStartBoostTime{AckHandle: 100}
|
||||||
|
|
||||||
|
handleMsgMhfStartBoostTime(session, pkt)
|
||||||
|
|
||||||
|
// When disabled, boost_time should NOT be saved
|
||||||
|
if _, ok := charMock.times["boost_time"]; ok {
|
||||||
|
t.Error("boost_time should not be saved when disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) < 4 {
|
||||||
|
t.Fatal("Response too short")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfStartBoostTime_Enabled(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
server.erupeConfig.GameplayOptions.DisableBoostTime = false
|
||||||
|
server.erupeConfig.GameplayOptions.BoostTimeDuration = 3600
|
||||||
|
charMock := newMockCharacterRepo()
|
||||||
|
server.charRepo = charMock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfStartBoostTime{AckHandle: 100}
|
||||||
|
|
||||||
|
handleMsgMhfStartBoostTime(session, pkt)
|
||||||
|
|
||||||
|
savedTime, ok := charMock.times["boost_time"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("boost_time should be saved")
|
||||||
|
}
|
||||||
|
if savedTime.Before(time.Now()) {
|
||||||
|
t.Error("boost_time should be in the future")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) < 4 {
|
||||||
|
t.Fatal("Response too short")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfGetBoostTimeLimit(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
charMock := newMockCharacterRepo()
|
||||||
|
future := time.Now().Add(1 * time.Hour)
|
||||||
|
charMock.times["boost_time"] = future
|
||||||
|
server.charRepo = charMock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfGetBoostTimeLimit{AckHandle: 100}
|
||||||
|
|
||||||
|
handleMsgMhfGetBoostTimeLimit(session, pkt)
|
||||||
|
|
||||||
|
// This handler sends two responses (doAckBufSucceed + doAckSimpleSucceed)
|
||||||
|
count := 0
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-session.sendPackets:
|
||||||
|
count++
|
||||||
|
default:
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done:
|
||||||
|
if count != 2 {
|
||||||
|
t.Errorf("Expected 2 response packets, got %d", count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfGetBoostTimeLimit_NoBoost(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
charMock := newMockCharacterRepo()
|
||||||
|
charMock.readErr = errNotFound
|
||||||
|
server.charRepo = charMock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfGetBoostTimeLimit{AckHandle: 100}
|
||||||
|
|
||||||
|
handleMsgMhfGetBoostTimeLimit(session, pkt)
|
||||||
|
|
||||||
|
// Should still send responses even on error
|
||||||
|
count := 0
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-session.sendPackets:
|
||||||
|
count++
|
||||||
|
default:
|
||||||
|
goto done2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done2:
|
||||||
|
if count < 1 {
|
||||||
|
t.Error("Should queue at least one response packet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfGetBoostRight_Active(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
charMock := newMockCharacterRepo()
|
||||||
|
charMock.times["boost_time"] = time.Now().Add(1 * time.Hour) // Future = active
|
||||||
|
server.charRepo = charMock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfGetBoostRight{AckHandle: 100}
|
||||||
|
|
||||||
|
handleMsgMhfGetBoostRight(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 TestHandleMsgMhfGetBoostRight_Expired(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
charMock := newMockCharacterRepo()
|
||||||
|
charMock.times["boost_time"] = time.Now().Add(-1 * time.Hour) // Past = expired
|
||||||
|
server.charRepo = charMock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfGetBoostRight{AckHandle: 100}
|
||||||
|
|
||||||
|
handleMsgMhfGetBoostRight(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 TestHandleMsgMhfGetBoostRight_NoRecord(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
charMock := newMockCharacterRepo()
|
||||||
|
charMock.readErr = errNotFound
|
||||||
|
server.charRepo = charMock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfGetBoostRight{AckHandle: 100}
|
||||||
|
|
||||||
|
handleMsgMhfGetBoostRight(session, pkt)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) < 4 {
|
||||||
|
t.Fatal("Response too short")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
150
server/channelserver/handlers_goocoo_test.go
Normal file
150
server/channelserver/handlers_goocoo_test.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
package channelserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"erupe-ce/network/mhfpacket"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandleMsgMhfEnumerateGuacot_Empty(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := newMockGoocooRepo()
|
||||||
|
server.goocooRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfEnumerateGuacot{AckHandle: 100}
|
||||||
|
|
||||||
|
handleMsgMhfEnumerateGuacot(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 TestHandleMsgMhfEnumerateGuacot_WithSlots(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := newMockGoocooRepo()
|
||||||
|
mock.slots[0] = []byte{0x01, 0x02, 0x03, 0x04} // slot 0 has data
|
||||||
|
mock.slots[2] = []byte{0x05, 0x06, 0x07, 0x08} // slot 2 has data
|
||||||
|
server.goocooRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfEnumerateGuacot{AckHandle: 100}
|
||||||
|
|
||||||
|
handleMsgMhfEnumerateGuacot(session, pkt)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
// Header (4 bytes) + 2 goocoo entries
|
||||||
|
if len(p.data) < 8 {
|
||||||
|
t.Errorf("Response too short: %d bytes", len(p.data))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfUpdateGuacot_ClearSlot(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := newMockGoocooRepo()
|
||||||
|
mock.slots[1] = []byte{0x01, 0x02} // pre-existing data
|
||||||
|
server.goocooRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfUpdateGuacot{
|
||||||
|
AckHandle: 100,
|
||||||
|
Goocoos: []mhfpacket.Goocoo{
|
||||||
|
{
|
||||||
|
Index: 1,
|
||||||
|
Data1: []int16{0, 0, 0}, // First byte 0 = clear
|
||||||
|
Data2: []uint32{0},
|
||||||
|
Name: []byte("test"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfUpdateGuacot(session, pkt)
|
||||||
|
|
||||||
|
if len(mock.clearCalled) != 1 || mock.clearCalled[0] != 1 {
|
||||||
|
t.Errorf("Expected ClearSlot(1), got %v", mock.clearCalled)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-session.sendPackets:
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfUpdateGuacot_SaveSlot(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := newMockGoocooRepo()
|
||||||
|
server.goocooRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfUpdateGuacot{
|
||||||
|
AckHandle: 100,
|
||||||
|
Goocoos: []mhfpacket.Goocoo{
|
||||||
|
{
|
||||||
|
Index: 2,
|
||||||
|
Data1: []int16{1, 2, 3}, // First byte non-zero = save
|
||||||
|
Data2: []uint32{100, 200},
|
||||||
|
Name: []byte("MyGoocoo"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfUpdateGuacot(session, pkt)
|
||||||
|
|
||||||
|
if _, ok := mock.savedSlots[2]; !ok {
|
||||||
|
t.Error("Expected SaveSlot to be called for slot 2")
|
||||||
|
}
|
||||||
|
if len(mock.clearCalled) != 0 {
|
||||||
|
t.Error("ClearSlot should not be called for a save operation")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-session.sendPackets:
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfUpdateGuacot_SkipInvalidIndex(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := newMockGoocooRepo()
|
||||||
|
server.goocooRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfUpdateGuacot{
|
||||||
|
AckHandle: 100,
|
||||||
|
Goocoos: []mhfpacket.Goocoo{
|
||||||
|
{
|
||||||
|
Index: 5, // > 4, should be skipped
|
||||||
|
Data1: []int16{1},
|
||||||
|
Data2: []uint32{0},
|
||||||
|
Name: []byte("Bad"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfUpdateGuacot(session, pkt)
|
||||||
|
|
||||||
|
if len(mock.savedSlots) != 0 {
|
||||||
|
t.Error("SaveSlot should not be called for index > 4")
|
||||||
|
}
|
||||||
|
if len(mock.clearCalled) != 0 {
|
||||||
|
t.Error("ClearSlot should not be called for index > 4")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-session.sendPackets:
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ package channelserver
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"erupe-ce/network/mhfpacket"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMailStruct(t *testing.T) {
|
func TestMailStruct(t *testing.T) {
|
||||||
@@ -81,3 +83,411 @@ func TestMailStruct_DefaultValues(t *testing.T) {
|
|||||||
t.Error("Default Read should be false")
|
t.Error("Default Read should be false")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Mock-based handler tests ---
|
||||||
|
|
||||||
|
func TestHandleMsgMhfListMail_Empty(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockMailRepo{mails: []Mail{}}
|
||||||
|
server.mailRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfListMail{AckHandle: 100}
|
||||||
|
|
||||||
|
handleMsgMhfListMail(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 TestHandleMsgMhfListMail_WithMails(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockMailRepo{
|
||||||
|
mails: []Mail{
|
||||||
|
{ID: 10, SenderID: 100, Subject: "Hello", SenderName: "Sender1", CreatedAt: time.Now()},
|
||||||
|
{ID: 20, SenderID: 200, Subject: "World", SenderName: "Sender2", CreatedAt: time.Now(), Locked: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
server.mailRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfListMail{AckHandle: 100}
|
||||||
|
|
||||||
|
handleMsgMhfListMail(session, pkt)
|
||||||
|
|
||||||
|
// Verify mailList was populated
|
||||||
|
if session.mailList == nil {
|
||||||
|
t.Fatal("mailList should be initialized")
|
||||||
|
}
|
||||||
|
if session.mailList[0] != 10 {
|
||||||
|
t.Errorf("mailList[0] = %d, want 10", session.mailList[0])
|
||||||
|
}
|
||||||
|
if session.mailList[1] != 20 {
|
||||||
|
t.Errorf("mailList[1] = %d, want 20", session.mailList[1])
|
||||||
|
}
|
||||||
|
if session.mailAccIndex != 2 {
|
||||||
|
t.Errorf("mailAccIndex = %d, want 2", session.mailAccIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 TestHandleMsgMhfListMail_DBError(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockMailRepo{listErr: errNotFound}
|
||||||
|
server.mailRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfListMail{AckHandle: 100}
|
||||||
|
|
||||||
|
handleMsgMhfListMail(session, pkt)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
// Should return a fallback response with single zero byte
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("Should have fallback response data")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfReadMail_Success(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockMailRepo{
|
||||||
|
mailByID: map[int]*Mail{
|
||||||
|
42: {ID: 42, Body: "Test body content"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
server.mailRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
session.mailList = make([]int, 256)
|
||||||
|
session.mailList[0] = 42
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfReadMail{
|
||||||
|
AckHandle: 100,
|
||||||
|
AccIndex: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfReadMail(session, pkt)
|
||||||
|
|
||||||
|
if mock.markReadCalled != 42 {
|
||||||
|
t.Errorf("MarkRead called with %d, want 42", mock.markReadCalled)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("Response should have body data")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfReadMail_OutOfBounds(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockMailRepo{}
|
||||||
|
server.mailRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
// mailList is nil, so any AccIndex is out of bounds
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfReadMail{
|
||||||
|
AckHandle: 100,
|
||||||
|
AccIndex: 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfReadMail(session, pkt)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
// Should get fallback single-byte response
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("Should have fallback response")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfReadMail_ZeroMailID(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockMailRepo{}
|
||||||
|
server.mailRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
session.mailList = make([]int, 256)
|
||||||
|
// mailList[0] is 0 (default)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfReadMail{
|
||||||
|
AckHandle: 100,
|
||||||
|
AccIndex: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfReadMail(session, pkt)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("Should have fallback response")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfOprtMail_Delete(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockMailRepo{
|
||||||
|
mailByID: map[int]*Mail{
|
||||||
|
42: {ID: 42},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
server.mailRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
session.mailList = make([]int, 256)
|
||||||
|
session.mailList[0] = 42
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfOprtMail{
|
||||||
|
AckHandle: 100,
|
||||||
|
AccIndex: 0,
|
||||||
|
Operation: mhfpacket.OperateMailDelete,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfOprtMail(session, pkt)
|
||||||
|
|
||||||
|
if mock.markDeletedID != 42 {
|
||||||
|
t.Errorf("MarkDeleted called with %d, want 42", mock.markDeletedID)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-session.sendPackets:
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfOprtMail_Lock(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockMailRepo{
|
||||||
|
mailByID: map[int]*Mail{
|
||||||
|
42: {ID: 42},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
server.mailRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
session.mailList = make([]int, 256)
|
||||||
|
session.mailList[0] = 42
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfOprtMail{
|
||||||
|
AckHandle: 100,
|
||||||
|
AccIndex: 0,
|
||||||
|
Operation: mhfpacket.OperateMailLock,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfOprtMail(session, pkt)
|
||||||
|
|
||||||
|
if mock.lockID != 42 || !mock.lockValue {
|
||||||
|
t.Errorf("SetLocked called with ID=%d locked=%v, want ID=42 locked=true", mock.lockID, mock.lockValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfOprtMail_Unlock(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockMailRepo{
|
||||||
|
mailByID: map[int]*Mail{
|
||||||
|
42: {ID: 42},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
server.mailRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
session.mailList = make([]int, 256)
|
||||||
|
session.mailList[0] = 42
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfOprtMail{
|
||||||
|
AckHandle: 100,
|
||||||
|
AccIndex: 0,
|
||||||
|
Operation: mhfpacket.OperateMailUnlock,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfOprtMail(session, pkt)
|
||||||
|
|
||||||
|
if mock.lockID != 42 || mock.lockValue {
|
||||||
|
t.Errorf("SetLocked called with ID=%d locked=%v, want ID=42 locked=false", mock.lockID, mock.lockValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfOprtMail_AcquireItem(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockMailRepo{
|
||||||
|
mailByID: map[int]*Mail{
|
||||||
|
42: {ID: 42, AttachedItemID: 100, AttachedItemAmount: 5},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
server.mailRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
session.mailList = make([]int, 256)
|
||||||
|
session.mailList[0] = 42
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfOprtMail{
|
||||||
|
AckHandle: 100,
|
||||||
|
AccIndex: 0,
|
||||||
|
Operation: mhfpacket.OperateMailAcquireItem,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfOprtMail(session, pkt)
|
||||||
|
|
||||||
|
if mock.itemReceivedID != 42 {
|
||||||
|
t.Errorf("MarkItemReceived called with %d, want 42", mock.itemReceivedID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfOprtMail_OutOfBounds(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockMailRepo{}
|
||||||
|
server.mailRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
// No mailList set
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfOprtMail{
|
||||||
|
AckHandle: 100,
|
||||||
|
AccIndex: 5,
|
||||||
|
Operation: mhfpacket.OperateMailDelete,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfOprtMail(session, pkt)
|
||||||
|
|
||||||
|
// Should not have called any repo methods
|
||||||
|
if mock.markDeletedID != 0 {
|
||||||
|
t.Error("Should not have called MarkDeleted for out-of-bounds access")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-session.sendPackets:
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfSendMail_Direct(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mock := &mockMailRepo{}
|
||||||
|
server.mailRepo = mock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfSendMail{
|
||||||
|
AckHandle: 100,
|
||||||
|
RecipientID: 42,
|
||||||
|
Subject: "Hello",
|
||||||
|
Body: "World",
|
||||||
|
ItemID: 500,
|
||||||
|
Quantity: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfSendMail(session, pkt)
|
||||||
|
|
||||||
|
if len(mock.sentMails) != 1 {
|
||||||
|
t.Fatalf("Expected 1 sent mail, got %d", len(mock.sentMails))
|
||||||
|
}
|
||||||
|
sent := mock.sentMails[0]
|
||||||
|
if sent.senderID != 1 {
|
||||||
|
t.Errorf("SenderID = %d, want 1", sent.senderID)
|
||||||
|
}
|
||||||
|
if sent.recipientID != 42 {
|
||||||
|
t.Errorf("RecipientID = %d, want 42", sent.recipientID)
|
||||||
|
}
|
||||||
|
if sent.subject != "Hello" {
|
||||||
|
t.Errorf("Subject = %s, want Hello", sent.subject)
|
||||||
|
}
|
||||||
|
if sent.itemID != 500 {
|
||||||
|
t.Errorf("ItemID = %d, want 500", sent.itemID)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-session.sendPackets:
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfSendMail_Guild(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mailMock := &mockMailRepo{}
|
||||||
|
guildMock := &mockGuildRepoForMail{
|
||||||
|
guild: &Guild{ID: 10},
|
||||||
|
members: []*GuildMember{
|
||||||
|
{CharID: 100},
|
||||||
|
{CharID: 200},
|
||||||
|
{CharID: 300},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
server.mailRepo = mailMock
|
||||||
|
server.guildRepo = guildMock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfSendMail{
|
||||||
|
AckHandle: 100,
|
||||||
|
RecipientID: 0, // 0 = guild mail
|
||||||
|
Subject: "Guild News",
|
||||||
|
Body: "Important update",
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfSendMail(session, pkt)
|
||||||
|
|
||||||
|
if len(mailMock.sentMails) != 3 {
|
||||||
|
t.Fatalf("Expected 3 sent mails (one per guild member), got %d", len(mailMock.sentMails))
|
||||||
|
}
|
||||||
|
for i, sent := range mailMock.sentMails {
|
||||||
|
if sent.senderID != 1 {
|
||||||
|
t.Errorf("Mail %d: SenderID = %d, want 1", i, sent.senderID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recipients := map[uint32]bool{}
|
||||||
|
for _, sent := range mailMock.sentMails {
|
||||||
|
recipients[sent.recipientID] = true
|
||||||
|
}
|
||||||
|
if !recipients[100] || !recipients[200] || !recipients[300] {
|
||||||
|
t.Errorf("Expected recipients 100, 200, 300, got %v", recipients)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfSendMail_GuildNotFound(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
mailMock := &mockMailRepo{}
|
||||||
|
guildMock := &mockGuildRepoForMail{getErr: errNotFound}
|
||||||
|
server.mailRepo = mailMock
|
||||||
|
server.guildRepo = guildMock
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
pkt := &mhfpacket.MsgMhfSendMail{
|
||||||
|
AckHandle: 100,
|
||||||
|
RecipientID: 0, // Guild mail
|
||||||
|
Subject: "Guild News",
|
||||||
|
Body: "Update",
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMsgMhfSendMail(session, pkt)
|
||||||
|
|
||||||
|
if len(mailMock.sentMails) != 0 {
|
||||||
|
t.Errorf("No mails should be sent when guild not found, got %d", len(mailMock.sentMails))
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-session.sendPackets:
|
||||||
|
default:
|
||||||
|
t.Error("No response packet queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
324
server/channelserver/repo_mocks_test.go
Normal file
324
server/channelserver/repo_mocks_test.go
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
package channelserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// errNotFound is a sentinel for mock repos that simulate "not found".
|
||||||
|
var errNotFound = errors.New("not found")
|
||||||
|
|
||||||
|
// --- mockAchievementRepo ---
|
||||||
|
|
||||||
|
type mockAchievementRepo struct {
|
||||||
|
scores [33]int32
|
||||||
|
ensureCalled bool
|
||||||
|
ensureErr error
|
||||||
|
getScoresErr error
|
||||||
|
incrementErr error
|
||||||
|
incrementedID uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockAchievementRepo) EnsureExists(_ uint32) error {
|
||||||
|
m.ensureCalled = true
|
||||||
|
return m.ensureErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockAchievementRepo) GetAllScores(_ uint32) ([33]int32, error) {
|
||||||
|
return m.scores, m.getScoresErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockAchievementRepo) IncrementScore(_ uint32, id uint8) error {
|
||||||
|
m.incrementedID = id
|
||||||
|
return m.incrementErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- mockMailRepo ---
|
||||||
|
|
||||||
|
type mockMailRepo struct {
|
||||||
|
mails []Mail
|
||||||
|
mailByID map[int]*Mail
|
||||||
|
listErr error
|
||||||
|
getByIDErr error
|
||||||
|
markReadCalled int
|
||||||
|
markDeletedID int
|
||||||
|
lockID int
|
||||||
|
lockValue bool
|
||||||
|
itemReceivedID int
|
||||||
|
sentMails []sentMailRecord
|
||||||
|
sendErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
type sentMailRecord struct {
|
||||||
|
senderID, recipientID uint32
|
||||||
|
subject, body string
|
||||||
|
itemID, itemAmount uint16
|
||||||
|
isGuildInvite, isSystemMessage bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockMailRepo) GetListForCharacter(_ uint32) ([]Mail, error) {
|
||||||
|
return m.mails, m.listErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockMailRepo) GetByID(id int) (*Mail, error) {
|
||||||
|
if m.getByIDErr != nil {
|
||||||
|
return nil, m.getByIDErr
|
||||||
|
}
|
||||||
|
if mail, ok := m.mailByID[id]; ok {
|
||||||
|
return mail, nil
|
||||||
|
}
|
||||||
|
return nil, errNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockMailRepo) MarkRead(id int) error {
|
||||||
|
m.markReadCalled = id
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockMailRepo) MarkDeleted(id int) error {
|
||||||
|
m.markDeletedID = id
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockMailRepo) SetLocked(id int, locked bool) error {
|
||||||
|
m.lockID = id
|
||||||
|
m.lockValue = locked
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockMailRepo) MarkItemReceived(id int) error {
|
||||||
|
m.itemReceivedID = id
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockMailRepo) SendMail(senderID, recipientID uint32, subject, body string, itemID, itemAmount uint16, isGuildInvite, isSystemMessage bool) error {
|
||||||
|
m.sentMails = append(m.sentMails, sentMailRecord{
|
||||||
|
senderID: senderID, recipientID: recipientID,
|
||||||
|
subject: subject, body: body,
|
||||||
|
itemID: itemID, itemAmount: itemAmount,
|
||||||
|
isGuildInvite: isGuildInvite, isSystemMessage: isSystemMessage,
|
||||||
|
})
|
||||||
|
return m.sendErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockMailRepo) SendMailTx(_ *sql.Tx, senderID, recipientID uint32, subject, body string, itemID, itemAmount uint16, isGuildInvite, isSystemMessage bool) error {
|
||||||
|
return m.SendMail(senderID, recipientID, subject, body, itemID, itemAmount, isGuildInvite, isSystemMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- mockCharacterRepo ---
|
||||||
|
|
||||||
|
type mockCharacterRepo struct {
|
||||||
|
ints map[string]int
|
||||||
|
times map[string]time.Time
|
||||||
|
columns map[string][]byte
|
||||||
|
strings map[string]string
|
||||||
|
bools map[string]bool
|
||||||
|
|
||||||
|
adjustErr error
|
||||||
|
readErr error
|
||||||
|
saveErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockCharacterRepo() *mockCharacterRepo {
|
||||||
|
return &mockCharacterRepo{
|
||||||
|
ints: make(map[string]int),
|
||||||
|
times: make(map[string]time.Time),
|
||||||
|
columns: make(map[string][]byte),
|
||||||
|
strings: make(map[string]string),
|
||||||
|
bools: make(map[string]bool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockCharacterRepo) ReadInt(_ uint32, column string) (int, error) {
|
||||||
|
if m.readErr != nil {
|
||||||
|
return 0, m.readErr
|
||||||
|
}
|
||||||
|
return m.ints[column], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockCharacterRepo) AdjustInt(_ uint32, column string, delta int) (int, error) {
|
||||||
|
if m.adjustErr != nil {
|
||||||
|
return 0, m.adjustErr
|
||||||
|
}
|
||||||
|
m.ints[column] += delta
|
||||||
|
return m.ints[column], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockCharacterRepo) SaveInt(_ uint32, column string, value int) error {
|
||||||
|
m.ints[column] = value
|
||||||
|
return m.saveErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockCharacterRepo) ReadTime(_ uint32, column string, defaultVal time.Time) (time.Time, error) {
|
||||||
|
if m.readErr != nil {
|
||||||
|
return defaultVal, m.readErr
|
||||||
|
}
|
||||||
|
if t, ok := m.times[column]; ok {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
return defaultVal, errNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockCharacterRepo) SaveTime(_ uint32, column string, value time.Time) error {
|
||||||
|
m.times[column] = value
|
||||||
|
return m.saveErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockCharacterRepo) LoadColumn(_ uint32, column string) ([]byte, error) { return m.columns[column], nil }
|
||||||
|
func (m *mockCharacterRepo) SaveColumn(_ uint32, column string, data []byte) error { m.columns[column] = data; return m.saveErr }
|
||||||
|
func (m *mockCharacterRepo) GetName(_ uint32) (string, error) { return "TestChar", nil }
|
||||||
|
func (m *mockCharacterRepo) GetUserID(_ uint32) (uint32, error) { return 1, nil }
|
||||||
|
func (m *mockCharacterRepo) UpdateLastLogin(_ uint32, _ int64) error { return nil }
|
||||||
|
func (m *mockCharacterRepo) UpdateTimePlayed(_ uint32, _ int) error { return nil }
|
||||||
|
func (m *mockCharacterRepo) GetCharIDsByUserID(_ uint32) ([]uint32, error) { return nil, nil }
|
||||||
|
func (m *mockCharacterRepo) SaveBool(_ uint32, col string, v bool) error { m.bools[col] = v; return nil }
|
||||||
|
func (m *mockCharacterRepo) SaveString(_ uint32, col string, v string) error { m.strings[col] = v; return nil }
|
||||||
|
func (m *mockCharacterRepo) ReadBool(_ uint32, col string) (bool, error) { return m.bools[col], nil }
|
||||||
|
func (m *mockCharacterRepo) ReadString(_ uint32, col string) (string, error) { return m.strings[col], nil }
|
||||||
|
func (m *mockCharacterRepo) LoadColumnWithDefault(_ uint32, col string, def []byte) ([]byte, error) {
|
||||||
|
if d, ok := m.columns[col]; ok {
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
return def, nil
|
||||||
|
}
|
||||||
|
func (m *mockCharacterRepo) SetDeleted(_ uint32) error { return nil }
|
||||||
|
func (m *mockCharacterRepo) UpdateDailyCafe(_ uint32, _ time.Time, _, _ uint32) error { return nil }
|
||||||
|
func (m *mockCharacterRepo) ResetDailyQuests(_ uint32) error { return nil }
|
||||||
|
func (m *mockCharacterRepo) ReadEtcPoints(_ uint32) (uint32, uint32, uint32, error) { return 0, 0, 0, nil }
|
||||||
|
func (m *mockCharacterRepo) ResetCafeTime(_ uint32, _ time.Time) error { return nil }
|
||||||
|
func (m *mockCharacterRepo) UpdateGuildPostChecked(_ uint32) error { return nil }
|
||||||
|
func (m *mockCharacterRepo) ReadGuildPostChecked(_ uint32) (time.Time, error) { return time.Time{}, nil }
|
||||||
|
func (m *mockCharacterRepo) SaveMercenary(_ uint32, _ []byte, _ uint32) error { return nil }
|
||||||
|
func (m *mockCharacterRepo) UpdateGCPAndPact(_ uint32, _ uint32, _ uint32) error { return nil }
|
||||||
|
func (m *mockCharacterRepo) FindByRastaID(_ int) (uint32, string, error) { return 0, "", nil }
|
||||||
|
func (m *mockCharacterRepo) SaveCharacterData(_ uint32, _ []byte, _, _ uint16, _ bool, _ uint8, _ uint16) error { return nil }
|
||||||
|
func (m *mockCharacterRepo) SaveHouseData(_ uint32, _ []byte, _, _, _, _, _ []byte) error { return nil }
|
||||||
|
|
||||||
|
// --- mockGoocooRepo ---
|
||||||
|
|
||||||
|
type mockGoocooRepo struct {
|
||||||
|
slots map[uint32][]byte
|
||||||
|
ensureCalled bool
|
||||||
|
clearCalled []uint32
|
||||||
|
savedSlots map[uint32][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockGoocooRepo() *mockGoocooRepo {
|
||||||
|
return &mockGoocooRepo{
|
||||||
|
slots: make(map[uint32][]byte),
|
||||||
|
savedSlots: make(map[uint32][]byte),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockGoocooRepo) EnsureExists(_ uint32) error {
|
||||||
|
m.ensureCalled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockGoocooRepo) GetSlot(_ uint32, slot uint32) ([]byte, error) {
|
||||||
|
if data, ok := m.slots[slot]; ok {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockGoocooRepo) ClearSlot(_ uint32, slot uint32) error {
|
||||||
|
m.clearCalled = append(m.clearCalled, slot)
|
||||||
|
delete(m.slots, slot)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockGoocooRepo) SaveSlot(_ uint32, slot uint32, data []byte) error {
|
||||||
|
m.savedSlots[slot] = data
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- mockGuildRepo (minimal, for SendMail guild path) ---
|
||||||
|
|
||||||
|
type mockGuildRepoForMail struct {
|
||||||
|
guild *Guild
|
||||||
|
members []*GuildMember
|
||||||
|
getErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockGuildRepoForMail) GetByCharID(_ uint32) (*Guild, error) {
|
||||||
|
if m.getErr != nil {
|
||||||
|
return nil, m.getErr
|
||||||
|
}
|
||||||
|
return m.guild, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockGuildRepoForMail) GetMembers(_ uint32, _ bool) ([]*GuildMember, error) {
|
||||||
|
return m.members, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stub out all other GuildRepo methods.
|
||||||
|
func (m *mockGuildRepoForMail) GetByID(_ uint32) (*Guild, error) { return nil, errNotFound }
|
||||||
|
func (m *mockGuildRepoForMail) ListAll() ([]*Guild, error) { return nil, nil }
|
||||||
|
func (m *mockGuildRepoForMail) Create(_ uint32, _ string) (int32, error) { return 0, nil }
|
||||||
|
func (m *mockGuildRepoForMail) Save(_ *Guild) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) Disband(_ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) RemoveCharacter(_ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) AcceptApplication(_, _ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) CreateApplication(_, _, _ uint32, _ GuildApplicationType, _ *sql.Tx) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (m *mockGuildRepoForMail) CancelInvitation(_, _ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) RejectApplication(_, _ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) ArrangeCharacters(_ []uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) GetApplication(_, _ uint32, _ GuildApplicationType) (*GuildApplication, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (m *mockGuildRepoForMail) HasApplication(_, _ uint32) (bool, error) { return false, nil }
|
||||||
|
func (m *mockGuildRepoForMail) GetItemBox(_ uint32) ([]byte, error) { return nil, nil }
|
||||||
|
func (m *mockGuildRepoForMail) SaveItemBox(_ uint32, _ []byte) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) GetCharacterMembership(_ uint32) (*GuildMember, error) { return nil, nil }
|
||||||
|
func (m *mockGuildRepoForMail) SaveMember(_ *GuildMember) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) SetRecruiting(_ uint32, _ bool) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) SetPugiOutfits(_ uint32, _ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) SetRecruiter(_ uint32, _ bool) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) AddMemberDailyRP(_ uint32, _ uint16) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) ExchangeEventRP(_ uint32, _ uint16) (uint32, error) { return 0, nil }
|
||||||
|
func (m *mockGuildRepoForMail) AddRankRP(_ uint32, _ uint16) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) AddEventRP(_ uint32, _ uint16) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) GetRoomRP(_ uint32) (uint16, error) { return 0, nil }
|
||||||
|
func (m *mockGuildRepoForMail) SetRoomRP(_ uint32, _ uint16) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) AddRoomRP(_ uint32, _ uint16) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) SetRoomExpiry(_ uint32, _ time.Time) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) ListPosts(_ uint32, _ int) ([]*MessageBoardPost, error) { return nil, nil }
|
||||||
|
func (m *mockGuildRepoForMail) CreatePost(_, _, _ uint32, _ int, _, _ string, _ int) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) DeletePost(_ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) UpdatePost(_ uint32, _, _ string) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) UpdatePostStamp(_, _ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) GetPostLikedBy(_ uint32) (string, error) { return "", nil }
|
||||||
|
func (m *mockGuildRepoForMail) SetPostLikedBy(_ uint32, _ string) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) CountNewPosts(_ uint32, _ time.Time) (int, error) { return 0, nil }
|
||||||
|
func (m *mockGuildRepoForMail) GetAllianceByID(_ uint32) (*GuildAlliance, error) { return nil, nil }
|
||||||
|
func (m *mockGuildRepoForMail) ListAlliances() ([]*GuildAlliance, error) { return nil, nil }
|
||||||
|
func (m *mockGuildRepoForMail) CreateAlliance(_ string, _ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) DeleteAlliance(_ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) RemoveGuildFromAlliance(_, _, _, _ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) ListAdventures(_ uint32) ([]*GuildAdventure, error) { return nil, nil }
|
||||||
|
func (m *mockGuildRepoForMail) CreateAdventure(_, _ uint32, _, _ int64) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) CreateAdventureWithCharge(_, _, _ uint32, _, _ int64) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) CollectAdventure(_ uint32, _ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) ChargeAdventure(_ uint32, _ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) GetPendingHunt(_ uint32) (*TreasureHunt, error) { return nil, nil }
|
||||||
|
func (m *mockGuildRepoForMail) ListGuildHunts(_, _ uint32) ([]*TreasureHunt, error) { return nil, nil }
|
||||||
|
func (m *mockGuildRepoForMail) CreateHunt(_, _, _, _ uint32, _ []byte, _ string) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) AcquireHunt(_ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) RegisterHuntReport(_, _ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) CollectHunt(_ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) ClaimHuntReward(_, _ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) ListMeals(_ uint32) ([]*GuildMeal, error) { return nil, nil }
|
||||||
|
func (m *mockGuildRepoForMail) CreateMeal(_, _, _ uint32, _ time.Time) (uint32, error) { return 0, nil }
|
||||||
|
func (m *mockGuildRepoForMail) UpdateMeal(_, _, _ uint32, _ time.Time) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) ClaimHuntBox(_ uint32, _ time.Time) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) ListGuildKills(_, _ uint32) ([]*GuildKill, error) { return nil, nil }
|
||||||
|
func (m *mockGuildRepoForMail) CountGuildKills(_, _ uint32) (int, error) { return 0, nil }
|
||||||
|
func (m *mockGuildRepoForMail) ClearTreasureHunt(_ uint32) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) InsertKillLog(_ uint32, _ int, _ uint8, _ time.Time) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) ListInvitedCharacters(_ uint32) ([]*ScoutedCharacter, error) { return nil, nil }
|
||||||
|
func (m *mockGuildRepoForMail) RolloverDailyRP(_ uint32, _ time.Time) error { return nil }
|
||||||
|
func (m *mockGuildRepoForMail) AddWeeklyBonusUsers(_ uint32, _ uint8) error { return nil }
|
||||||
Reference in New Issue
Block a user