mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,10 +128,46 @@ func ApplyTestSchema(t *testing.T, db *sqlx.DB) {
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the 9.2 update schema (init.sql bootstraps to 9.1.0)
|
||||
applyUpdateSchema(t, db, projectRoot)
|
||||
|
||||
// Apply patch schemas in order
|
||||
applyPatchSchemas(t, db, projectRoot)
|
||||
}
|
||||
|
||||
// applyUpdateSchema applies the 9.2 update schema that bridges init.sql (v9.1.0) to v9.2.0.
|
||||
// It runs each statement individually to tolerate partial failures (e.g. role references).
|
||||
func applyUpdateSchema(t *testing.T, db *sqlx.DB, projectRoot string) {
|
||||
t.Helper()
|
||||
|
||||
updatePath := filepath.Join(projectRoot, "schemas", "update-schema", "9.2-update.sql")
|
||||
updateSQL, err := os.ReadFile(updatePath)
|
||||
if err != nil {
|
||||
t.Logf("Warning: Could not read 9.2 update schema: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Strip the outer BEGIN/END transaction wrapper so we can run statements individually.
|
||||
content := string(updateSQL)
|
||||
content = strings.Replace(content, "BEGIN;", "", 1)
|
||||
// Remove trailing END; (last occurrence)
|
||||
if idx := strings.LastIndex(content, "END;"); idx >= 0 {
|
||||
content = content[:idx] + content[idx+4:]
|
||||
}
|
||||
|
||||
// Split on semicolons and execute each statement, tolerating errors from
|
||||
// role references or already-applied changes.
|
||||
for _, stmt := range strings.Split(content, ";") {
|
||||
stmt = strings.TrimSpace(stmt)
|
||||
if stmt == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := db.Exec(stmt); err != nil {
|
||||
// Silently ignore — these are expected for role mismatches, already-applied changes, etc.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// applyPatchSchemas applies all patch schema files in numeric order
|
||||
func applyPatchSchemas(t *testing.T, db *sqlx.DB, projectRoot string) {
|
||||
t.Helper()
|
||||
|
||||
Reference in New Issue
Block a user