package plugin import ( "strings" "testing" ) // ── Housing Tier Definitions ─────────────────────────────────────────────── func TestHouseTierByTier_Valid(t *testing.T) { for _, def := range houseTiers { got := houseTierByTier(def.Tier) if got == nil { t.Errorf("houseTierByTier(%d) returned nil", def.Tier) continue } if got.Tier != def.Tier { t.Errorf("houseTierByTier(%d).Tier = %d", def.Tier, got.Tier) } } } func TestHouseTierByTier_Invalid(t *testing.T) { if houseTierByTier(0) != nil { t.Error("tier 0 (no house) should return nil") } if houseTierByTier(99) != nil { t.Error("tier 99 should return nil") } } func TestHouseNextTier(t *testing.T) { // From no house (tier 0) → tier 1 def := houseNextTier(0) if def == nil || def.Tier != 1 { t.Error("next tier from 0 should be 1") } // From tier 1 → tier 2 def = houseNextTier(1) if def == nil || def.Tier != 2 { t.Error("next tier from 1 should be 2") } // From max tier → nil def = houseNextTier(4) if def != nil { t.Error("next tier from max should be nil") } } // ── Closing Costs ────────────────────────────────────────────────────────── func TestHouseClosingCosts(t *testing.T) { // 11% of 75000 = 8250 costs := houseClosingCosts(75000) if costs != 8250 { t.Errorf("closing costs on 75000: got %d, want 8250", costs) } } func TestHouseTotalCost(t *testing.T) { // 75000 + 11% = 83250 total := houseTotalCost(75000) if total != 83250 { t.Errorf("total cost on 75000: got %d, want 83250", total) } } // ── Weekly Payment ───────────────────────────────────────────────────────── func TestHouseWeeklyPayment_Normal(t *testing.T) { // 100000 balance at 6.5%: interest = 100000*0.065/52 ≈ 125, principal = 100000*0.005 = 500 // Total ≈ 625 payment := houseWeeklyPayment(100000, 6.5) if payment < 600 || payment > 650 { t.Errorf("weekly payment on 100k at 6.5%%: got %d, want ~625", payment) } } func TestHouseWeeklyPayment_ZeroBalance(t *testing.T) { if houseWeeklyPayment(0, 6.5) != 0 { t.Error("zero balance should produce zero payment") } } func TestHouseWeeklyPayment_ZeroRate(t *testing.T) { if houseWeeklyPayment(100000, 0) != 0 { t.Error("zero rate should produce zero payment") } } func TestHouseWeeklyPayment_MinimumOne(t *testing.T) { // Very small balance should still yield at least 1 payment := houseWeeklyPayment(1, 0.01) if payment < 1 { t.Errorf("tiny balance should produce at least 1, got %d", payment) } } // ── Missed Payment Penalty ───────────────────────────────────────────────── func TestHouseMissedPenalty(t *testing.T) { // 5% of 625 = 31 penalty := houseMissedPenalty(625) if penalty != 31 { t.Errorf("missed penalty on 625 weekly: got %d, want 31", penalty) } } func TestHouseMissedPenalty_Minimum(t *testing.T) { // Tiny payment should still yield at least 1 penalty := houseMissedPenalty(1) if penalty < 1 { t.Errorf("penalty should be at least 1, got %d", penalty) } } // ── Character Helper Methods ─────────────────────────────────────────────── func TestHasHouse(t *testing.T) { tests := []struct { name string tier int loan int expected bool }{ {"no house", 0, 0, false}, {"base house", 1, 0, true}, {"loan only", 0, 50000, true}, {"livable with loan", 2, 100000, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { char := &AdventureCharacter{HouseTier: tt.tier, HouseLoanBalance: tt.loan} if char.HasHouse() != tt.expected { t.Errorf("HasHouse() = %v, want %v", char.HasHouse(), tt.expected) } }) } } func TestHouseHPBonus(t *testing.T) { tests := []struct { tier int expected float64 }{ {0, 0}, {1, 0}, // Base house — no bonus {2, 0.05}, // Livable {3, 0.12}, // Comfortable {4, 0.20}, // Established } for _, tt := range tests { char := &AdventureCharacter{HouseTier: tt.tier} if bonus := char.HouseHPBonus(); bonus != tt.expected { t.Errorf("tier %d: HouseHPBonus() = %f, want %f", tt.tier, bonus, tt.expected) } } } // ── Thom Greeting ────────────────────────────────────────────────────────── func TestThomGreeting_PreAdoption(t *testing.T) { char := &AdventureCharacter{} greeting := thomGreeting(char) found := false for _, g := range thomGreetingsPreAdoption { if greeting == g { found = true break } } if !found { t.Errorf("pre-adoption greeting not from expected pool: %q", greeting) } } func TestThomGreeting_PostAdoption(t *testing.T) { char := &AdventureCharacter{PetType: "dog", PetName: "Rex", PetArrived: true, PetLevel: 5} greeting := thomGreeting(char) found := false for _, g := range thomGreetingsPostAdoption { if greeting == g { found = true break } } if !found { t.Errorf("post-adoption greeting not from expected pool: %q", greeting) } } func TestThomGreeting_PostLevel10(t *testing.T) { char := &AdventureCharacter{PetType: "cat", PetName: "Whiskers", PetArrived: true, PetLevel: 10} greeting := thomGreeting(char) if !strings.Contains(greeting, "Whiskers") { t.Errorf("post-level-10 greeting should contain pet name, got: %q", greeting) } } // ── Thom Shop View ───────────────────────────────────────────────────────── func TestThomShopView_NoHouse(t *testing.T) { char := &AdventureCharacter{} text := thomShopView(char, 100000) if !strings.Contains(text, "Krooke Realty") { t.Error("should contain shop name") } if !strings.Contains(text, "don't own a house") { t.Error("should mention no house") } if !strings.Contains(text, "Base House") { t.Error("should show first tier available") } if !strings.Contains(text, "€75000") { t.Error("should show base price") } } func TestThomShopView_WithLoan(t *testing.T) { char := &AdventureCharacter{HouseTier: 1, HouseLoanBalance: 50000, HouseCurrentRate: 6.5} text := thomShopView(char, 10000) if !strings.Contains(text, "Loan balance") { t.Error("should show loan balance") } if !strings.Contains(text, "Pay off") { t.Error("should mention paying off loan") } } func TestThomShopView_MaxTier(t *testing.T) { char := &AdventureCharacter{HouseTier: 4} // max tier text := thomShopView(char, 999999) if !strings.Contains(text, "Max tier") { t.Error("should mention max tier reached") } } func TestThomShopView_Autopay(t *testing.T) { char := &AdventureCharacter{HouseTier: 1, HouseLoanBalance: 50000, HouseCurrentRate: 6.5, HouseAutopay: true} text := thomShopView(char, 10000) if !strings.Contains(text, "autopay") { t.Error("should show autopay status") } } func TestThomShopView_Frozen(t *testing.T) { char := &AdventureCharacter{HouseTier: 1, HouseLoanBalance: 50000, HouseCurrentRate: 6.5, HouseLoanFrozen: true} text := thomShopView(char, 10000) if !strings.Contains(text, "FROZEN") { t.Error("should show frozen status") } } // ── Pet Armor ────────────────────────────────────────────────────────────── func TestPetArmorDefs_BothTypes(t *testing.T) { dogDefs := petArmorDefs("dog") catDefs := petArmorDefs("cat") if len(dogDefs) != 5 { t.Errorf("dog armor: got %d tiers, want 5", len(dogDefs)) } if len(catDefs) != 5 { t.Errorf("cat armor: got %d tiers, want 5", len(catDefs)) } // Verify tiers are sequential 1-5 for i, def := range dogDefs { if def.Tier != i+1 { t.Errorf("dog armor[%d].Tier = %d, want %d", i, def.Tier, i+1) } } // Deflect bonuses should increase with tier for i := 1; i < len(dogDefs); i++ { if dogDefs[i].DeflectBonus <= dogDefs[i-1].DeflectBonus { t.Errorf("dog armor tier %d deflect bonus should exceed tier %d", dogDefs[i].Tier, dogDefs[i-1].Tier) } } } func TestPetArmorShopView_HasUpgrades(t *testing.T) { text := petArmorShopView("dog", 0, "petbuy") if !strings.Contains(text, "⬆️") { t.Error("should show upgrade indicators") } if !strings.Contains(text, "petbuy") { t.Error("should show buy instructions") } } func TestPetArmorShopView_MaxTier(t *testing.T) { text := petArmorShopView("cat", 5, "petbuy") if !strings.Contains(text, "Max pet armor") { t.Error("should show max tier message") } } // ── Flavor Pool Coverage ─────────────────────────────────────────────────── func TestThomFlavorPools_NonEmpty(t *testing.T) { pools := map[string][]string{ "thomGreetingsPreAdoption": thomGreetingsPreAdoption, "thomGreetingsPostAdoption": thomGreetingsPostAdoption, "thomGreetingsPostLevel10": thomGreetingsPostLevel10, "thomHouseSellingLines": thomHouseSellingLines, "ThomRateIncrease": ThomRateIncrease, "ThomRateDecrease": ThomRateDecrease, "ThomRateIncreasePet": ThomRateIncreasePet, "ThomRateDecreasePet": ThomRateDecreasePet, } for name, pool := range pools { if len(pool) == 0 { t.Errorf("flavor pool %s is empty", name) } } } func TestThomRateFlavor_Placeholders(t *testing.T) { for i, line := range ThomRateIncrease { if !strings.Contains(line, "{amount}") { t.Errorf("ThomRateIncrease[%d] missing {amount} placeholder", i) } } for i, line := range ThomRateDecrease { if !strings.Contains(line, "{amount}") { t.Errorf("ThomRateDecrease[%d] missing {amount} placeholder", i) } } for i, line := range ThomRateIncreasePet { if !strings.Contains(line, "{amount}") { t.Errorf("ThomRateIncreasePet[%d] missing {amount} placeholder", i) } if !strings.Contains(line, "{pet_name}") { t.Errorf("ThomRateIncreasePet[%d] missing {pet_name} placeholder", i) } } for i, line := range ThomRateDecreasePet { if !strings.Contains(line, "{amount}") { t.Errorf("ThomRateDecreasePet[%d] missing {amount} placeholder", i) } if !strings.Contains(line, "{pet_name}") { t.Errorf("ThomRateDecreasePet[%d] missing {pet_name} placeholder", i) } } } // ── House Tiers Sanity ───────────────────────────────────────────────────── func TestHouseTiers_PricesIncreasing(t *testing.T) { for i := 1; i < len(houseTiers); i++ { if houseTiers[i].BasePrice <= houseTiers[i-1].BasePrice { t.Errorf("tier %d price (%d) should exceed tier %d price (%d)", houseTiers[i].Tier, houseTiers[i].BasePrice, houseTiers[i-1].Tier, houseTiers[i-1].BasePrice) } } } func TestHouseTiers_SequentialTiers(t *testing.T) { for i, def := range houseTiers { if def.Tier != i+1 { t.Errorf("houseTiers[%d].Tier = %d, want %d", i, def.Tier, i+1) } } }