eq2go/internal/recipes/recipes_test.go

865 lines
20 KiB
Go

package recipes
import (
"testing"
)
// Test Recipe creation and basic operations
func TestNewRecipe(t *testing.T) {
recipe := NewRecipe()
if recipe == nil {
t.Error("NewRecipe should not return nil")
}
if recipe.Components == nil {
t.Error("Components map should be initialized")
}
if recipe.Products == nil {
t.Error("Products map should be initialized")
}
}
func TestNewRecipeFromRecipe(t *testing.T) {
// Test with nil source
recipe := NewRecipeFromRecipe(nil)
if recipe == nil {
t.Error("NewRecipeFromRecipe with nil should return valid recipe")
}
// Test with valid source
source := NewRecipe()
source.ID = 12345
source.Name = "Test Recipe"
source.Level = 50
source.Tier = 5
source.Icon = 100
source.Components[0] = []int32{1001, 1002}
source.Products[0] = &RecipeProducts{
ProductID: 2001,
ProductQty: 1,
}
copied := NewRecipeFromRecipe(source)
if copied == nil {
t.Error("NewRecipeFromRecipe should not return nil")
}
if copied.ID != source.ID {
t.Error("Copied recipe should have same ID")
}
if copied.Name != source.Name {
t.Error("Copied recipe should have same name")
}
if copied.Level != source.Level {
t.Error("Copied recipe should have same level")
}
if copied.Tier != source.Tier {
t.Error("Copied recipe should have same tier")
}
// Check component copying
if len(copied.Components[0]) != len(source.Components[0]) {
t.Error("Components should be copied")
}
// Check product copying
if copied.Products[0] == nil {
t.Error("Products should be copied")
}
if copied.Products[0].ProductID != source.Products[0].ProductID {
t.Error("Product data should be copied correctly")
}
}
func TestRecipeIsValid(t *testing.T) {
// Test invalid recipe (empty)
recipe := NewRecipe()
if recipe.IsValid() {
t.Error("Empty recipe should not be valid")
}
// Test valid recipe
recipe.ID = 12345
recipe.Name = "Valid Recipe"
recipe.Level = 50
recipe.Tier = 5 // Need valid tier for validation
if !recipe.IsValid() {
t.Error("Recipe with valid data should be valid")
}
// Test invalid ID
recipe.ID = 0
if recipe.IsValid() {
t.Error("Recipe with ID 0 should not be valid")
}
// Test empty name
recipe.ID = 12345
recipe.Name = ""
if recipe.IsValid() {
t.Error("Recipe with empty name should not be valid")
}
// Test invalid level
recipe.Name = "Valid Recipe"
recipe.Tier = 5
recipe.Level = -1
if recipe.IsValid() {
t.Error("Recipe with negative level should not be valid")
}
recipe.Level = 101
if recipe.IsValid() {
t.Error("Recipe with level > 100 should not be valid")
}
// Test invalid tier
recipe.Level = 50
recipe.Tier = 0
if recipe.IsValid() {
t.Error("Recipe with tier 0 should not be valid")
}
recipe.Tier = 11
if recipe.IsValid() {
t.Error("Recipe with tier > 10 should not be valid")
}
}
func TestRecipeGetTotalBuildComponents(t *testing.T) {
recipe := NewRecipe()
// Test with no build components
if recipe.GetTotalBuildComponents() != 0 {
t.Error("Recipe with no build components should return 0")
}
// Add build components to different slots
recipe.Components[SlotBuild1] = []int32{1001}
recipe.Components[SlotBuild3] = []int32{1003, 1004}
count := recipe.GetTotalBuildComponents()
if count != 2 {
t.Errorf("Expected 2 build component slots, got %d", count)
}
}
func TestRecipeCanUseRecipeByClass(t *testing.T) {
recipe := NewRecipe()
// Test "any class" recipe
recipe.Classes = 2
if !recipe.CanUseRecipeByClass(1) {
t.Error("Any class recipe should be usable by any class")
}
// Test specific class requirement
recipe.Classes = 1 << 3 // Bit 3 set (class ID 3)
if !recipe.CanUseRecipeByClass(3) {
t.Error("Recipe should be usable by matching class")
}
if recipe.CanUseRecipeByClass(2) {
t.Error("Recipe should not be usable by non-matching class")
}
}
func TestRecipeComponentOperations(t *testing.T) {
recipe := NewRecipe()
// Test getting components by slot
recipe.Components[0] = []int32{1001, 1002, 1003}
components := recipe.GetComponentsBySlot(0)
if len(components) != 3 {
t.Error("Should return all components for slot")
}
// Test with empty slot
emptyComponents := recipe.GetComponentsBySlot(1)
if len(emptyComponents) != 0 {
t.Error("Empty slot should return empty slice")
}
}
func TestRecipeProductOperations(t *testing.T) {
recipe := NewRecipe()
// Test getting products for stage
recipe.Products[0] = &RecipeProducts{
ProductID: 2001,
ProductQty: 1,
ByproductID: 2002,
ByproductQty: 2,
}
products := recipe.GetProductsForStage(0)
if products == nil {
t.Error("Should return products for stage")
}
if products.ProductID != 2001 {
t.Error("Product ID should match")
}
if products.ByproductID != 2002 {
t.Error("Byproduct ID should match")
}
// Test with empty stage
emptyProducts := recipe.GetProductsForStage(1)
if emptyProducts != nil {
t.Error("Empty stage should return nil")
}
}
// Test MasterRecipeList operations
func TestNewMasterRecipeList(t *testing.T) {
list := NewMasterRecipeList()
if list == nil {
t.Error("NewMasterRecipeList should not return nil")
}
if list.recipes == nil {
t.Error("Recipes map should be initialized")
}
if list.stats == nil {
t.Error("Statistics should be initialized")
}
}
func TestMasterRecipeListAddRecipe(t *testing.T) {
list := NewMasterRecipeList()
// Test adding nil recipe
if list.AddRecipe(nil) {
t.Error("Adding nil recipe should fail")
}
// Test adding invalid recipe
invalidRecipe := NewRecipe()
if list.AddRecipe(invalidRecipe) {
t.Error("Adding invalid recipe should fail")
}
// Test adding valid recipe
validRecipe := NewRecipe()
validRecipe.ID = 12345
validRecipe.Name = "Test Recipe"
validRecipe.Level = 50
validRecipe.Tier = 5 // Need valid tier
if !list.AddRecipe(validRecipe) {
t.Error("Adding valid recipe should succeed")
}
// Test adding duplicate recipe
duplicate := NewRecipe()
duplicate.ID = 12345
duplicate.Name = "Duplicate"
duplicate.Level = 60
if list.AddRecipe(duplicate) {
t.Error("Adding duplicate recipe ID should fail")
}
}
func TestMasterRecipeListGetRecipe(t *testing.T) {
list := NewMasterRecipeList()
// Test getting non-existent recipe
recipe := list.GetRecipe(99999)
if recipe != nil {
t.Error("Getting non-existent recipe should return nil")
}
// Add a recipe
testRecipe := NewRecipe()
testRecipe.ID = 12345
testRecipe.Name = "Test Recipe"
testRecipe.Level = 50
testRecipe.Tier = 5 // Need valid tier
list.AddRecipe(testRecipe)
// Test getting existing recipe
retrieved := list.GetRecipe(12345)
if retrieved == nil {
t.Error("Getting existing recipe should not return nil")
}
if retrieved.ID != 12345 {
t.Error("Retrieved recipe should have correct ID")
}
}
func TestMasterRecipeListSize(t *testing.T) {
list := NewMasterRecipeList()
// Test empty list
if list.Size() != 0 {
t.Error("Empty list should have size 0")
}
// Add recipes
for i := 1; i <= 3; i++ {
recipe := NewRecipe()
recipe.ID = int32(i)
recipe.Name = "Test Recipe"
recipe.Level = 50
recipe.Tier = 5
list.AddRecipe(recipe)
}
if list.Size() != 3 {
t.Error("List with 3 recipes should have size 3")
}
}
func TestMasterRecipeListClear(t *testing.T) {
list := NewMasterRecipeList()
// Add some recipes
for i := 1; i <= 3; i++ {
recipe := NewRecipe()
recipe.ID = int32(i)
recipe.Name = "Test Recipe"
recipe.Level = 50
recipe.Tier = 5
list.AddRecipe(recipe)
}
// Clear the list
list.ClearRecipes()
if list.Size() != 0 {
t.Error("Cleared list should have size 0")
}
if list.GetRecipe(1) != nil {
t.Error("Recipe should not exist after clear")
}
}
func TestMasterRecipeListGetRecipesByTier(t *testing.T) {
list := NewMasterRecipeList()
// Add recipes with different tiers
for i := 1; i <= 5; i++ {
recipe := NewRecipe()
recipe.ID = int32(i)
recipe.Name = "Test Recipe"
recipe.Level = int8(i * 10)
recipe.Tier = int8(i)
list.AddRecipe(recipe)
}
// Get recipes for tier 3
tier3Recipes := list.GetRecipesByTier(3)
if len(tier3Recipes) != 1 {
t.Errorf("Expected 1 recipe for tier 3, got %d", len(tier3Recipes))
}
if tier3Recipes[0].Tier != 3 {
t.Error("Recipe should have tier 3")
}
// Get recipes for non-existent tier
emptyTier := list.GetRecipesByTier(99)
if len(emptyTier) != 0 {
t.Error("Non-existent tier should return empty slice")
}
}
func TestMasterRecipeListGetRecipesBySkill(t *testing.T) {
list := NewMasterRecipeList()
// Add recipes with different skills
skillRecipe1 := NewRecipe()
skillRecipe1.ID = 1
skillRecipe1.Name = "Skill Recipe 1"
skillRecipe1.Level = 50
skillRecipe1.Tier = 5
skillRecipe1.Skill = 100
list.AddRecipe(skillRecipe1)
skillRecipe2 := NewRecipe()
skillRecipe2.ID = 2
skillRecipe2.Name = "Skill Recipe 2"
skillRecipe2.Level = 60
skillRecipe2.Tier = 6
skillRecipe2.Skill = 100
list.AddRecipe(skillRecipe2)
differentSkillRecipe := NewRecipe()
differentSkillRecipe.ID = 3
differentSkillRecipe.Name = "Different Skill Recipe"
differentSkillRecipe.Level = 50
differentSkillRecipe.Tier = 5
differentSkillRecipe.Skill = 200
list.AddRecipe(differentSkillRecipe)
// Get recipes for skill 100
skill100Recipes := list.GetRecipesBySkill(100)
if len(skill100Recipes) != 2 {
t.Errorf("Expected 2 recipes for skill 100, got %d", len(skill100Recipes))
}
// Get recipes for skill 200
skill200Recipes := list.GetRecipesBySkill(200)
if len(skill200Recipes) != 1 {
t.Errorf("Expected 1 recipe for skill 200, got %d", len(skill200Recipes))
}
}
// Test PlayerRecipeList operations
func TestNewPlayerRecipeList(t *testing.T) {
playerList := NewPlayerRecipeList()
if playerList == nil {
t.Error("NewPlayerRecipeList should not return nil")
}
if playerList.recipes == nil {
t.Error("Recipes map should be initialized")
}
}
func TestPlayerRecipeListAddRecipe(t *testing.T) {
playerList := NewPlayerRecipeList()
// Test adding nil recipe
if playerList.AddRecipe(nil) {
t.Error("Adding nil recipe should fail")
}
// Test adding valid recipe
recipe := NewRecipe()
recipe.ID = 12345
recipe.Name = "Test Recipe"
recipe.Level = 50
recipe.Tier = 5
if !playerList.AddRecipe(recipe) {
t.Error("Adding valid recipe should succeed")
}
// Test adding duplicate recipe
duplicate := NewRecipe()
duplicate.ID = 12345
duplicate.Name = "Duplicate"
duplicate.Level = 60
if playerList.AddRecipe(duplicate) {
t.Error("Adding duplicate recipe should fail")
}
}
func TestPlayerRecipeListGetRecipe(t *testing.T) {
playerList := NewPlayerRecipeList()
// Test checking non-existent recipe
if playerList.GetRecipe(99999) != nil {
t.Error("Player should not have non-existent recipe")
}
// Add a recipe
recipe := NewRecipe()
recipe.ID = 12345
recipe.Name = "Test Recipe"
recipe.Level = 50
recipe.Tier = 5
playerList.AddRecipe(recipe)
// Test checking existing recipe
if playerList.GetRecipe(12345) == nil {
t.Error("Player should have added recipe")
}
}
func TestPlayerRecipeListSize(t *testing.T) {
playerList := NewPlayerRecipeList()
// Test empty list
if playerList.Size() != 0 {
t.Error("Empty player recipe list should have size 0")
}
// Add recipes
for i := 1; i <= 3; i++ {
recipe := NewRecipe()
recipe.ID = int32(i)
recipe.Name = "Test Recipe"
recipe.Level = 50
recipe.Tier = 5
playerList.AddRecipe(recipe)
}
if playerList.Size() != 3 {
t.Error("Player recipe list with 3 recipes should have size 3")
}
}
// Test Recipe Book List operations
func TestNewMasterRecipeBookList(t *testing.T) {
bookList := NewMasterRecipeBookList()
if bookList == nil {
t.Error("NewMasterRecipeBookList should not return nil")
}
}
func TestMasterRecipeBookListOperations(t *testing.T) {
bookList := NewMasterRecipeBookList()
// Test adding recipe book
recipeBook := NewRecipe()
recipeBook.ID = 5001
recipeBook.BookID = 5001 // Need BookID for recipe books
recipeBook.Name = "Test Recipe Book"
recipeBook.Level = 1
recipeBook.Tier = 1
if !bookList.AddRecipeBook(recipeBook) {
t.Error("Adding valid recipe book should succeed")
}
// Test getting recipe book
retrieved := bookList.GetRecipeBook(5001)
if retrieved == nil {
t.Error("Should retrieve added recipe book")
}
if retrieved.ID != 5001 {
t.Error("Retrieved recipe book should have correct ID")
}
// Test size
if bookList.Size() != 1 {
t.Error("Recipe book list should have size 1")
}
}
func TestNewPlayerRecipeBookList(t *testing.T) {
playerBookList := NewPlayerRecipeBookList()
if playerBookList == nil {
t.Error("NewPlayerRecipeBookList should not return nil")
}
}
// Test RecipeManager operations
func TestNewRecipeManager(t *testing.T) {
manager := NewRecipeManager()
if manager == nil {
t.Error("NewRecipeManager should not return nil")
}
if manager.masterRecipeList == nil {
t.Error("Master recipe list should be initialized")
}
if manager.masterRecipeBookList == nil {
t.Error("Master recipe book list should be initialized")
}
if manager.loadedRecipes == nil {
t.Error("Loaded recipes map should be initialized")
}
if manager.loadedRecipeBooks == nil {
t.Error("Loaded recipe books map should be initialized")
}
}
func TestRecipeManagerGetters(t *testing.T) {
manager := NewRecipeManager()
// Test getting master lists
masterList := manager.GetMasterRecipeList()
if masterList == nil {
t.Error("Should return master recipe list")
}
masterBookList := manager.GetMasterRecipeBookList()
if masterBookList == nil {
t.Error("Should return master recipe book list")
}
// Test getting non-existent recipe
recipe := manager.GetRecipe(99999)
if recipe != nil {
t.Error("Non-existent recipe should return nil")
}
// Test getting non-existent recipe book
book := manager.GetRecipeBook(99999)
if book != nil {
t.Error("Non-existent recipe book should return nil")
}
}
func TestRecipeManagerStatistics(t *testing.T) {
manager := NewRecipeManager()
// Test getting statistics when enabled
manager.GetStatistics() // Call method without assignment to avoid lock copy
// Test disabling statistics
manager.SetStatisticsEnabled(false)
manager.GetStatistics() // Call method without assignment to avoid lock copy
// Test enabling statistics
manager.SetStatisticsEnabled(true)
}
func TestRecipeManagerValidation(t *testing.T) {
manager := NewRecipeManager()
// Test validation of empty manager
issues := manager.Validate()
if len(issues) > 0 {
// Manager validation might find issues, that's fine
}
}
func TestRecipeManagerSize(t *testing.T) {
manager := NewRecipeManager()
// Test size of empty manager
recipes, books := manager.Size()
if recipes != 0 {
t.Error("Empty manager should have 0 recipes")
}
if books != 0 {
t.Error("Empty manager should have 0 recipe books")
}
}
// Test RecipeComponent operations
func TestRecipeComponent(t *testing.T) {
component := &RecipeComponent{
ItemID: 1001,
Slot: 0,
}
if component.ItemID != 1001 {
t.Error("Component item ID should be set correctly")
}
if component.Slot != 0 {
t.Error("Component slot should be set correctly")
}
}
// Test RecipeProducts operations
func TestRecipeProducts(t *testing.T) {
products := &RecipeProducts{
ProductID: 2001,
ProductQty: 1,
ByproductID: 2002,
ByproductQty: 2,
}
if products.ProductID != 2001 {
t.Error("Product ID should be set correctly")
}
if products.ProductQty != 1 {
t.Error("Product quantity should be set correctly")
}
if products.ByproductID != 2002 {
t.Error("Byproduct ID should be set correctly")
}
if products.ByproductQty != 2 {
t.Error("Byproduct quantity should be set correctly")
}
}
// Test Statistics operations
func TestNewStatistics(t *testing.T) {
stats := NewStatistics()
if stats == nil {
t.Error("NewStatistics should not return nil")
}
if stats.RecipesByTier == nil {
t.Error("RecipesByTier map should be initialized")
}
if stats.RecipesBySkill == nil {
t.Error("RecipesBySkill map should be initialized")
}
}
func TestStatisticsOperations(t *testing.T) {
stats := NewStatistics()
// Test incrementing lookups
initialLookups := stats.RecipeLookups
stats.IncrementRecipeLookups()
if stats.RecipeLookups != initialLookups+1 {
t.Error("Recipe lookups should be incremented")
}
// Test incrementing recipe book lookups
initialBookLookups := stats.RecipeBookLookups
stats.IncrementRecipeBookLookups()
if stats.RecipeBookLookups != initialBookLookups+1 {
t.Error("Recipe book lookups should be incremented")
}
// Test incrementing player recipe loads
initialLoads := stats.PlayerRecipeLoads
stats.IncrementPlayerRecipeLoads()
if stats.PlayerRecipeLoads != initialLoads+1 {
t.Error("Player recipe loads should be incremented")
}
// Test incrementing component queries
initialQueries := stats.ComponentQueries
stats.IncrementComponentQueries()
if stats.ComponentQueries != initialQueries+1 {
t.Error("Component queries should be incremented")
}
// Test getting snapshot
snapshot := stats.GetSnapshot()
if snapshot.RecipeLookups != stats.RecipeLookups {
t.Error("Snapshot should match current stats")
}
}
// Test error constants
func TestErrorConstants(t *testing.T) {
if ErrRecipeNotFound == nil {
t.Error("ErrRecipeNotFound should be defined")
}
if ErrRecipeBookNotFound == nil {
t.Error("ErrRecipeBookNotFound should be defined")
}
if ErrInvalidRecipeID == nil {
t.Error("ErrInvalidRecipeID should be defined")
}
if ErrDuplicateRecipe == nil {
t.Error("ErrDuplicateRecipe should be defined")
}
}
// Test constants
func TestConstants(t *testing.T) {
// Test slot constants
if SlotPrimary != 0 {
t.Error("SlotPrimary should be 0")
}
if SlotBuild1 != 1 {
t.Error("SlotBuild1 should be 1")
}
if SlotFuel != 5 {
t.Error("SlotFuel should be 5")
}
// Test stage constants
if Stage0 != 0 {
t.Error("Stage0 should be 0")
}
if Stage4 != 4 {
t.Error("Stage4 should be 4")
}
// Test validation constants
if MinRecipeID != 1 {
t.Error("MinRecipeID should be 1")
}
if MaxRecipeLevel != 100 {
t.Error("MaxRecipeLevel should be 100")
}
}
// Edge case tests
func TestRecipeEdgeCases(t *testing.T) {
recipe := NewRecipe()
// Test accessing non-existent component slots
components := recipe.GetComponentsBySlot(99)
if len(components) != 0 {
t.Error("Non-existent slot should return empty slice")
}
// Test accessing non-existent product stages
products := recipe.GetProductsForStage(99)
if products != nil {
t.Error("Non-existent stage should return nil")
}
// Test recipe with maximum values
recipe.ID = MaxRecipeID
recipe.Level = MaxRecipeLevel
recipe.Tier = MaxTier
recipe.Name = "Max Recipe"
if !recipe.IsValid() {
t.Error("Recipe with maximum valid values should be valid")
}
}
func TestMasterRecipeListEdgeCases(t *testing.T) {
list := NewMasterRecipeList()
// Test operations on empty list
emptyTier := list.GetRecipesByTier(1)
if len(emptyTier) != 0 {
t.Error("Empty list should return empty slice for tier query")
}
emptySkill := list.GetRecipesBySkill(100)
if len(emptySkill) != 0 {
t.Error("Empty list should return empty slice for skill query")
}
// Test clearing already empty list
list.ClearRecipes()
if list.Size() != 0 {
t.Error("Clearing empty list should keep size 0")
}
}
// Benchmark tests
func BenchmarkNewRecipe(b *testing.B) {
for i := 0; i < b.N; i++ {
NewRecipe()
}
}
func BenchmarkRecipeCopy(b *testing.B) {
source := NewRecipe()
source.ID = 12345
source.Name = "Benchmark Recipe"
source.Level = 50
source.Tier = 5
source.Components[0] = []int32{1001, 1002, 1003}
b.ResetTimer()
for i := 0; i < b.N; i++ {
NewRecipeFromRecipe(source)
}
}
func BenchmarkMasterRecipeListAdd(b *testing.B) {
list := NewMasterRecipeList()
b.ResetTimer()
for i := 0; i < b.N; i++ {
recipe := NewRecipe()
recipe.ID = int32(i)
recipe.Name = "Benchmark Recipe"
recipe.Level = 50
recipe.Tier = 5
list.AddRecipe(recipe)
}
}
func BenchmarkMasterRecipeListGet(b *testing.B) {
list := NewMasterRecipeList()
// Add test recipes
for i := 1; i <= 1000; i++ {
recipe := NewRecipe()
recipe.ID = int32(i)
recipe.Name = "Benchmark Recipe"
recipe.Level = 50
recipe.Tier = 5
list.AddRecipe(recipe)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
list.GetRecipe(int32((i % 1000) + 1))
}
}
func BenchmarkPlayerRecipeListOperations(b *testing.B) {
playerList := NewPlayerRecipeList()
b.ResetTimer()
for i := 0; i < b.N; i++ {
recipe := NewRecipe()
recipe.ID = int32(i)
recipe.Name = "Benchmark Recipe"
recipe.Level = 50
recipe.Tier = 5
playerList.AddRecipe(recipe)
playerList.GetRecipe(int32(i))
}
}