Files
Erupe/server/channelserver/handlers_mail_test.go
Houmgaor 41a103af9d refactor(test): consolidate two GuildRepo mocks into one
mockGuildRepoForMail and mockGuildRepoOps each implemented different
subsets of the 68-method GuildRepo interface. Adding any new method
required updating both mocks. Merged into a single mockGuildRepo with
configurable struct fields for error injection and no-op defaults for
the rest.
2026-02-24 14:13:20 +01:00

497 lines
12 KiB
Go

package channelserver
import (
"testing"
"time"
"erupe-ce/network/mhfpacket"
)
func TestMailStruct(t *testing.T) {
mail := Mail{
ID: 123,
SenderID: 1000,
RecipientID: 2000,
Subject: "Test Subject",
Body: "Test Body Content",
Read: false,
Deleted: false,
Locked: true,
AttachedItemReceived: false,
AttachedItemID: 500,
AttachedItemAmount: 10,
CreatedAt: time.Now(),
IsGuildInvite: false,
IsSystemMessage: true,
SenderName: "TestSender",
}
if mail.ID != 123 {
t.Errorf("ID = %d, want 123", mail.ID)
}
if mail.SenderID != 1000 {
t.Errorf("SenderID = %d, want 1000", mail.SenderID)
}
if mail.RecipientID != 2000 {
t.Errorf("RecipientID = %d, want 2000", mail.RecipientID)
}
if mail.Subject != "Test Subject" {
t.Errorf("Subject = %s, want 'Test Subject'", mail.Subject)
}
if mail.Body != "Test Body Content" {
t.Errorf("Body = %s, want 'Test Body Content'", mail.Body)
}
if mail.Read {
t.Error("Read should be false")
}
if mail.Deleted {
t.Error("Deleted should be false")
}
if !mail.Locked {
t.Error("Locked should be true")
}
if mail.AttachedItemReceived {
t.Error("AttachedItemReceived should be false")
}
if mail.AttachedItemID != 500 {
t.Errorf("AttachedItemID = %d, want 500", mail.AttachedItemID)
}
if mail.AttachedItemAmount != 10 {
t.Errorf("AttachedItemAmount = %d, want 10", mail.AttachedItemAmount)
}
if mail.IsGuildInvite {
t.Error("IsGuildInvite should be false")
}
if !mail.IsSystemMessage {
t.Error("IsSystemMessage should be true")
}
if mail.SenderName != "TestSender" {
t.Errorf("SenderName = %s, want 'TestSender'", mail.SenderName)
}
}
func TestMailStruct_DefaultValues(t *testing.T) {
mail := Mail{}
if mail.ID != 0 {
t.Errorf("Default ID should be 0, got %d", mail.ID)
}
if mail.Subject != "" {
t.Errorf("Default Subject should be empty, got %s", mail.Subject)
}
if mail.Read {
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
ensureMailService(server)
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 := &mockGuildRepo{
guild: &Guild{ID: 10},
members: []*GuildMember{
{CharID: 100},
{CharID: 200},
{CharID: 300},
},
}
server.mailRepo = mailMock
server.guildRepo = guildMock
ensureMailService(server)
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 := &mockGuildRepo{getErr: errNotFound}
server.mailRepo = mailMock
server.guildRepo = guildMock
ensureMailService(server)
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")
}
}