refactor(channelserver): extract GuildRepository for guild table access

Per anti-patterns.md item #9, guild-related SQL was scattered across
~15 handler files with no repository abstraction. Following the same
pattern established by CharacterRepository, this centralizes all
guilds, guild_characters, and guild_applications table access into a
single GuildRepository (~30 methods).

guild_model.go and handlers_guild_member.go are trimmed to types and
pure business logic only. All handler files (guild_*, festa, mail,
house, mercenary, rengoku) now call s.server.guildRepo methods
instead of direct DB queries or methods on domain objects.
This commit is contained in:
Houmgaor
2026-02-20 22:06:55 +01:00
parent d642cbef24
commit 96d07f1c04
21 changed files with 1244 additions and 791 deletions

View File

@@ -0,0 +1,531 @@
package channelserver
import (
"testing"
"time"
"github.com/jmoiron/sqlx"
)
func setupGuildRepo(t *testing.T) (*GuildRepository, *sqlx.DB, uint32, uint32) {
t.Helper()
db := SetupTestDB(t)
userID := CreateTestUser(t, db, "guild_test_user")
charID := CreateTestCharacter(t, db, userID, "GuildLeader")
repo := NewGuildRepository(db)
guildID := CreateTestGuild(t, db, charID, "TestGuild")
t.Cleanup(func() { TeardownTestDB(t, db) })
return repo, db, guildID, charID
}
func TestGetByID(t *testing.T) {
repo, _, guildID, charID := setupGuildRepo(t)
guild, err := repo.GetByID(guildID)
if err != nil {
t.Fatalf("GetByID failed: %v", err)
}
if guild == nil {
t.Fatal("Expected guild, got nil")
}
if guild.ID != guildID {
t.Errorf("Expected guild ID %d, got %d", guildID, guild.ID)
}
if guild.Name != "TestGuild" {
t.Errorf("Expected name 'TestGuild', got %q", guild.Name)
}
if guild.LeaderCharID != charID {
t.Errorf("Expected leader %d, got %d", charID, guild.LeaderCharID)
}
}
func TestGetByIDNotFound(t *testing.T) {
repo, _, _, _ := setupGuildRepo(t)
guild, err := repo.GetByID(999999)
if err != nil {
t.Fatalf("GetByID failed: %v", err)
}
if guild != nil {
t.Errorf("Expected nil for non-existent guild, got: %+v", guild)
}
}
func TestGetByCharID(t *testing.T) {
repo, _, guildID, charID := setupGuildRepo(t)
guild, err := repo.GetByCharID(charID)
if err != nil {
t.Fatalf("GetByCharID failed: %v", err)
}
if guild == nil {
t.Fatal("Expected guild, got nil")
}
if guild.ID != guildID {
t.Errorf("Expected guild ID %d, got %d", guildID, guild.ID)
}
}
func TestGetByCharIDNotFound(t *testing.T) {
repo, _, _, _ := setupGuildRepo(t)
guild, err := repo.GetByCharID(999999)
if err != nil {
t.Fatalf("GetByCharID failed: %v", err)
}
if guild != nil {
t.Errorf("Expected nil for non-member, got: %+v", guild)
}
}
func TestCreate(t *testing.T) {
db := SetupTestDB(t)
defer TeardownTestDB(t, db)
repo := NewGuildRepository(db)
userID := CreateTestUser(t, db, "create_guild_user")
charID := CreateTestCharacter(t, db, userID, "CreateLeader")
guildID, err := repo.Create(charID, "NewGuild")
if err != nil {
t.Fatalf("Create failed: %v", err)
}
if guildID <= 0 {
t.Errorf("Expected positive guild ID, got %d", guildID)
}
// Verify guild exists
guild, err := repo.GetByID(uint32(guildID))
if err != nil {
t.Fatalf("GetByID after Create failed: %v", err)
}
if guild == nil {
t.Fatal("Created guild not found")
}
if guild.Name != "NewGuild" {
t.Errorf("Expected name 'NewGuild', got %q", guild.Name)
}
// Verify leader is a member
member, err := repo.GetCharacterMembership(charID)
if err != nil {
t.Fatalf("GetCharacterMembership failed: %v", err)
}
if member == nil {
t.Fatal("Leader not found as guild member")
}
}
func TestSaveGuild(t *testing.T) {
repo, _, guildID, _ := setupGuildRepo(t)
guild, err := repo.GetByID(guildID)
if err != nil {
t.Fatalf("GetByID failed: %v", err)
}
guild.Comment = "Updated comment"
guild.MainMotto = 5
guild.SubMotto = 3
if err := repo.Save(guild); err != nil {
t.Fatalf("Save failed: %v", err)
}
updated, err := repo.GetByID(guildID)
if err != nil {
t.Fatalf("GetByID after Save failed: %v", err)
}
if updated.Comment != "Updated comment" {
t.Errorf("Expected comment 'Updated comment', got %q", updated.Comment)
}
if updated.MainMotto != 5 || updated.SubMotto != 3 {
t.Errorf("Expected mottos 5/3, got %d/%d", updated.MainMotto, updated.SubMotto)
}
}
func TestDisband(t *testing.T) {
repo, _, guildID, charID := setupGuildRepo(t)
if err := repo.Disband(guildID); err != nil {
t.Fatalf("Disband failed: %v", err)
}
guild, err := repo.GetByID(guildID)
if err != nil {
t.Fatalf("GetByID after Disband failed: %v", err)
}
if guild != nil {
t.Errorf("Expected nil after disband, got: %+v", guild)
}
member, err := repo.GetCharacterMembership(charID)
if err != nil {
t.Fatalf("GetCharacterMembership after Disband failed: %v", err)
}
if member != nil {
t.Errorf("Expected nil membership after disband, got: %+v", member)
}
}
func TestGetMembers(t *testing.T) {
repo, db, guildID, leaderID := setupGuildRepo(t)
// Add a second member
user2 := CreateTestUser(t, db, "member_user")
member2 := CreateTestCharacter(t, db, user2, "Member2")
if _, err := db.Exec("INSERT INTO guild_characters (guild_id, character_id, order_index) VALUES ($1, $2, 2)", guildID, member2); err != nil {
t.Fatalf("Failed to add member: %v", err)
}
members, err := repo.GetMembers(guildID, false)
if err != nil {
t.Fatalf("GetMembers failed: %v", err)
}
if len(members) != 2 {
t.Fatalf("Expected 2 members, got %d", len(members))
}
ids := map[uint32]bool{leaderID: false, member2: false}
for _, m := range members {
ids[m.CharID] = true
}
if !ids[leaderID] || !ids[member2] {
t.Errorf("Expected members %d and %d, got: %v", leaderID, member2, members)
}
}
func TestGetCharacterMembership(t *testing.T) {
repo, _, guildID, charID := setupGuildRepo(t)
member, err := repo.GetCharacterMembership(charID)
if err != nil {
t.Fatalf("GetCharacterMembership failed: %v", err)
}
if member == nil {
t.Fatal("Expected membership, got nil")
}
if member.GuildID != guildID {
t.Errorf("Expected guild ID %d, got %d", guildID, member.GuildID)
}
if !member.IsLeader {
t.Error("Expected leader flag to be true")
}
}
func TestSaveMember(t *testing.T) {
repo, _, _, charID := setupGuildRepo(t)
member, err := repo.GetCharacterMembership(charID)
if err != nil {
t.Fatalf("GetCharacterMembership failed: %v", err)
}
member.AvoidLeadership = true
member.OrderIndex = 5
if err := repo.SaveMember(member); err != nil {
t.Fatalf("SaveMember failed: %v", err)
}
updated, err := repo.GetCharacterMembership(charID)
if err != nil {
t.Fatalf("GetCharacterMembership after Save failed: %v", err)
}
if !updated.AvoidLeadership {
t.Error("Expected avoid_leadership=true")
}
if updated.OrderIndex != 5 {
t.Errorf("Expected order_index=5, got %d", updated.OrderIndex)
}
}
func TestRemoveCharacter(t *testing.T) {
repo, db, guildID, _ := setupGuildRepo(t)
// Add and remove a member
user2 := CreateTestUser(t, db, "remove_user")
char2 := CreateTestCharacter(t, db, user2, "RemoveMe")
if _, err := db.Exec("INSERT INTO guild_characters (guild_id, character_id, order_index) VALUES ($1, $2, 2)", guildID, char2); err != nil {
t.Fatalf("Failed to add member: %v", err)
}
if err := repo.RemoveCharacter(char2); err != nil {
t.Fatalf("RemoveCharacter failed: %v", err)
}
member, err := repo.GetCharacterMembership(char2)
if err != nil {
t.Fatalf("GetCharacterMembership after remove failed: %v", err)
}
if member != nil {
t.Errorf("Expected nil membership after remove, got: %+v", member)
}
}
func TestApplicationWorkflow(t *testing.T) {
repo, db, guildID, _ := setupGuildRepo(t)
user2 := CreateTestUser(t, db, "applicant_user")
applicantID := CreateTestCharacter(t, db, user2, "Applicant")
// Create application
err := repo.CreateApplication(guildID, applicantID, applicantID, GuildApplicationTypeApplied, nil)
if err != nil {
t.Fatalf("CreateApplication failed: %v", err)
}
// Check HasApplication
has, err := repo.HasApplication(guildID, applicantID)
if err != nil {
t.Fatalf("HasApplication failed: %v", err)
}
if !has {
t.Error("Expected application to exist")
}
// Get application
app, err := repo.GetApplication(guildID, applicantID, GuildApplicationTypeApplied)
if err != nil {
t.Fatalf("GetApplication failed: %v", err)
}
if app == nil {
t.Fatal("Expected application, got nil")
}
// Accept
err = repo.AcceptApplication(guildID, applicantID)
if err != nil {
t.Fatalf("AcceptApplication failed: %v", err)
}
// Verify membership
member, err := repo.GetCharacterMembership(applicantID)
if err != nil {
t.Fatalf("GetCharacterMembership after accept failed: %v", err)
}
if member == nil {
t.Fatal("Expected membership after accept")
}
// Verify application removed
has, err = repo.HasApplication(guildID, applicantID)
if err != nil {
t.Fatalf("HasApplication after accept failed: %v", err)
}
if has {
t.Error("Expected no application after accept")
}
}
func TestRejectApplication(t *testing.T) {
repo, db, guildID, _ := setupGuildRepo(t)
user2 := CreateTestUser(t, db, "reject_user")
applicantID := CreateTestCharacter(t, db, user2, "Rejected")
err := repo.CreateApplication(guildID, applicantID, applicantID, GuildApplicationTypeApplied, nil)
if err != nil {
t.Fatalf("CreateApplication failed: %v", err)
}
err = repo.RejectApplication(guildID, applicantID)
if err != nil {
t.Fatalf("RejectApplication failed: %v", err)
}
has, err := repo.HasApplication(guildID, applicantID)
if err != nil {
t.Fatalf("HasApplication after reject failed: %v", err)
}
if has {
t.Error("Expected no application after reject")
}
}
func TestSetRecruiting(t *testing.T) {
repo, db, guildID, _ := setupGuildRepo(t)
if err := repo.SetRecruiting(guildID, false); err != nil {
t.Fatalf("SetRecruiting failed: %v", err)
}
var recruiting bool
if err := db.QueryRow("SELECT recruiting FROM guilds WHERE id=$1", guildID).Scan(&recruiting); err != nil {
t.Fatalf("Verification query failed: %v", err)
}
if recruiting {
t.Error("Expected recruiting=false")
}
}
func TestRPOperations(t *testing.T) {
repo, db, guildID, _ := setupGuildRepo(t)
// AddRankRP
if err := repo.AddRankRP(guildID, 100); err != nil {
t.Fatalf("AddRankRP failed: %v", err)
}
var rankRP uint16
if err := db.QueryRow("SELECT rank_rp FROM guilds WHERE id=$1", guildID).Scan(&rankRP); err != nil {
t.Fatalf("Verification failed: %v", err)
}
if rankRP != 100 {
t.Errorf("Expected rank_rp=100, got %d", rankRP)
}
// AddEventRP
if err := repo.AddEventRP(guildID, 50); err != nil {
t.Fatalf("AddEventRP failed: %v", err)
}
// ExchangeEventRP
balance, err := repo.ExchangeEventRP(guildID, 20)
if err != nil {
t.Fatalf("ExchangeEventRP failed: %v", err)
}
if balance != 30 {
t.Errorf("Expected event_rp balance=30, got %d", balance)
}
// Room RP operations
if err := repo.AddRoomRP(guildID, 10); err != nil {
t.Fatalf("AddRoomRP failed: %v", err)
}
roomRP, err := repo.GetRoomRP(guildID)
if err != nil {
t.Fatalf("GetRoomRP failed: %v", err)
}
if roomRP != 10 {
t.Errorf("Expected room_rp=10, got %d", roomRP)
}
if err := repo.SetRoomRP(guildID, 0); err != nil {
t.Fatalf("SetRoomRP failed: %v", err)
}
roomRP, err = repo.GetRoomRP(guildID)
if err != nil {
t.Fatalf("GetRoomRP after reset failed: %v", err)
}
if roomRP != 0 {
t.Errorf("Expected room_rp=0, got %d", roomRP)
}
// SetRoomExpiry
expiry := time.Date(2025, 7, 1, 0, 0, 0, 0, time.UTC)
if err := repo.SetRoomExpiry(guildID, expiry); err != nil {
t.Fatalf("SetRoomExpiry failed: %v", err)
}
var gotExpiry time.Time
if err := db.QueryRow("SELECT room_expiry FROM guilds WHERE id=$1", guildID).Scan(&gotExpiry); err != nil {
t.Fatalf("Verification failed: %v", err)
}
if !gotExpiry.Equal(expiry) {
t.Errorf("Expected expiry %v, got %v", expiry, gotExpiry)
}
}
func TestItemBox(t *testing.T) {
repo, _, guildID, _ := setupGuildRepo(t)
// Initially nil
data, err := repo.GetItemBox(guildID)
if err != nil {
t.Fatalf("GetItemBox failed: %v", err)
}
if data != nil {
t.Errorf("Expected nil item box initially, got %x", data)
}
// Save and retrieve
blob := []byte{0x01, 0x02, 0x03}
if err := repo.SaveItemBox(guildID, blob); err != nil {
t.Fatalf("SaveItemBox failed: %v", err)
}
data, err = repo.GetItemBox(guildID)
if err != nil {
t.Fatalf("GetItemBox after save failed: %v", err)
}
if len(data) != 3 || data[0] != 0x01 || data[2] != 0x03 {
t.Errorf("Expected %x, got %x", blob, data)
}
}
func TestListAll(t *testing.T) {
repo, db, _, _ := setupGuildRepo(t)
// Create a second guild
user2 := CreateTestUser(t, db, "list_user")
char2 := CreateTestCharacter(t, db, user2, "ListLeader")
CreateTestGuild(t, db, char2, "SecondGuild")
guilds, err := repo.ListAll()
if err != nil {
t.Fatalf("ListAll failed: %v", err)
}
if len(guilds) < 2 {
t.Errorf("Expected at least 2 guilds, got %d", len(guilds))
}
}
func TestArrangeCharacters(t *testing.T) {
repo, db, guildID, leaderID := setupGuildRepo(t)
// Add two more members
user2 := CreateTestUser(t, db, "arrange_user2")
char2 := CreateTestCharacter(t, db, user2, "Char2")
user3 := CreateTestUser(t, db, "arrange_user3")
char3 := CreateTestCharacter(t, db, user3, "Char3")
if _, err := db.Exec("INSERT INTO guild_characters (guild_id, character_id, order_index) VALUES ($1, $2, 2)", guildID, char2); err != nil {
t.Fatalf("Failed to add member: %v", err)
}
if _, err := db.Exec("INSERT INTO guild_characters (guild_id, character_id, order_index) VALUES ($1, $2, 3)", guildID, char3); err != nil {
t.Fatalf("Failed to add member: %v", err)
}
// Rearrange (excludes leader, sets order_index starting at 2)
if err := repo.ArrangeCharacters([]uint32{char3, char2}); err != nil {
t.Fatalf("ArrangeCharacters failed: %v", err)
}
// Verify order changed
var order2, order3 uint16
_ = db.QueryRow("SELECT order_index FROM guild_characters WHERE character_id=$1", char2).Scan(&order2)
_ = db.QueryRow("SELECT order_index FROM guild_characters WHERE character_id=$1", char3).Scan(&order3)
if order3 != 2 || order2 != 3 {
t.Errorf("Expected char3=2, char2=3 but got char3=%d, char2=%d", order3, order2)
}
_ = leaderID
}
func TestSetRecruiter(t *testing.T) {
repo, db, _, charID := setupGuildRepo(t)
if err := repo.SetRecruiter(charID, true); err != nil {
t.Fatalf("SetRecruiter failed: %v", err)
}
var recruiter bool
if err := db.QueryRow("SELECT recruiter FROM guild_characters WHERE character_id=$1", charID).Scan(&recruiter); err != nil {
t.Fatalf("Verification failed: %v", err)
}
if !recruiter {
t.Error("Expected recruiter=true")
}
}
func TestAddMemberDailyRP(t *testing.T) {
repo, db, _, charID := setupGuildRepo(t)
if err := repo.AddMemberDailyRP(charID, 25); err != nil {
t.Fatalf("AddMemberDailyRP failed: %v", err)
}
var rp uint16
if err := db.QueryRow("SELECT rp_today FROM guild_characters WHERE character_id=$1", charID).Scan(&rp); err != nil {
t.Fatalf("Verification failed: %v", err)
}
if rp != 25 {
t.Errorf("Expected rp_today=25, got %d", rp)
}
}