test(channelserver): add comprehensive GuildRepository tests

Add 34 tests covering all previously-untested GuildRepository methods:
invitations/scouts, guild posts, alliances, adventures, treasure
hunts, meals, kill tracking, and edge cases like disband with
alliance cleanup.

Fix test schema setup to apply the 9.2 update schema after init.sql,
which bridges v9.1.0 to v9.2.0 and resolves 9 pre-existing test
failures caused by missing columns (rp_today, created_at, etc.).

Make patch schemas 14 and 19 idempotent so they no longer fail when
applied against a schema that already has the target state (e.g.
festival_color type already renamed, legacy column names).
This commit is contained in:
Houmgaor
2026-02-20 22:53:16 +01:00
parent 93f28c721a
commit 7436ac0870
4 changed files with 980 additions and 18 deletions

View File

@@ -1,6 +1,7 @@
package channelserver
import (
"fmt"
"testing"
"time"
@@ -529,3 +530,918 @@ func TestAddMemberDailyRP(t *testing.T) {
t.Errorf("Expected rp_today=25, got %d", rp)
}
}
// --- Invitation / Scout tests ---
func TestCancelInvitation(t *testing.T) {
repo, db, guildID, leaderID := setupGuildRepo(t)
user2 := CreateTestUser(t, db, "invite_user")
char2 := CreateTestCharacter(t, db, user2, "Invited")
if err := repo.CreateApplication(guildID, char2, leaderID, GuildApplicationTypeInvited, nil); err != nil {
t.Fatalf("CreateApplication (invited) failed: %v", err)
}
if err := repo.CancelInvitation(guildID, char2); err != nil {
t.Fatalf("CancelInvitation failed: %v", err)
}
has, err := repo.HasApplication(guildID, char2)
if err != nil {
t.Fatalf("HasApplication failed: %v", err)
}
if has {
t.Error("Expected no application after cancellation")
}
}
func TestListInvitedCharacters(t *testing.T) {
repo, db, guildID, leaderID := setupGuildRepo(t)
user2 := CreateTestUser(t, db, "scout_user")
char2 := CreateTestCharacter(t, db, user2, "Scouted")
if err := repo.CreateApplication(guildID, char2, leaderID, GuildApplicationTypeInvited, nil); err != nil {
t.Fatalf("CreateApplication failed: %v", err)
}
chars, err := repo.ListInvitedCharacters(guildID)
if err != nil {
t.Fatalf("ListInvitedCharacters failed: %v", err)
}
if len(chars) != 1 {
t.Fatalf("Expected 1 invited character, got %d", len(chars))
}
if chars[0].CharID != char2 {
t.Errorf("Expected char ID %d, got %d", char2, chars[0].CharID)
}
if chars[0].Name != "Scouted" {
t.Errorf("Expected name 'Scouted', got %q", chars[0].Name)
}
if chars[0].ActorID != leaderID {
t.Errorf("Expected actor ID %d, got %d", leaderID, chars[0].ActorID)
}
}
func TestListInvitedCharactersEmpty(t *testing.T) {
repo, _, guildID, _ := setupGuildRepo(t)
chars, err := repo.ListInvitedCharacters(guildID)
if err != nil {
t.Fatalf("ListInvitedCharacters failed: %v", err)
}
if len(chars) != 0 {
t.Errorf("Expected 0 invited characters, got %d", len(chars))
}
}
func TestGetByCharIDWithApplication(t *testing.T) {
repo, db, guildID, _ := setupGuildRepo(t)
user2 := CreateTestUser(t, db, "app_char_user")
char2 := CreateTestCharacter(t, db, user2, "Applicant2")
if err := repo.CreateApplication(guildID, char2, char2, GuildApplicationTypeApplied, nil); err != nil {
t.Fatalf("CreateApplication failed: %v", err)
}
guild, err := repo.GetByCharID(char2)
if err != nil {
t.Fatalf("GetByCharID failed: %v", err)
}
if guild == nil {
t.Fatal("Expected guild via application, got nil")
}
if guild.ID != guildID {
t.Errorf("Expected guild ID %d, got %d", guildID, guild.ID)
}
}
func TestGetMembersApplicants(t *testing.T) {
repo, db, guildID, _ := setupGuildRepo(t)
user2 := CreateTestUser(t, db, "applicant_member_user")
char2 := CreateTestCharacter(t, db, user2, "AppMember")
if err := repo.CreateApplication(guildID, char2, char2, GuildApplicationTypeApplied, nil); err != nil {
t.Fatalf("CreateApplication failed: %v", err)
}
applicants, err := repo.GetMembers(guildID, true)
if err != nil {
t.Fatalf("GetMembers(applicants=true) failed: %v", err)
}
if len(applicants) != 1 {
t.Fatalf("Expected 1 applicant, got %d", len(applicants))
}
if applicants[0].CharID != char2 {
t.Errorf("Expected applicant char ID %d, got %d", char2, applicants[0].CharID)
}
if !applicants[0].IsApplicant {
t.Error("Expected IsApplicant=true")
}
}
// --- SetPugiOutfits ---
func TestSetPugiOutfits(t *testing.T) {
repo, db, guildID, _ := setupGuildRepo(t)
if err := repo.SetPugiOutfits(guildID, 0xFF); err != nil {
t.Fatalf("SetPugiOutfits failed: %v", err)
}
var outfits uint32
if err := db.QueryRow("SELECT pugi_outfits FROM guilds WHERE id=$1", guildID).Scan(&outfits); err != nil {
t.Fatalf("Verification failed: %v", err)
}
if outfits != 0xFF {
t.Errorf("Expected pugi_outfits=0xFF, got %d", outfits)
}
}
// --- Guild Posts ---
func TestCreateAndListPosts(t *testing.T) {
repo, db, guildID, charID := setupGuildRepo(t)
_ = db
if err := repo.CreatePost(guildID, charID, 1, 0, "Hello", "World", 10); err != nil {
t.Fatalf("CreatePost failed: %v", err)
}
if err := repo.CreatePost(guildID, charID, 2, 0, "Second", "Post", 10); err != nil {
t.Fatalf("CreatePost 2 failed: %v", err)
}
posts, err := repo.ListPosts(guildID, 0)
if err != nil {
t.Fatalf("ListPosts failed: %v", err)
}
if len(posts) != 2 {
t.Fatalf("Expected 2 posts, got %d", len(posts))
}
// Newest first
if posts[0].Title != "Second" {
t.Errorf("Expected newest first, got %q", posts[0].Title)
}
}
func TestCreatePostMaxPosts(t *testing.T) {
repo, _, guildID, charID := setupGuildRepo(t)
// Create 3 posts with maxPosts=2 — the oldest should be soft-deleted
for i := 0; i < 3; i++ {
if err := repo.CreatePost(guildID, charID, 0, 0, fmt.Sprintf("Post%d", i), "body", 2); err != nil {
t.Fatalf("CreatePost %d failed: %v", i, err)
}
}
posts, err := repo.ListPosts(guildID, 0)
if err != nil {
t.Fatalf("ListPosts failed: %v", err)
}
if len(posts) != 2 {
t.Errorf("Expected 2 posts after max enforcement, got %d", len(posts))
}
}
func TestDeletePost(t *testing.T) {
repo, _, guildID, charID := setupGuildRepo(t)
if err := repo.CreatePost(guildID, charID, 0, 0, "ToDelete", "body", 10); err != nil {
t.Fatalf("CreatePost failed: %v", err)
}
posts, _ := repo.ListPosts(guildID, 0)
if len(posts) == 0 {
t.Fatal("Expected post to exist")
}
if err := repo.DeletePost(posts[0].ID); err != nil {
t.Fatalf("DeletePost failed: %v", err)
}
posts, _ = repo.ListPosts(guildID, 0)
if len(posts) != 0 {
t.Errorf("Expected 0 posts after delete, got %d", len(posts))
}
}
func TestUpdatePost(t *testing.T) {
repo, _, guildID, charID := setupGuildRepo(t)
if err := repo.CreatePost(guildID, charID, 0, 0, "Original", "body", 10); err != nil {
t.Fatalf("CreatePost failed: %v", err)
}
posts, _ := repo.ListPosts(guildID, 0)
if err := repo.UpdatePost(posts[0].ID, "Updated", "new body"); err != nil {
t.Fatalf("UpdatePost failed: %v", err)
}
posts, _ = repo.ListPosts(guildID, 0)
if posts[0].Title != "Updated" || posts[0].Body != "new body" {
t.Errorf("Expected 'Updated'/'new body', got %q/%q", posts[0].Title, posts[0].Body)
}
}
func TestUpdatePostStamp(t *testing.T) {
repo, _, guildID, charID := setupGuildRepo(t)
if err := repo.CreatePost(guildID, charID, 0, 0, "Stamp", "body", 10); err != nil {
t.Fatalf("CreatePost failed: %v", err)
}
posts, _ := repo.ListPosts(guildID, 0)
if err := repo.UpdatePostStamp(posts[0].ID, 42); err != nil {
t.Fatalf("UpdatePostStamp failed: %v", err)
}
posts, _ = repo.ListPosts(guildID, 0)
if posts[0].StampID != 42 {
t.Errorf("Expected stamp_id=42, got %d", posts[0].StampID)
}
}
func TestPostLikedBy(t *testing.T) {
repo, _, guildID, charID := setupGuildRepo(t)
if err := repo.CreatePost(guildID, charID, 0, 0, "Like", "body", 10); err != nil {
t.Fatalf("CreatePost failed: %v", err)
}
posts, _ := repo.ListPosts(guildID, 0)
if err := repo.SetPostLikedBy(posts[0].ID, "100,200"); err != nil {
t.Fatalf("SetPostLikedBy failed: %v", err)
}
liked, err := repo.GetPostLikedBy(posts[0].ID)
if err != nil {
t.Fatalf("GetPostLikedBy failed: %v", err)
}
if liked != "100,200" {
t.Errorf("Expected '100,200', got %q", liked)
}
}
func TestCountNewPosts(t *testing.T) {
repo, _, guildID, charID := setupGuildRepo(t)
since := time.Now().Add(-1 * time.Hour)
if err := repo.CreatePost(guildID, charID, 0, 0, "New", "body", 10); err != nil {
t.Fatalf("CreatePost failed: %v", err)
}
count, err := repo.CountNewPosts(guildID, since)
if err != nil {
t.Fatalf("CountNewPosts failed: %v", err)
}
if count != 1 {
t.Errorf("Expected 1 new post, got %d", count)
}
// Future time should yield 0
count, err = repo.CountNewPosts(guildID, time.Now().Add(1*time.Hour))
if err != nil {
t.Fatalf("CountNewPosts (future) failed: %v", err)
}
if count != 0 {
t.Errorf("Expected 0 new posts with future time, got %d", count)
}
}
func TestListPostsByType(t *testing.T) {
repo, _, guildID, charID := setupGuildRepo(t)
if err := repo.CreatePost(guildID, charID, 0, 0, "TypeA", "body", 10); err != nil {
t.Fatalf("CreatePost type 0 failed: %v", err)
}
if err := repo.CreatePost(guildID, charID, 0, 1, "TypeB", "body", 10); err != nil {
t.Fatalf("CreatePost type 1 failed: %v", err)
}
posts0, _ := repo.ListPosts(guildID, 0)
posts1, _ := repo.ListPosts(guildID, 1)
if len(posts0) != 1 {
t.Errorf("Expected 1 type-0 post, got %d", len(posts0))
}
if len(posts1) != 1 {
t.Errorf("Expected 1 type-1 post, got %d", len(posts1))
}
}
// --- Guild Alliances ---
func TestCreateAndGetAlliance(t *testing.T) {
repo, db, guildID, _ := setupGuildRepo(t)
if err := repo.CreateAlliance("TestAlliance", guildID); err != nil {
t.Fatalf("CreateAlliance failed: %v", err)
}
var allianceID uint32
if err := db.QueryRow("SELECT id FROM guild_alliances WHERE parent_id=$1", guildID).Scan(&allianceID); err != nil {
t.Fatalf("Alliance not found in DB: %v", err)
}
alliance, err := repo.GetAllianceByID(allianceID)
if err != nil {
t.Fatalf("GetAllianceByID failed: %v", err)
}
if alliance == nil {
t.Fatal("Expected alliance, got nil")
}
if alliance.Name != "TestAlliance" {
t.Errorf("Expected name 'TestAlliance', got %q", alliance.Name)
}
if alliance.ParentGuildID != guildID {
t.Errorf("Expected parent guild %d, got %d", guildID, alliance.ParentGuildID)
}
if alliance.ParentGuild.ID != guildID {
t.Errorf("Expected populated ParentGuild.ID=%d, got %d", guildID, alliance.ParentGuild.ID)
}
}
func TestGetAllianceByIDNotFound(t *testing.T) {
repo, _, _, _ := setupGuildRepo(t)
alliance, err := repo.GetAllianceByID(999999)
if err != nil {
t.Fatalf("GetAllianceByID failed: %v", err)
}
if alliance != nil {
t.Errorf("Expected nil for non-existent alliance, got: %+v", alliance)
}
}
func TestListAlliances(t *testing.T) {
repo, db, guildID, _ := setupGuildRepo(t)
if err := repo.CreateAlliance("Alliance1", guildID); err != nil {
t.Fatalf("CreateAlliance failed: %v", err)
}
// Create a second guild and alliance
user2 := CreateTestUser(t, db, "alliance_user2")
char2 := CreateTestCharacter(t, db, user2, "AlliLeader2")
guild2 := CreateTestGuild(t, db, char2, "AlliGuild2")
if err := repo.CreateAlliance("Alliance2", guild2); err != nil {
t.Fatalf("CreateAlliance 2 failed: %v", err)
}
alliances, err := repo.ListAlliances()
if err != nil {
t.Fatalf("ListAlliances failed: %v", err)
}
if len(alliances) < 2 {
t.Errorf("Expected at least 2 alliances, got %d", len(alliances))
}
}
func TestDeleteAlliance(t *testing.T) {
repo, db, guildID, _ := setupGuildRepo(t)
if err := repo.CreateAlliance("ToDelete", guildID); err != nil {
t.Fatalf("CreateAlliance failed: %v", err)
}
var allianceID uint32
if err := db.QueryRow("SELECT id FROM guild_alliances WHERE parent_id=$1", guildID).Scan(&allianceID); err != nil {
t.Fatalf("Alliance not found: %v", err)
}
if err := repo.DeleteAlliance(allianceID); err != nil {
t.Fatalf("DeleteAlliance failed: %v", err)
}
alliance, err := repo.GetAllianceByID(allianceID)
if err != nil {
t.Fatalf("GetAllianceByID after delete failed: %v", err)
}
if alliance != nil {
t.Errorf("Expected nil after delete, got: %+v", alliance)
}
}
func TestRemoveGuildFromAllianceSub1(t *testing.T) {
repo, db, guildID, _ := setupGuildRepo(t)
user2 := CreateTestUser(t, db, "alli_sub1_user")
char2 := CreateTestCharacter(t, db, user2, "Sub1Leader")
guild2 := CreateTestGuild(t, db, char2, "SubGuild1")
if err := repo.CreateAlliance("AlliSub", guildID); err != nil {
t.Fatalf("CreateAlliance failed: %v", err)
}
var allianceID uint32
db.QueryRow("SELECT id FROM guild_alliances WHERE parent_id=$1", guildID).Scan(&allianceID)
// Add sub1
db.Exec("UPDATE guild_alliances SET sub1_id=$1 WHERE id=$2", guild2, allianceID)
// Remove sub1
if err := repo.RemoveGuildFromAlliance(allianceID, guild2, guild2, 0); err != nil {
t.Fatalf("RemoveGuildFromAlliance failed: %v", err)
}
alliance, err := repo.GetAllianceByID(allianceID)
if err != nil {
t.Fatalf("GetAllianceByID failed: %v", err)
}
if alliance == nil {
t.Fatal("Expected alliance to still exist")
}
if alliance.SubGuild1ID != 0 {
t.Errorf("Expected sub1_id=0, got %d", alliance.SubGuild1ID)
}
}
func TestRemoveGuildFromAllianceSub1ShiftsSub2(t *testing.T) {
repo, db, guildID, _ := setupGuildRepo(t)
user2 := CreateTestUser(t, db, "alli_shift_user2")
char2 := CreateTestCharacter(t, db, user2, "Shift2Leader")
guild2 := CreateTestGuild(t, db, char2, "ShiftGuild2")
user3 := CreateTestUser(t, db, "alli_shift_user3")
char3 := CreateTestCharacter(t, db, user3, "Shift3Leader")
guild3 := CreateTestGuild(t, db, char3, "ShiftGuild3")
if err := repo.CreateAlliance("AlliShift", guildID); err != nil {
t.Fatalf("CreateAlliance failed: %v", err)
}
var allianceID uint32
db.QueryRow("SELECT id FROM guild_alliances WHERE parent_id=$1", guildID).Scan(&allianceID)
db.Exec("UPDATE guild_alliances SET sub1_id=$1, sub2_id=$2 WHERE id=$3", guild2, guild3, allianceID)
// Remove sub1 — sub2 should shift into sub1's slot
if err := repo.RemoveGuildFromAlliance(allianceID, guild2, guild2, guild3); err != nil {
t.Fatalf("RemoveGuildFromAlliance failed: %v", err)
}
alliance, err := repo.GetAllianceByID(allianceID)
if err != nil {
t.Fatalf("GetAllianceByID failed: %v", err)
}
if alliance == nil {
t.Fatal("Expected alliance to still exist")
}
if alliance.SubGuild1ID != guild3 {
t.Errorf("Expected sub1_id=%d (shifted from sub2), got %d", guild3, alliance.SubGuild1ID)
}
if alliance.SubGuild2ID != 0 {
t.Errorf("Expected sub2_id=0, got %d", alliance.SubGuild2ID)
}
}
func TestRemoveGuildFromAllianceSub2(t *testing.T) {
repo, db, guildID, _ := setupGuildRepo(t)
user2 := CreateTestUser(t, db, "alli_s2_user2")
char2 := CreateTestCharacter(t, db, user2, "S2Leader2")
guild2 := CreateTestGuild(t, db, char2, "S2Guild2")
user3 := CreateTestUser(t, db, "alli_s2_user3")
char3 := CreateTestCharacter(t, db, user3, "S2Leader3")
guild3 := CreateTestGuild(t, db, char3, "S2Guild3")
if err := repo.CreateAlliance("AlliS2", guildID); err != nil {
t.Fatalf("CreateAlliance failed: %v", err)
}
var allianceID uint32
db.QueryRow("SELECT id FROM guild_alliances WHERE parent_id=$1", guildID).Scan(&allianceID)
db.Exec("UPDATE guild_alliances SET sub1_id=$1, sub2_id=$2 WHERE id=$3", guild2, guild3, allianceID)
// Remove sub2 directly
if err := repo.RemoveGuildFromAlliance(allianceID, guild3, guild2, guild3); err != nil {
t.Fatalf("RemoveGuildFromAlliance failed: %v", err)
}
alliance, err := repo.GetAllianceByID(allianceID)
if err != nil {
t.Fatalf("GetAllianceByID failed: %v", err)
}
if alliance == nil {
t.Fatal("Expected alliance to still exist")
}
if alliance.SubGuild1ID != guild2 {
t.Errorf("Expected sub1_id=%d unchanged, got %d", guild2, alliance.SubGuild1ID)
}
if alliance.SubGuild2ID != 0 {
t.Errorf("Expected sub2_id=0, got %d", alliance.SubGuild2ID)
}
}
// --- Guild Adventures ---
func TestCreateAndListAdventures(t *testing.T) {
repo, _, guildID, _ := setupGuildRepo(t)
if err := repo.CreateAdventure(guildID, 5, 1000, 2000); err != nil {
t.Fatalf("CreateAdventure failed: %v", err)
}
adventures, err := repo.ListAdventures(guildID)
if err != nil {
t.Fatalf("ListAdventures failed: %v", err)
}
if len(adventures) != 1 {
t.Fatalf("Expected 1 adventure, got %d", len(adventures))
}
if adventures[0].Destination != 5 {
t.Errorf("Expected destination=5, got %d", adventures[0].Destination)
}
if adventures[0].Depart != 1000 {
t.Errorf("Expected depart=1000, got %d", adventures[0].Depart)
}
if adventures[0].Return != 2000 {
t.Errorf("Expected return=2000, got %d", adventures[0].Return)
}
}
func TestCreateAdventureWithCharge(t *testing.T) {
repo, _, guildID, _ := setupGuildRepo(t)
if err := repo.CreateAdventureWithCharge(guildID, 3, 50, 1000, 2000); err != nil {
t.Fatalf("CreateAdventureWithCharge failed: %v", err)
}
adventures, err := repo.ListAdventures(guildID)
if err != nil {
t.Fatalf("ListAdventures failed: %v", err)
}
if len(adventures) != 1 {
t.Fatalf("Expected 1 adventure, got %d", len(adventures))
}
if adventures[0].Charge != 50 {
t.Errorf("Expected charge=50, got %d", adventures[0].Charge)
}
}
func TestChargeAdventure(t *testing.T) {
repo, db, guildID, _ := setupGuildRepo(t)
if err := repo.CreateAdventure(guildID, 1, 1000, 2000); err != nil {
t.Fatalf("CreateAdventure failed: %v", err)
}
adventures, _ := repo.ListAdventures(guildID)
advID := adventures[0].ID
if err := repo.ChargeAdventure(advID, 25); err != nil {
t.Fatalf("ChargeAdventure failed: %v", err)
}
var charge uint32
db.QueryRow("SELECT charge FROM guild_adventures WHERE id=$1", advID).Scan(&charge)
if charge != 25 {
t.Errorf("Expected charge=25, got %d", charge)
}
}
func TestCollectAdventure(t *testing.T) {
repo, _, guildID, charID := setupGuildRepo(t)
if err := repo.CreateAdventure(guildID, 1, 1000, 2000); err != nil {
t.Fatalf("CreateAdventure failed: %v", err)
}
adventures, _ := repo.ListAdventures(guildID)
advID := adventures[0].ID
if err := repo.CollectAdventure(advID, charID); err != nil {
t.Fatalf("CollectAdventure failed: %v", err)
}
// Verify collected_by updated
adventures, _ = repo.ListAdventures(guildID)
if adventures[0].CollectedBy == "" {
t.Error("Expected collected_by to be non-empty")
}
}
func TestListAdventuresEmpty(t *testing.T) {
repo, _, guildID, _ := setupGuildRepo(t)
adventures, err := repo.ListAdventures(guildID)
if err != nil {
t.Fatalf("ListAdventures failed: %v", err)
}
if len(adventures) != 0 {
t.Errorf("Expected 0 adventures, got %d", len(adventures))
}
}
// --- Guild Treasure Hunts ---
func TestCreateAndGetPendingHunt(t *testing.T) {
repo, _, guildID, charID := setupGuildRepo(t)
huntData := []byte{0xAA, 0xBB, 0xCC}
if err := repo.CreateHunt(guildID, charID, 10, 1, huntData, ""); err != nil {
t.Fatalf("CreateHunt failed: %v", err)
}
hunt, err := repo.GetPendingHunt(charID)
if err != nil {
t.Fatalf("GetPendingHunt failed: %v", err)
}
if hunt == nil {
t.Fatal("Expected pending hunt, got nil")
}
if hunt.HostID != charID {
t.Errorf("Expected host_id=%d, got %d", charID, hunt.HostID)
}
if hunt.Destination != 10 {
t.Errorf("Expected destination=10, got %d", hunt.Destination)
}
if hunt.Level != 1 {
t.Errorf("Expected level=1, got %d", hunt.Level)
}
if len(hunt.HuntData) != 3 || hunt.HuntData[0] != 0xAA {
t.Errorf("Expected hunt_data [AA BB CC], got %x", hunt.HuntData)
}
}
func TestGetPendingHuntNone(t *testing.T) {
repo, _, _, charID := setupGuildRepo(t)
hunt, err := repo.GetPendingHunt(charID)
if err != nil {
t.Fatalf("GetPendingHunt failed: %v", err)
}
if hunt != nil {
t.Errorf("Expected nil when no pending hunt, got: %+v", hunt)
}
}
func TestAcquireHunt(t *testing.T) {
repo, db, guildID, charID := setupGuildRepo(t)
if err := repo.CreateHunt(guildID, charID, 10, 2, nil, ""); err != nil {
t.Fatalf("CreateHunt failed: %v", err)
}
hunt, _ := repo.GetPendingHunt(charID)
if err := repo.AcquireHunt(hunt.HuntID); err != nil {
t.Fatalf("AcquireHunt failed: %v", err)
}
// After acquiring, it should no longer appear as pending
pending, _ := repo.GetPendingHunt(charID)
if pending != nil {
t.Error("Expected no pending hunt after acquire")
}
// Verify in DB
var acquired bool
db.QueryRow("SELECT acquired FROM guild_hunts WHERE id=$1", hunt.HuntID).Scan(&acquired)
if !acquired {
t.Error("Expected acquired=true in DB")
}
}
func TestListGuildHunts(t *testing.T) {
repo, _, guildID, charID := setupGuildRepo(t)
// Create a level-2 hunt and acquire it
if err := repo.CreateHunt(guildID, charID, 10, 2, []byte{0x01}, ""); err != nil {
t.Fatalf("CreateHunt failed: %v", err)
}
hunt, _ := repo.GetPendingHunt(charID)
repo.AcquireHunt(hunt.HuntID)
// Create a level-1 hunt (should not appear)
if err := repo.CreateHunt(guildID, charID, 20, 1, nil, ""); err != nil {
t.Fatalf("CreateHunt level-1 failed: %v", err)
}
hunts, err := repo.ListGuildHunts(guildID, charID)
if err != nil {
t.Fatalf("ListGuildHunts failed: %v", err)
}
if len(hunts) != 1 {
t.Fatalf("Expected 1 acquired level-2 hunt, got %d", len(hunts))
}
if hunts[0].Destination != 10 {
t.Errorf("Expected destination=10, got %d", hunts[0].Destination)
}
}
func TestRegisterHuntReport(t *testing.T) {
repo, db, guildID, charID := setupGuildRepo(t)
if err := repo.CreateHunt(guildID, charID, 10, 2, nil, ""); err != nil {
t.Fatalf("CreateHunt failed: %v", err)
}
hunt, _ := repo.GetPendingHunt(charID)
if err := repo.RegisterHuntReport(hunt.HuntID, charID); err != nil {
t.Fatalf("RegisterHuntReport failed: %v", err)
}
var treasureHunt *uint32
db.QueryRow("SELECT treasure_hunt FROM guild_characters WHERE character_id=$1", charID).Scan(&treasureHunt)
if treasureHunt == nil || *treasureHunt != hunt.HuntID {
t.Errorf("Expected treasure_hunt=%d, got %v", hunt.HuntID, treasureHunt)
}
}
func TestCollectHunt(t *testing.T) {
repo, db, guildID, charID := setupGuildRepo(t)
if err := repo.CreateHunt(guildID, charID, 10, 2, nil, ""); err != nil {
t.Fatalf("CreateHunt failed: %v", err)
}
hunt, _ := repo.GetPendingHunt(charID)
repo.RegisterHuntReport(hunt.HuntID, charID)
if err := repo.CollectHunt(hunt.HuntID); err != nil {
t.Fatalf("CollectHunt failed: %v", err)
}
// Hunt should be marked collected
var collected bool
db.QueryRow("SELECT collected FROM guild_hunts WHERE id=$1", hunt.HuntID).Scan(&collected)
if !collected {
t.Error("Expected collected=true")
}
// Character's treasure_hunt should be cleared
var treasureHunt *uint32
db.QueryRow("SELECT treasure_hunt FROM guild_characters WHERE character_id=$1", charID).Scan(&treasureHunt)
if treasureHunt != nil {
t.Errorf("Expected treasure_hunt=NULL, got %v", *treasureHunt)
}
}
func TestClaimHuntReward(t *testing.T) {
repo, db, guildID, charID := setupGuildRepo(t)
if err := repo.CreateHunt(guildID, charID, 10, 2, nil, ""); err != nil {
t.Fatalf("CreateHunt failed: %v", err)
}
hunt, _ := repo.GetPendingHunt(charID)
if err := repo.ClaimHuntReward(hunt.HuntID, charID); err != nil {
t.Fatalf("ClaimHuntReward failed: %v", err)
}
var count int
db.QueryRow("SELECT COUNT(*) FROM guild_hunts_claimed WHERE hunt_id=$1 AND character_id=$2", hunt.HuntID, charID).Scan(&count)
if count != 1 {
t.Errorf("Expected 1 claimed entry, got %d", count)
}
}
// --- Guild Meals ---
func TestCreateAndListMeals(t *testing.T) {
repo, _, guildID, _ := setupGuildRepo(t)
now := time.Now().UTC().Truncate(time.Second)
id, err := repo.CreateMeal(guildID, 5, 3, now)
if err != nil {
t.Fatalf("CreateMeal failed: %v", err)
}
if id == 0 {
t.Error("Expected non-zero meal ID")
}
meals, err := repo.ListMeals(guildID)
if err != nil {
t.Fatalf("ListMeals failed: %v", err)
}
if len(meals) != 1 {
t.Fatalf("Expected 1 meal, got %d", len(meals))
}
if meals[0].MealID != 5 {
t.Errorf("Expected meal_id=5, got %d", meals[0].MealID)
}
if meals[0].Level != 3 {
t.Errorf("Expected level=3, got %d", meals[0].Level)
}
}
func TestUpdateMeal(t *testing.T) {
repo, _, guildID, _ := setupGuildRepo(t)
now := time.Now().UTC().Truncate(time.Second)
id, _ := repo.CreateMeal(guildID, 5, 3, now)
later := now.Add(30 * time.Minute)
if err := repo.UpdateMeal(id, 10, 5, later); err != nil {
t.Fatalf("UpdateMeal failed: %v", err)
}
meals, _ := repo.ListMeals(guildID)
if meals[0].MealID != 10 {
t.Errorf("Expected meal_id=10, got %d", meals[0].MealID)
}
if meals[0].Level != 5 {
t.Errorf("Expected level=5, got %d", meals[0].Level)
}
}
func TestListMealsEmpty(t *testing.T) {
repo, _, guildID, _ := setupGuildRepo(t)
meals, err := repo.ListMeals(guildID)
if err != nil {
t.Fatalf("ListMeals failed: %v", err)
}
if len(meals) != 0 {
t.Errorf("Expected 0 meals, got %d", len(meals))
}
}
// --- Kill tracking ---
func TestClaimHuntBox(t *testing.T) {
repo, db, _, charID := setupGuildRepo(t)
claimedAt := time.Now().UTC().Truncate(time.Second)
if err := repo.ClaimHuntBox(charID, claimedAt); err != nil {
t.Fatalf("ClaimHuntBox failed: %v", err)
}
var got time.Time
db.QueryRow("SELECT box_claimed FROM guild_characters WHERE character_id=$1", charID).Scan(&got)
if !got.Equal(claimedAt) {
t.Errorf("Expected box_claimed=%v, got %v", claimedAt, got)
}
}
func TestListAndCountGuildKills(t *testing.T) {
repo, db, guildID, charID := setupGuildRepo(t)
// Set box_claimed to the past so kills after it are visible
past := time.Now().Add(-1 * time.Hour).UTC().Truncate(time.Second)
repo.ClaimHuntBox(charID, past)
// Insert kill logs for this character
db.Exec("INSERT INTO kill_logs (character_id, monster, quantity, timestamp) VALUES ($1, 100, 1, NOW())", charID)
db.Exec("INSERT INTO kill_logs (character_id, monster, quantity, timestamp) VALUES ($1, 200, 1, NOW())", charID)
kills, err := repo.ListGuildKills(guildID, charID)
if err != nil {
t.Fatalf("ListGuildKills failed: %v", err)
}
if len(kills) != 2 {
t.Fatalf("Expected 2 kills, got %d", len(kills))
}
count, err := repo.CountGuildKills(guildID, charID)
if err != nil {
t.Fatalf("CountGuildKills failed: %v", err)
}
if count != 2 {
t.Errorf("Expected count=2, got %d", count)
}
}
func TestListGuildKillsEmpty(t *testing.T) {
repo, _, guildID, charID := setupGuildRepo(t)
// Set box_claimed to now — no kills after it
repo.ClaimHuntBox(charID, time.Now().UTC())
kills, err := repo.ListGuildKills(guildID, charID)
if err != nil {
t.Fatalf("ListGuildKills failed: %v", err)
}
if len(kills) != 0 {
t.Errorf("Expected 0 kills, got %d", len(kills))
}
count, err := repo.CountGuildKills(guildID, charID)
if err != nil {
t.Fatalf("CountGuildKills failed: %v", err)
}
if count != 0 {
t.Errorf("Expected count=0, got %d", count)
}
}
// --- Disband with alliance cleanup ---
func TestDisbandCleansUpAlliance(t *testing.T) {
repo, db, guildID, _ := setupGuildRepo(t)
// Create alliance with this guild as parent
if err := repo.CreateAlliance("DisbandAlliance", guildID); err != nil {
t.Fatalf("CreateAlliance failed: %v", err)
}
var allianceID uint32
db.QueryRow("SELECT id FROM guild_alliances WHERE parent_id=$1", guildID).Scan(&allianceID)
if err := repo.Disband(guildID); err != nil {
t.Fatalf("Disband failed: %v", err)
}
// Alliance should be deleted too (parent_id match in Disband)
alliance, _ := repo.GetAllianceByID(allianceID)
if alliance != nil {
t.Errorf("Expected alliance to be deleted after parent guild disband, got: %+v", alliance)
}
}