mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-21 23:22:34 +01:00
test(channelserver): add tests for guild member and gacha functions
Add comprehensive tests for pure logic functions: - GuildMember.CanRecruit() and IsSubLeader() methods - getRandomEntries() for gacha weighted/box selection All targeted functions now have 100% coverage.
This commit is contained in:
209
server/channelserver/handlers_guild_member_test.go
Normal file
209
server/channelserver/handlers_guild_member_test.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGuildMember_CanRecruit(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
member GuildMember
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "recruiter flag true",
|
||||
member: GuildMember{
|
||||
Recruiter: true,
|
||||
OrderIndex: 10,
|
||||
IsLeader: false,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "order index 1",
|
||||
member: GuildMember{
|
||||
Recruiter: false,
|
||||
OrderIndex: 1,
|
||||
IsLeader: false,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "order index 2",
|
||||
member: GuildMember{
|
||||
Recruiter: false,
|
||||
OrderIndex: 2,
|
||||
IsLeader: false,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "order index 3",
|
||||
member: GuildMember{
|
||||
Recruiter: false,
|
||||
OrderIndex: 3,
|
||||
IsLeader: false,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "order index 0 (sub-leader)",
|
||||
member: GuildMember{
|
||||
Recruiter: false,
|
||||
OrderIndex: 0,
|
||||
IsLeader: false,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "order index 4 cannot recruit",
|
||||
member: GuildMember{
|
||||
Recruiter: false,
|
||||
OrderIndex: 4,
|
||||
IsLeader: false,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "order index 5 cannot recruit",
|
||||
member: GuildMember{
|
||||
Recruiter: false,
|
||||
OrderIndex: 5,
|
||||
IsLeader: false,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "is leader can recruit",
|
||||
member: GuildMember{
|
||||
Recruiter: false,
|
||||
OrderIndex: 100,
|
||||
IsLeader: true,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "regular member cannot recruit",
|
||||
member: GuildMember{
|
||||
Recruiter: false,
|
||||
OrderIndex: 10,
|
||||
IsLeader: false,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "all flags true",
|
||||
member: GuildMember{
|
||||
Recruiter: true,
|
||||
OrderIndex: 1,
|
||||
IsLeader: true,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "high order index with leader",
|
||||
member: GuildMember{
|
||||
Recruiter: false,
|
||||
OrderIndex: 255,
|
||||
IsLeader: true,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.member.CanRecruit()
|
||||
if result != tt.expected {
|
||||
t.Errorf("CanRecruit() = %v, expected %v (Recruiter=%v, OrderIndex=%d, IsLeader=%v)",
|
||||
result, tt.expected, tt.member.Recruiter, tt.member.OrderIndex, tt.member.IsLeader)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuildMember_IsSubLeader(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
orderIndex uint8
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "order index 0",
|
||||
orderIndex: 0,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "order index 1",
|
||||
orderIndex: 1,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "order index 2",
|
||||
orderIndex: 2,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "order index 3",
|
||||
orderIndex: 3,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "order index 4",
|
||||
orderIndex: 4,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "order index 5",
|
||||
orderIndex: 5,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "order index 100",
|
||||
orderIndex: 100,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "order index 255",
|
||||
orderIndex: 255,
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
member := GuildMember{OrderIndex: tt.orderIndex}
|
||||
result := member.IsSubLeader()
|
||||
if result != tt.expected {
|
||||
t.Errorf("IsSubLeader() with OrderIndex=%d = %v, expected %v",
|
||||
tt.orderIndex, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuildMember_CanRecruit_Priority(t *testing.T) {
|
||||
// Test that Recruiter flag takes priority (short-circuit)
|
||||
member := GuildMember{
|
||||
Recruiter: true,
|
||||
OrderIndex: 100, // Would fail OrderIndex check
|
||||
IsLeader: false,
|
||||
}
|
||||
|
||||
if !member.CanRecruit() {
|
||||
t.Error("Recruiter flag should allow recruiting regardless of OrderIndex")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuildMember_CanRecruit_OrderIndexBoundary(t *testing.T) {
|
||||
// Test the exact boundary at OrderIndex == 3 vs 4
|
||||
member3 := GuildMember{Recruiter: false, OrderIndex: 3, IsLeader: false}
|
||||
member4 := GuildMember{Recruiter: false, OrderIndex: 4, IsLeader: false}
|
||||
|
||||
if !member3.CanRecruit() {
|
||||
t.Error("OrderIndex 3 should be able to recruit")
|
||||
}
|
||||
if member4.CanRecruit() {
|
||||
t.Error("OrderIndex 4 should NOT be able to recruit")
|
||||
}
|
||||
}
|
||||
@@ -230,3 +230,192 @@ func TestGachaItemStruct(t *testing.T) {
|
||||
t.Errorf("Quantity = %d, want 20", item.Quantity)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRandomEntries_EmptyEntriesZeroRolls(t *testing.T) {
|
||||
// Note: getRandomEntries with empty entries and rolls > 0 causes infinite loop.
|
||||
// Only test the valid case of 0 rolls with empty entries.
|
||||
entries := []GachaEntry{}
|
||||
result, err := getRandomEntries(entries, 0, false)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if len(result) != 0 {
|
||||
t.Errorf("expected empty result, got %d entries", len(result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRandomEntries_ZeroRolls(t *testing.T) {
|
||||
entries := []GachaEntry{
|
||||
{ID: 1, Weight: 1.0},
|
||||
}
|
||||
result, err := getRandomEntries(entries, 0, false)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if len(result) != 0 {
|
||||
t.Errorf("expected 0 results, got %d", len(result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRandomEntries_SingleEntryNonBox(t *testing.T) {
|
||||
entries := []GachaEntry{
|
||||
{ID: 1, Weight: 1.0, ItemNumber: 100},
|
||||
}
|
||||
result, err := getRandomEntries(entries, 3, false)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if len(result) != 3 {
|
||||
t.Errorf("expected 3 results, got %d", len(result))
|
||||
}
|
||||
for i, r := range result {
|
||||
if r.ID != 1 {
|
||||
t.Errorf("result[%d].ID = %d, expected 1", i, r.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRandomEntries_NonBoxAllowsDuplicates(t *testing.T) {
|
||||
entries := []GachaEntry{
|
||||
{ID: 1, Weight: 1.0},
|
||||
}
|
||||
result, err := getRandomEntries(entries, 5, false)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if len(result) != 5 {
|
||||
t.Errorf("expected 5 results, got %d", len(result))
|
||||
}
|
||||
// All should be the same since there's only one entry
|
||||
for i, r := range result {
|
||||
if r.ID != 1 {
|
||||
t.Errorf("result[%d].ID = %d, expected 1", i, r.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRandomEntries_BoxModeRemovesSelected(t *testing.T) {
|
||||
entries := []GachaEntry{
|
||||
{ID: 1, Weight: 1.0},
|
||||
{ID: 2, Weight: 1.0},
|
||||
{ID: 3, Weight: 1.0},
|
||||
}
|
||||
result, err := getRandomEntries(entries, 3, true)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if len(result) != 3 {
|
||||
t.Errorf("expected 3 results, got %d", len(result))
|
||||
}
|
||||
|
||||
// In box mode, all entries should be unique
|
||||
seen := make(map[uint32]bool)
|
||||
for _, r := range result {
|
||||
if seen[r.ID] {
|
||||
t.Errorf("duplicate entry in box mode: ID=%d", r.ID)
|
||||
}
|
||||
seen[r.ID] = true
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRandomEntries_BoxModeMatchingCount(t *testing.T) {
|
||||
entries := []GachaEntry{
|
||||
{ID: 1, Weight: 1.0},
|
||||
{ID: 2, Weight: 1.0},
|
||||
}
|
||||
result, err := getRandomEntries(entries, 2, true)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if len(result) != 2 {
|
||||
t.Errorf("expected 2 results, got %d", len(result))
|
||||
}
|
||||
|
||||
// Should contain both entries exactly once
|
||||
seen := make(map[uint32]bool)
|
||||
for _, r := range result {
|
||||
seen[r.ID] = true
|
||||
}
|
||||
if !seen[1] || !seen[2] {
|
||||
t.Errorf("box mode should return all entries when rolls == len(entries)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRandomEntries_WeightedSelectionBias(t *testing.T) {
|
||||
// Test that weighted selection respects weights
|
||||
entries := []GachaEntry{
|
||||
{ID: 1, Weight: 100.0}, // Very high weight
|
||||
{ID: 2, Weight: 0.001}, // Very low weight
|
||||
}
|
||||
|
||||
// Run many iterations
|
||||
counts := make(map[uint32]int)
|
||||
for i := 0; i < 1000; i++ {
|
||||
result, _ := getRandomEntries(entries, 1, false)
|
||||
if len(result) > 0 {
|
||||
counts[result[0].ID]++
|
||||
}
|
||||
}
|
||||
|
||||
// ID 1 should be selected much more often
|
||||
if counts[1] <= counts[2] {
|
||||
t.Errorf("weighted selection not working: high weight count=%d, low weight count=%d",
|
||||
counts[1], counts[2])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRandomEntries_MultipleEntriesMultipleRolls(t *testing.T) {
|
||||
entries := []GachaEntry{
|
||||
{ID: 1, Weight: 1.0},
|
||||
{ID: 2, Weight: 1.0},
|
||||
{ID: 3, Weight: 1.0},
|
||||
}
|
||||
result, err := getRandomEntries(entries, 10, false)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if len(result) != 10 {
|
||||
t.Errorf("expected 10 results, got %d", len(result))
|
||||
}
|
||||
|
||||
// All results should have valid IDs
|
||||
for i, r := range result {
|
||||
if r.ID < 1 || r.ID > 3 {
|
||||
t.Errorf("result[%d].ID = %d, expected 1, 2, or 3", i, r.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRandomEntries_PreservesEntryData(t *testing.T) {
|
||||
entries := []GachaEntry{
|
||||
{
|
||||
ID: 1,
|
||||
Weight: 1.0,
|
||||
ItemNumber: 100,
|
||||
ItemQuantity: 5,
|
||||
Rarity: 3,
|
||||
FrontierPoints: 500,
|
||||
},
|
||||
}
|
||||
result, err := getRandomEntries(entries, 1, false)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("expected 1 result, got %d", len(result))
|
||||
}
|
||||
|
||||
r := result[0]
|
||||
if r.ItemNumber != 100 {
|
||||
t.Errorf("ItemNumber = %d, expected 100", r.ItemNumber)
|
||||
}
|
||||
if r.ItemQuantity != 5 {
|
||||
t.Errorf("ItemQuantity = %d, expected 5", r.ItemQuantity)
|
||||
}
|
||||
if r.Rarity != 3 {
|
||||
t.Errorf("Rarity = %d, expected 3", r.Rarity)
|
||||
}
|
||||
if r.FrontierPoints != 500 {
|
||||
t.Errorf("FrontierPoints = %d, expected 500", r.FrontierPoints)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user