1449 lines
51 KiB
Go
1449 lines
51 KiB
Go
package character
|
|
|
|
import (
|
|
"bamort/database"
|
|
"bamort/gsmaster"
|
|
"bamort/models"
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestImprovedSkillCostAPI(t *testing.T) {
|
|
// Setup test database
|
|
database.SetupTestDB()
|
|
defer database.ResetTestDB()
|
|
|
|
// Migrate the schema
|
|
err := models.MigrateStructure()
|
|
assert.NoError(t, err)
|
|
|
|
// Create test skill data
|
|
err = createTestSkillData()
|
|
assert.NoError(t, err)
|
|
defer cleanupTestSkillData()
|
|
|
|
// Create test character
|
|
testChar := createChar()
|
|
testChar.ID = 1 // Set the ID to match our test requests
|
|
err = testChar.Create()
|
|
assert.NoError(t, err)
|
|
|
|
// Setup Gin in test mode
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
// Create test cases
|
|
testCases := []struct {
|
|
name string
|
|
request SkillCostRequest
|
|
expectedStatus int
|
|
description string
|
|
}{
|
|
{
|
|
name: "Learn new skill",
|
|
request: SkillCostRequest{
|
|
Name: "Menschenkenntnis",
|
|
Type: "skill",
|
|
Action: "learn",
|
|
},
|
|
expectedStatus: http.StatusOK,
|
|
description: "Should calculate costs for learning a new skill",
|
|
},
|
|
{
|
|
name: "Improve existing skill",
|
|
request: SkillCostRequest{
|
|
Name: "Menschenkenntnis",
|
|
Type: "skill",
|
|
Action: "improve",
|
|
CurrentLevel: 10,
|
|
},
|
|
expectedStatus: http.StatusOK,
|
|
description: "Should calculate costs for improving an existing skill",
|
|
},
|
|
{
|
|
name: "Multi-level improvement",
|
|
request: SkillCostRequest{
|
|
Name: "Menschenkenntnis",
|
|
Type: "skill",
|
|
Action: "improve",
|
|
CurrentLevel: 10,
|
|
TargetLevel: 13,
|
|
},
|
|
expectedStatus: http.StatusOK,
|
|
description: "Should calculate costs for multi-level improvement",
|
|
},
|
|
{
|
|
name: "Invalid request - missing name",
|
|
request: SkillCostRequest{
|
|
Type: "skill",
|
|
Action: "learn",
|
|
},
|
|
expectedStatus: http.StatusBadRequest,
|
|
description: "Should return error for missing skill name",
|
|
},
|
|
{
|
|
name: "Invalid request - invalid type",
|
|
request: SkillCostRequest{
|
|
Name: "Test",
|
|
Type: "invalid",
|
|
Action: "learn",
|
|
},
|
|
expectedStatus: http.StatusBadRequest,
|
|
description: "Should return error for invalid skill type",
|
|
},
|
|
{
|
|
name: "Invalid request - invalid action",
|
|
request: SkillCostRequest{
|
|
Name: "Test",
|
|
Type: "skill",
|
|
Action: "invalid",
|
|
},
|
|
expectedStatus: http.StatusBadRequest,
|
|
description: "Should return error for invalid action",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
// Create request body
|
|
requestBody, _ := json.Marshal(tc.request)
|
|
|
|
// Create HTTP request
|
|
req, _ := http.NewRequest("POST", "/api/characters/1/skill-cost", bytes.NewBuffer(requestBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create response recorder
|
|
w := httptest.NewRecorder()
|
|
|
|
// Create Gin context
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
c.Params = []gin.Param{{Key: "id", Value: "1"}}
|
|
|
|
// Note: This test would need a proper database setup to work fully
|
|
// For now, we're just testing the request parsing and validation
|
|
|
|
fmt.Printf("Test: %s\n", tc.description)
|
|
fmt.Printf("Request: %+v\n", tc.request)
|
|
fmt.Printf("Expected Status: %d\n", tc.expectedStatus)
|
|
|
|
// Call the actual handler function
|
|
GetSkillCost(c)
|
|
|
|
// Check the response status
|
|
assert.Equal(t, tc.expectedStatus, w.Code, "Status code should match expected")
|
|
|
|
// If successful, validate response structure
|
|
if w.Code == http.StatusOK {
|
|
var response map[string]interface{}
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Response should be valid JSON")
|
|
|
|
// Check for expected fields in successful responses
|
|
if tc.request.TargetLevel > 0 {
|
|
// Multi-level response
|
|
assert.Contains(t, response, "LevelCosts", "Multi-level response should contain LevelCosts")
|
|
} else {
|
|
// Single-level response
|
|
assert.Contains(t, response, "SkillName", "Response should contain SkillName")
|
|
assert.Contains(t, response, "SkillType", "Response should contain SkillType")
|
|
assert.Contains(t, response, "Action", "Response should contain Action")
|
|
}
|
|
}
|
|
|
|
// Validate request structure
|
|
var parsedRequest SkillCostRequest
|
|
err := json.Unmarshal(requestBody, &parsedRequest)
|
|
assert.NoError(t, err, "Request should be valid JSON")
|
|
|
|
// Test validation logic
|
|
if tc.request.Name == "" {
|
|
assert.Empty(t, parsedRequest.Name, "Name should be empty when not provided")
|
|
}
|
|
|
|
if tc.request.Type != "" {
|
|
assert.Equal(t, tc.request.Type, parsedRequest.Type, "Type should match")
|
|
}
|
|
|
|
if tc.request.Action != "" {
|
|
assert.Equal(t, tc.request.Action, parsedRequest.Action, "Action should match")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test the response structures
|
|
func TestSkillCostResponseStructures(t *testing.T) {
|
|
t.Run("SkillCostResponse structure", func(t *testing.T) {
|
|
response := SkillCostResponse{
|
|
SkillName: "Menschenkenntnis",
|
|
SkillType: "skill",
|
|
Action: "learn",
|
|
CharacterID: 1,
|
|
CurrentLevel: 0,
|
|
TargetLevel: 0,
|
|
Category: "Sozial",
|
|
Difficulty: "schwer",
|
|
CanAfford: true,
|
|
Notes: "Neue Fertigkeit erlernen",
|
|
}
|
|
|
|
// Test JSON marshaling
|
|
jsonData, err := json.Marshal(response)
|
|
assert.NoError(t, err, "Response should be marshallable to JSON")
|
|
|
|
// Test JSON unmarshaling
|
|
var parsedResponse SkillCostResponse
|
|
err = json.Unmarshal(jsonData, &parsedResponse)
|
|
assert.NoError(t, err, "Response should be unmarshallable from JSON")
|
|
|
|
assert.Equal(t, response.SkillName, parsedResponse.SkillName, "Skill name should match")
|
|
assert.Equal(t, response.SkillType, parsedResponse.SkillType, "Skill type should match")
|
|
assert.Equal(t, response.Action, parsedResponse.Action, "Action should match")
|
|
assert.Equal(t, response.CharacterID, parsedResponse.CharacterID, "Character ID should match")
|
|
assert.Equal(t, response.Category, parsedResponse.Category, "Category should match")
|
|
assert.Equal(t, response.Difficulty, parsedResponse.Difficulty, "Difficulty should match")
|
|
assert.Equal(t, response.CanAfford, parsedResponse.CanAfford, "Can afford should match")
|
|
assert.Equal(t, response.Notes, parsedResponse.Notes, "Notes should match")
|
|
})
|
|
|
|
t.Run("MultiLevelCostResponse structure", func(t *testing.T) {
|
|
response := MultiLevelCostResponse{
|
|
SkillName: "Menschenkenntnis",
|
|
SkillType: "skill",
|
|
CharacterID: 1,
|
|
CurrentLevel: 10,
|
|
TargetLevel: 13,
|
|
LevelCosts: []SkillCostResponse{},
|
|
CanAffordTotal: true,
|
|
}
|
|
|
|
// Test JSON marshaling
|
|
jsonData, err := json.Marshal(response)
|
|
assert.NoError(t, err, "MultiLevelCostResponse should be marshallable to JSON")
|
|
|
|
// Test JSON unmarshaling
|
|
var parsedResponse MultiLevelCostResponse
|
|
err = json.Unmarshal(jsonData, &parsedResponse)
|
|
assert.NoError(t, err, "MultiLevelCostResponse should be unmarshallable from JSON")
|
|
|
|
assert.Equal(t, response.SkillName, parsedResponse.SkillName, "Skill name should match")
|
|
assert.Equal(t, response.SkillType, parsedResponse.SkillType, "Skill type should match")
|
|
assert.Equal(t, response.CharacterID, parsedResponse.CharacterID, "Character ID should match")
|
|
assert.Equal(t, response.CurrentLevel, parsedResponse.CurrentLevel, "Current level should match")
|
|
assert.Equal(t, response.TargetLevel, parsedResponse.TargetLevel, "Target level should match")
|
|
assert.Equal(t, response.CanAffordTotal, parsedResponse.CanAffordTotal, "Can afford total should match")
|
|
})
|
|
}
|
|
|
|
// Test helper functions
|
|
func TestHelperFunctions(t *testing.T) {
|
|
t.Run("getCurrentSkillLevel", func(t *testing.T) {
|
|
// This would need a proper character setup to test fully
|
|
// For now, we're just testing the function exists and doesn't panic
|
|
|
|
var character models.Char
|
|
level := getCurrentSkillLevel(&character, "Test", "skill")
|
|
assert.Equal(t, -1, level, "Should return -1 for non-existent skill")
|
|
})
|
|
}
|
|
|
|
// Test integration with gsmaster exported functions
|
|
func TestGSMasterIntegration(t *testing.T) {
|
|
t.Run("GetDefaultCategory integration", func(t *testing.T) {
|
|
// Test that we can access the exported function from gsmaster
|
|
category := gsmaster.GetDefaultCategoryOld("Menschenkenntnis")
|
|
assert.Equal(t, "Sozial", category, "Should return correct category for Menschenkenntnis")
|
|
|
|
category = gsmaster.GetDefaultCategoryOld("Stichwaffen")
|
|
assert.Equal(t, "Waffen", category, "Should return correct category for Stichwaffen")
|
|
|
|
// Test fallback for unknown skill
|
|
category = gsmaster.GetDefaultCategoryOld("NonExistentSkill")
|
|
assert.Equal(t, "Alltag", category, "Should return default category for unknown skill")
|
|
})
|
|
|
|
t.Run("GetDefaultDifficulty integration", func(t *testing.T) {
|
|
// Test that we can access the exported function from gsmaster
|
|
difficulty := gsmaster.GetDefaultDifficultyOld("Menschenkenntnis")
|
|
assert.Equal(t, "schwer", difficulty, "Should return correct difficulty for Menschenkenntnis")
|
|
|
|
difficulty = gsmaster.GetDefaultDifficultyOld("Stichwaffen")
|
|
assert.Equal(t, "leicht", difficulty, "Should return correct difficulty for Stichwaffen")
|
|
|
|
// Test fallback for unknown skill
|
|
difficulty = gsmaster.GetDefaultDifficultyOld("NonExistentSkill")
|
|
assert.Equal(t, "normal", difficulty, "Should return default difficulty for unknown skill")
|
|
})
|
|
|
|
t.Run("Reward system structures", func(t *testing.T) {
|
|
// Test RewardOptions structure
|
|
rewards := RewardOptions{
|
|
Type: "free_learning",
|
|
UseGoldForEP: true,
|
|
MaxGoldEP: 50,
|
|
}
|
|
|
|
// Test JSON marshaling
|
|
jsonData, err := json.Marshal(rewards)
|
|
assert.NoError(t, err, "RewardOptions should be marshallable to JSON")
|
|
|
|
// Test JSON unmarshaling
|
|
var parsedRewards RewardOptions
|
|
err = json.Unmarshal(jsonData, &parsedRewards)
|
|
assert.NoError(t, err, "RewardOptions should be unmarshallable from JSON")
|
|
|
|
assert.Equal(t, rewards.Type, parsedRewards.Type, "Type should match")
|
|
assert.Equal(t, rewards.UseGoldForEP, parsedRewards.UseGoldForEP, "UseGoldForEP should match")
|
|
assert.Equal(t, rewards.MaxGoldEP, parsedRewards.MaxGoldEP, "MaxGoldEP should match")
|
|
|
|
// Test validation of reward types
|
|
validTypes := []string{"free_learning", "free_spell_learning", "half_ep_improvement", "gold_for_ep"}
|
|
for _, validType := range validTypes {
|
|
rewards.Type = validType
|
|
_, err := json.Marshal(rewards)
|
|
assert.NoError(t, err, fmt.Sprintf("Should marshal valid type: %s", validType))
|
|
}
|
|
})
|
|
|
|
t.Run("Reward system integration with gsmaster functions", func(t *testing.T) {
|
|
// Test that the reward system works with the exported gsmaster functions
|
|
// This simulates the flow where we get skill info from gsmaster and apply rewards
|
|
|
|
skillName := "Menschenkenntnis"
|
|
|
|
// Get skill info using exported functions
|
|
category := gsmaster.GetDefaultCategoryOld(skillName)
|
|
difficulty := gsmaster.GetDefaultDifficultyOld(skillName)
|
|
|
|
assert.Equal(t, "Sozial", category, "Should get correct category from gsmaster")
|
|
assert.Equal(t, "schwer", difficulty, "Should get correct difficulty from gsmaster")
|
|
|
|
// Test reward structure that would be used in the actual API
|
|
rewards := RewardOptions{
|
|
Type: "half_ep_improvement",
|
|
UseGoldForEP: false,
|
|
MaxGoldEP: 0,
|
|
}
|
|
|
|
// Test that structure is valid
|
|
jsonData, err := json.Marshal(rewards)
|
|
assert.NoError(t, err, "Reward options should marshal correctly")
|
|
|
|
var parsedRewards RewardOptions
|
|
err = json.Unmarshal(jsonData, &parsedRewards)
|
|
assert.NoError(t, err, "Reward options should unmarshal correctly")
|
|
assert.Equal(t, "half_ep_improvement", parsedRewards.Type, "Reward type should be preserved")
|
|
})
|
|
}
|
|
|
|
// Test GetLernCost endpoint specifically with gsmaster.LernCostRequest structure
|
|
func TestGetLernCostEndpoint(t *testing.T) {
|
|
// Setup test database
|
|
database.SetupTestDB(true, true)
|
|
defer database.ResetTestDB()
|
|
|
|
// Migrate the schema
|
|
err := models.MigrateStructure()
|
|
assert.NoError(t, err)
|
|
|
|
// Setup Gin in test mode
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
t.Run("GetLernCost with Athletik for Krieger character", func(t *testing.T) {
|
|
// Create request body using gsmaster.LernCostRequest structure
|
|
requestData := gsmaster.LernCostRequest{
|
|
CharId: 20, // CharacterID = 20
|
|
Name: "Athletik", // SkillName = Athletik
|
|
CurrentLevel: 9, // CurrentLevel = 9
|
|
Type: "skill", // Type = skill
|
|
Action: "improve", // Action = improve (since we have current level)
|
|
TargetLevel: 0, // TargetLevel = 0 (will calculate up to level 18)
|
|
UsePP: 0, // No practice points used
|
|
UseGold: 0,
|
|
Reward: &[]string{"default"}[0], // Default reward type
|
|
}
|
|
requestBody, _ := json.Marshal(requestData)
|
|
|
|
// Create HTTP request
|
|
req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create response recorder
|
|
w := httptest.NewRecorder()
|
|
|
|
// Create Gin context
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
c.Params = []gin.Param{{Key: "id", Value: "20"}}
|
|
|
|
fmt.Printf("Test: GetLernCost for Athletik improvement for Krieger character ID 20\n")
|
|
fmt.Printf("Request: CharId=%d, SkillName=%s, CurrentLevel=%d, TargetLevel=%d\n",
|
|
requestData.CharId, requestData.Name, requestData.CurrentLevel, requestData.TargetLevel)
|
|
|
|
// Call the actual handler function
|
|
GetLernCost(c)
|
|
|
|
// Print the actual response to see what we get
|
|
fmt.Printf("Response Status: %d\n", w.Code)
|
|
fmt.Printf("Response Body: %s\n", w.Body.String())
|
|
|
|
// Check if we got an error response first
|
|
if w.Code != http.StatusOK {
|
|
var errorResponse map[string]interface{}
|
|
err := json.Unmarshal(w.Body.Bytes(), &errorResponse)
|
|
if err == nil {
|
|
fmt.Printf("Error Response: %+v\n", errorResponse)
|
|
}
|
|
assert.Fail(t, "Expected successful response but got error: %s", w.Body.String())
|
|
return
|
|
}
|
|
|
|
// Parse and validate response for success case
|
|
var response []gsmaster.SkillCostResultNew
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Response should be valid JSON array of SkillCostResultNew")
|
|
|
|
// Should have costs for levels 10, 11, 12, ... up to 18 (from current level 9)
|
|
assert.Greater(t, len(response), 0, "Should return learning costs for multiple levels")
|
|
assert.LessOrEqual(t, len(response), 9, "Should not return more than 9 levels (10-18)")
|
|
|
|
// Validate the first entry (level 10)
|
|
if len(response) > 0 {
|
|
firstResult := response[0]
|
|
assert.Equal(t, "20", firstResult.CharacterID, "Character ID should match")
|
|
assert.Equal(t, "Athletik", firstResult.SkillName, "Skill name should match")
|
|
assert.Equal(t, 10, firstResult.TargetLevel, "First target level should be 10")
|
|
|
|
// Character class should be "Kr" (abbreviation for "Krieger")
|
|
assert.Equal(t, "Kr", firstResult.CharacterClass, "Character class should be abbreviated to 'Kr'")
|
|
|
|
// Should have valid costs
|
|
assert.Greater(t, firstResult.EP, 0, "EP cost should be greater than 0")
|
|
assert.GreaterOrEqual(t, firstResult.GoldCost, 0, "Gold cost should be 0 or greater")
|
|
|
|
fmt.Printf("Level 10 cost: EP=%d, GoldCost=%d, LE=%d\n",
|
|
firstResult.EP, firstResult.GoldCost, firstResult.LE)
|
|
fmt.Printf("Category=%s, Difficulty=%s\n",
|
|
firstResult.Category, firstResult.Difficulty)
|
|
}
|
|
|
|
// Find cost for level 12 specifically to test mid-range
|
|
var level12Cost *gsmaster.SkillCostResultNew
|
|
for i := range response {
|
|
if response[i].TargetLevel == 12 {
|
|
level12Cost = &response[i]
|
|
break
|
|
}
|
|
}
|
|
|
|
if level12Cost != nil {
|
|
assert.Equal(t, 12, level12Cost.TargetLevel, "Target level should be 12")
|
|
assert.Greater(t, level12Cost.EP, 0, "EP cost should be greater than 0 for level 12")
|
|
|
|
fmt.Printf("Level 12 cost: EP=%d, GoldCost=%d, LE=%d\n",
|
|
level12Cost.EP, level12Cost.GoldCost, level12Cost.LE)
|
|
} else {
|
|
fmt.Printf("No cost found for level 12. Available levels: ")
|
|
for _, cost := range response {
|
|
fmt.Printf("%d ", cost.TargetLevel)
|
|
}
|
|
fmt.Println()
|
|
}
|
|
|
|
// Verify all target levels are sequential and start from current level + 1
|
|
expectedLevel := 10 // Current level 9 + 1
|
|
for _, cost := range response {
|
|
assert.Equal(t, expectedLevel, cost.TargetLevel,
|
|
"Target levels should be sequential starting from %d", expectedLevel)
|
|
assert.Equal(t, "Athletik", cost.SkillName, "All entries should have correct skill name")
|
|
assert.Equal(t, "Kr", cost.CharacterClass, "All entries should have correct character class")
|
|
expectedLevel++
|
|
}
|
|
})
|
|
|
|
t.Run("GetLernCost Athletik - Detailed Cost Analysis for Each Level", func(t *testing.T) {
|
|
requestData := gsmaster.LernCostRequest{
|
|
CharId: 20,
|
|
Name: "Athletik",
|
|
CurrentLevel: 9,
|
|
Type: "skill",
|
|
Action: "improve",
|
|
TargetLevel: 0, // Calculate all levels
|
|
UsePP: 0,
|
|
UseGold: 0,
|
|
Reward: &[]string{"default"}[0],
|
|
}
|
|
requestBody, _ := json.Marshal(requestData)
|
|
|
|
req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
|
|
GetLernCost(c)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code, "Request should succeed")
|
|
|
|
var response []gsmaster.SkillCostResultNew
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Response should be valid JSON")
|
|
|
|
fmt.Printf("\n=== Detailed Cost Analysis for Athletik (Levels 10-18) ===\n")
|
|
fmt.Printf("Level | EP Cost | Gold Cost | LE Cost | PP Used | Gold Used\n")
|
|
fmt.Printf("------|---------|-----------|---------|---------|----------\n")
|
|
|
|
for _, cost := range response {
|
|
fmt.Printf("%5d | %7d | %9d | %7d | %7d | %9d\n",
|
|
cost.TargetLevel, cost.EP, cost.GoldCost, cost.LE, cost.PPUsed, cost.GoldUsed)
|
|
|
|
// Validate each level's costs
|
|
assert.Greater(t, cost.EP, 0, "EP cost should be positive for level %d", cost.TargetLevel)
|
|
assert.GreaterOrEqual(t, cost.GoldCost, 0, "Gold cost should be non-negative for level %d", cost.TargetLevel)
|
|
assert.GreaterOrEqual(t, cost.LE, 0, "LE cost should be non-negative for level %d", cost.TargetLevel)
|
|
assert.Equal(t, 0, cost.PPUsed, "PP Used should be 0 when UsePP=0 for level %d", cost.TargetLevel)
|
|
assert.Equal(t, 0, cost.GoldUsed, "Gold Used should be 0 when UseGold=0 for level %d", cost.TargetLevel)
|
|
|
|
// Verify cost progression (higher levels should generally cost more)
|
|
if cost.TargetLevel > 10 {
|
|
prevLevel := cost.TargetLevel - 1
|
|
var prevCost *gsmaster.SkillCostResultNew
|
|
for i := range response {
|
|
if response[i].TargetLevel == prevLevel {
|
|
prevCost = &response[i]
|
|
break
|
|
}
|
|
}
|
|
if prevCost != nil {
|
|
assert.GreaterOrEqual(t, cost.EP, prevCost.EP,
|
|
"EP cost should not decrease from level %d to %d", prevLevel, cost.TargetLevel)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("GetLernCost Athletik - With Practice Points Usage", func(t *testing.T) {
|
|
testCases := []struct {
|
|
usePP int
|
|
description string
|
|
}{
|
|
{5, "Using 5 Practice Points"},
|
|
{10, "Using 10 Practice Points"},
|
|
{20, "Using 20 Practice Points"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.description, func(t *testing.T) {
|
|
requestData := gsmaster.LernCostRequest{
|
|
CharId: 20,
|
|
Name: "Athletik",
|
|
CurrentLevel: 9,
|
|
Type: "skill",
|
|
Action: "improve",
|
|
TargetLevel: 0,
|
|
UsePP: tc.usePP,
|
|
UseGold: 0,
|
|
Reward: &[]string{"default"}[0],
|
|
}
|
|
requestBody, _ := json.Marshal(requestData)
|
|
|
|
req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
|
|
GetLernCost(c)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code, "Request should succeed for UsePP=%d", tc.usePP)
|
|
|
|
var response []gsmaster.SkillCostResultNew
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Response should be valid JSON")
|
|
|
|
fmt.Printf("\n=== Cost Analysis with %d Practice Points ===\n", tc.usePP)
|
|
fmt.Printf("Level | EP Cost | Gold Cost | LE Cost | PP Used | Gold Used\n")
|
|
fmt.Printf("------|---------|-----------|---------|---------|----------\n")
|
|
|
|
for i, cost := range response {
|
|
fmt.Printf("%5d | %7d | %9d | %7d | %7d | %9d\n",
|
|
cost.TargetLevel, cost.EP, cost.GoldCost, cost.LE, cost.PPUsed, cost.GoldUsed)
|
|
|
|
// Simple validation: PP should be reasonable and Gold should be 0
|
|
assert.LessOrEqual(t, cost.PPUsed, 50, "PP Used should be reasonable for level %d", cost.TargetLevel)
|
|
assert.Equal(t, 0, cost.GoldUsed, "Gold Used should be 0 when UseGold=0 for level %d", cost.TargetLevel)
|
|
|
|
// EP cost should be non-negative
|
|
assert.GreaterOrEqual(t, cost.EP, 0, "EP cost should be non-negative for level %d", cost.TargetLevel)
|
|
|
|
// When enough PP are available, early levels should have 0 EP cost
|
|
if i == 0 && tc.usePP >= 2 {
|
|
assert.Equal(t, 0, cost.EP, "Level 10 should have 0 EP cost when enough PP available")
|
|
}
|
|
|
|
// EP cost validation
|
|
if cost.PPUsed > 0 {
|
|
// When PP are used, EP should be reduced or zero
|
|
assert.GreaterOrEqual(t, cost.EP, 0, "EP cost should be non-negative for level %d", cost.TargetLevel)
|
|
} else {
|
|
// When no PP are used, EP should be positive
|
|
assert.Greater(t, cost.EP, 0, "EP cost should be positive when no PP used for level %d", cost.TargetLevel)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("GetLernCost Athletik - With Gold Usage", func(t *testing.T) {
|
|
testCases := []struct {
|
|
useGold int
|
|
description string
|
|
}{
|
|
{50, "Using 50 Gold"},
|
|
{100, "Using 100 Gold"},
|
|
{200, "Using 200 Gold"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.description, func(t *testing.T) {
|
|
requestData := gsmaster.LernCostRequest{
|
|
CharId: 20,
|
|
Name: "Athletik",
|
|
CurrentLevel: 9,
|
|
Type: "skill",
|
|
Action: "improve",
|
|
TargetLevel: 0,
|
|
UsePP: 0,
|
|
UseGold: tc.useGold,
|
|
Reward: &[]string{"default"}[0],
|
|
}
|
|
requestBody, _ := json.Marshal(requestData)
|
|
|
|
req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
|
|
GetLernCost(c)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code, "Request should succeed for UseGold=%d", tc.useGold)
|
|
|
|
var response []gsmaster.SkillCostResultNew
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Response should be valid JSON")
|
|
|
|
fmt.Printf("\n=== Cost Analysis with %d Gold ===\n", tc.useGold)
|
|
fmt.Printf("Level | EP Cost | Gold Cost | LE Cost | PP Used | Gold Used\n")
|
|
fmt.Printf("------|---------|-----------|---------|---------|----------\n")
|
|
|
|
for i, cost := range response {
|
|
fmt.Printf("%5d | %7d | %9d | %7d | %7d | %9d\n",
|
|
cost.TargetLevel, cost.EP, cost.GoldCost, cost.LE, cost.PPUsed, cost.GoldUsed)
|
|
|
|
// Validate Gold usage based on EP needs and cumulative usage
|
|
remainingGold := tc.useGold
|
|
|
|
// Calculate cumulative Gold usage for previous levels
|
|
for j := 0; j < i; j++ {
|
|
if j < len(response) {
|
|
remainingGold -= response[j].GoldUsed
|
|
}
|
|
}
|
|
|
|
// Current level's expected Gold usage
|
|
epCostWithoutGold := cost.EP + (cost.GoldUsed / 10) // Reverse calculate original EP
|
|
maxGoldUsable := epCostWithoutGold * 10 // Max gold that can be used (10 gold = 1 EP)
|
|
|
|
expectedGoldUsed := 0
|
|
if remainingGold > 0 {
|
|
if remainingGold >= maxGoldUsable {
|
|
expectedGoldUsed = maxGoldUsable
|
|
} else {
|
|
expectedGoldUsed = remainingGold
|
|
}
|
|
}
|
|
|
|
assert.Equal(t, expectedGoldUsed, cost.GoldUsed, "Gold Used should match calculated value for level %d (remaining Gold: %d, max usable: %d)", cost.TargetLevel, remainingGold, maxGoldUsable)
|
|
assert.Equal(t, 0, cost.PPUsed, "PP Used should be 0 when UsePP=0 for level %d", cost.TargetLevel)
|
|
|
|
// EP cost validation
|
|
assert.GreaterOrEqual(t, cost.EP, 0, "EP cost should be non-negative for level %d", cost.TargetLevel)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("GetLernCost Athletik - Combined PP and Gold Usage", func(t *testing.T) {
|
|
testCases := []struct {
|
|
usePP int
|
|
useGold int
|
|
description string
|
|
}{
|
|
{10, 50, "Using 10 PP and 50 Gold"},
|
|
{15, 100, "Using 15 PP and 100 Gold"},
|
|
{25, 200, "Using 25 PP and 200 Gold"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.description, func(t *testing.T) {
|
|
requestData := gsmaster.LernCostRequest{
|
|
CharId: 20,
|
|
Name: "Athletik",
|
|
CurrentLevel: 9,
|
|
Type: "skill",
|
|
Action: "improve",
|
|
TargetLevel: 0,
|
|
UsePP: tc.usePP,
|
|
UseGold: tc.useGold,
|
|
Reward: &[]string{"default"}[0],
|
|
}
|
|
requestBody, _ := json.Marshal(requestData)
|
|
|
|
req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
|
|
GetLernCost(c)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code, "Request should succeed for UsePP=%d, UseGold=%d", tc.usePP, tc.useGold)
|
|
|
|
var response []gsmaster.SkillCostResultNew
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Response should be valid JSON")
|
|
|
|
fmt.Printf("\n=== Cost Analysis with %d PP and %d Gold ===\n", tc.usePP, tc.useGold)
|
|
fmt.Printf("Level | EP Cost | Gold Cost | LE Cost | PP Used | Gold Used\n")
|
|
fmt.Printf("------|---------|-----------|---------|---------|----------\n")
|
|
|
|
for _, cost := range response {
|
|
fmt.Printf("%5d | %7d | %9d | %7d | %7d | %9d\n",
|
|
cost.TargetLevel, cost.EP, cost.GoldCost, cost.LE, cost.PPUsed, cost.GoldUsed)
|
|
|
|
// Calculate original TE needed (LE + PP Used = original TE)
|
|
teNeeded := cost.LE + cost.PPUsed
|
|
|
|
// Calculate original EP before gold usage (EP + Gold/10 = original EP)
|
|
epAfterPP := cost.EP + (cost.GoldUsed / 10)
|
|
maxGoldUsable := epAfterPP * 10
|
|
|
|
// Validate that resources are used reasonably
|
|
assert.LessOrEqual(t, cost.PPUsed, teNeeded, "PP Used should not exceed TE needed for level %d", cost.TargetLevel)
|
|
assert.LessOrEqual(t, cost.GoldUsed, maxGoldUsable, "Gold Used should not exceed max usable for level %d", cost.TargetLevel)
|
|
|
|
// EP cost should be non-negative
|
|
assert.GreaterOrEqual(t, cost.EP, 0, "EP cost should be non-negative for level %d", cost.TargetLevel)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("GetLernCost Athletik - Cost Comparison Baseline vs Resources", func(t *testing.T) {
|
|
// First get baseline costs (no resources used)
|
|
baselineRequest := gsmaster.LernCostRequest{
|
|
CharId: 20,
|
|
Name: "Athletik",
|
|
CurrentLevel: 9,
|
|
Type: "skill",
|
|
Action: "improve",
|
|
TargetLevel: 0,
|
|
UsePP: 0,
|
|
UseGold: 0,
|
|
Reward: &[]string{"default"}[0],
|
|
}
|
|
baselineBody, _ := json.Marshal(baselineRequest)
|
|
|
|
req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(baselineBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
|
|
GetLernCost(c)
|
|
|
|
var baselineResponse []gsmaster.SkillCostResultNew
|
|
err := json.Unmarshal(w.Body.Bytes(), &baselineResponse)
|
|
assert.NoError(t, err, "Baseline response should be valid JSON")
|
|
|
|
// Now get costs with resources
|
|
resourceRequest := gsmaster.LernCostRequest{
|
|
CharId: 20,
|
|
Name: "Athletik",
|
|
CurrentLevel: 9,
|
|
Type: "skill",
|
|
Action: "improve",
|
|
TargetLevel: 0,
|
|
UsePP: 15,
|
|
UseGold: 100,
|
|
Reward: &[]string{"default"}[0],
|
|
}
|
|
resourceBody, _ := json.Marshal(resourceRequest)
|
|
|
|
req2, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(resourceBody))
|
|
req2.Header.Set("Content-Type", "application/json")
|
|
|
|
w2 := httptest.NewRecorder()
|
|
c2, _ := gin.CreateTestContext(w2)
|
|
c2.Request = req2
|
|
|
|
GetLernCost(c2)
|
|
|
|
var resourceResponse []gsmaster.SkillCostResultNew
|
|
err = json.Unmarshal(w2.Body.Bytes(), &resourceResponse)
|
|
assert.NoError(t, err, "Resource response should be valid JSON")
|
|
|
|
// Compare the results
|
|
fmt.Printf("\n=== Cost Comparison: Baseline vs Using Resources ===\n")
|
|
fmt.Printf("Level | Baseline EP | Resource EP | EP Saved | PP Used | Gold Used\n")
|
|
fmt.Printf("------|-------------|-------------|----------|---------|----------\n")
|
|
|
|
assert.Equal(t, len(baselineResponse), len(resourceResponse), "Both responses should have same number of levels")
|
|
|
|
for i, baseline := range baselineResponse {
|
|
if i < len(resourceResponse) {
|
|
resource := resourceResponse[i]
|
|
assert.Equal(t, baseline.TargetLevel, resource.TargetLevel, "Target levels should match")
|
|
|
|
epSaved := baseline.EP - resource.EP
|
|
fmt.Printf("%5d | %11d | %11d | %8d | %7d | %9d\n",
|
|
baseline.TargetLevel, baseline.EP, resource.EP, epSaved, resource.PPUsed, resource.GoldUsed)
|
|
|
|
// Validate that using resources reduces EP cost (or at least doesn't increase it)
|
|
assert.LessOrEqual(t, resource.EP, baseline.EP,
|
|
"EP cost should not increase when using resources for level %d", baseline.TargetLevel)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("GetLernCost with invalid character ID", func(t *testing.T) {
|
|
// Test with non-existent character ID
|
|
requestData := gsmaster.LernCostRequest{
|
|
CharId: 999, // Non-existent character
|
|
Name: "Athletik",
|
|
CurrentLevel: 9,
|
|
Type: "skill",
|
|
Action: "improve",
|
|
TargetLevel: 0,
|
|
UsePP: 0,
|
|
Reward: &[]string{"default"}[0],
|
|
}
|
|
requestBody, _ := json.Marshal(requestData)
|
|
|
|
req, _ := http.NewRequest("POST", "/api/characters/999/lerncost", bytes.NewBuffer(requestBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
c.Params = []gin.Param{{Key: "id", Value: "999"}}
|
|
|
|
GetLernCost(c)
|
|
|
|
// Should return 404 Not Found
|
|
assert.Equal(t, http.StatusNotFound, w.Code, "Status code should be 404 Not Found")
|
|
|
|
var response map[string]interface{}
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Response should be valid JSON")
|
|
assert.Contains(t, response, "error", "Response should contain error message")
|
|
|
|
fmt.Printf("Error case - Invalid character ID: %s\n", response["error"])
|
|
})
|
|
|
|
t.Run("GetLernCost with invalid request structure", func(t *testing.T) {
|
|
// Test with missing required fields
|
|
requestData := map[string]interface{}{
|
|
"char_id": "invalid", // Invalid type - should be uint
|
|
"name": "", // Empty name
|
|
}
|
|
requestBody, _ := json.Marshal(requestData)
|
|
|
|
req, _ := http.NewRequest("POST", "/api/characters/20/lerncost", bytes.NewBuffer(requestBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
c.Params = []gin.Param{{Key: "id", Value: "20"}}
|
|
|
|
GetLernCost(c)
|
|
|
|
// Should return 400 Bad Request
|
|
assert.Equal(t, http.StatusBadRequest, w.Code, "Status code should be 400 Bad Request")
|
|
|
|
var response map[string]interface{}
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Response should be valid JSON")
|
|
assert.Contains(t, response, "error", "Response should contain error message")
|
|
|
|
fmt.Printf("Error case - Invalid request: %s\n", response["error"])
|
|
})
|
|
}
|
|
|
|
// Test GetLernCost endpoint specifically with gsmaster.LernCostRequest structure
|
|
func TestGetLernCostEndpointNewSystem(t *testing.T) {
|
|
// Setup test database
|
|
database.SetupTestDB()
|
|
defer database.ResetTestDB()
|
|
|
|
// Migrate the schema
|
|
err := models.MigrateStructure()
|
|
assert.NoError(t, err)
|
|
|
|
// Setup Gin in test mode
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
t.Run("GetLernCost with Athletik for Krieger character", func(t *testing.T) {
|
|
// Create request body using gsmaster.LernCostRequest structure
|
|
requestData := gsmaster.LernCostRequest{
|
|
CharId: 20, // CharacterID = 20
|
|
Name: "Athletik", // SkillName = Athletik
|
|
CurrentLevel: 9, // CurrentLevel = 9
|
|
Type: "skill", // Type = skill
|
|
Action: "improve", // Action = improve (since we have current level)
|
|
TargetLevel: 0, // TargetLevel = 0 (will calculate up to level 18)
|
|
UsePP: 0, // No practice points used
|
|
UseGold: 0,
|
|
Reward: &[]string{"default"}[0], // Default reward type
|
|
}
|
|
requestBody, _ := json.Marshal(requestData)
|
|
|
|
// Create HTTP request
|
|
req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create response recorder
|
|
w := httptest.NewRecorder()
|
|
|
|
// Create Gin context
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
c.Params = []gin.Param{{Key: "id", Value: "20"}}
|
|
|
|
fmt.Printf("Test: GetLernCost for Athletik improvement for Krieger character ID 20\n")
|
|
fmt.Printf("Request: CharId=%d, SkillName=%s, CurrentLevel=%d, TargetLevel=%d\n",
|
|
requestData.CharId, requestData.Name, requestData.CurrentLevel, requestData.TargetLevel)
|
|
|
|
// Call the actual handler function
|
|
GetLernCostNewSystem(c)
|
|
|
|
// Print the actual response to see what we get
|
|
fmt.Printf("Response Status: %d\n", w.Code)
|
|
fmt.Printf("Response Body: %s\n", w.Body.String())
|
|
|
|
// Check if we got an error response first
|
|
if w.Code != http.StatusOK {
|
|
var errorResponse map[string]interface{}
|
|
err := json.Unmarshal(w.Body.Bytes(), &errorResponse)
|
|
if err == nil {
|
|
fmt.Printf("Error Response: %+v\n", errorResponse)
|
|
}
|
|
assert.Fail(t, "Expected successful response but got error: %s", w.Body.String())
|
|
return
|
|
}
|
|
|
|
// Parse and validate response for success case
|
|
var response []gsmaster.SkillCostResultNew
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Response should be valid JSON array of SkillCostResultNew")
|
|
|
|
// Should have costs for levels 10, 11, 12, ... up to 18 (from current level 9)
|
|
assert.Greater(t, len(response), 0, "Should return learning costs for multiple levels")
|
|
assert.LessOrEqual(t, len(response), 9, "Should not return more than 9 levels (10-18)")
|
|
|
|
// Validate the first entry (level 10)
|
|
if len(response) > 0 {
|
|
firstResult := response[0]
|
|
assert.Equal(t, "20", firstResult.CharacterID, "Character ID should match")
|
|
assert.Equal(t, "Athletik", firstResult.SkillName, "Skill name should match")
|
|
assert.Equal(t, 10, firstResult.TargetLevel, "First target level should be 10")
|
|
|
|
// Character class should be "Kr" (abbreviation for "Krieger")
|
|
assert.Equal(t, "Kr", firstResult.CharacterClass, "Character class should be abbreviated to 'Kr'")
|
|
|
|
// Should have valid costs
|
|
assert.Greater(t, firstResult.EP, 0, "EP cost should be greater than 0")
|
|
assert.GreaterOrEqual(t, firstResult.GoldCost, 0, "Gold cost should be 0 or greater")
|
|
assert.Equal(t, firstResult.EP, 20, "EP cost should be 20")
|
|
assert.Equal(t, firstResult.GoldCost, 40, "Gold cost should be 40")
|
|
|
|
fmt.Printf("Level %d cost: EP=%d, GoldCost=%d, LE=%d\n", firstResult.TargetLevel,
|
|
firstResult.EP, firstResult.GoldCost, firstResult.LE)
|
|
fmt.Printf("Category=%s, Difficulty=%s\n",
|
|
firstResult.Category, firstResult.Difficulty)
|
|
}
|
|
|
|
// Find cost for level 12 specifically to test mid-range
|
|
var level12Cost *gsmaster.SkillCostResultNew
|
|
for i := range response {
|
|
if response[i].TargetLevel == 12 {
|
|
level12Cost = &response[i]
|
|
break
|
|
}
|
|
}
|
|
|
|
if level12Cost != nil {
|
|
assert.Equal(t, 12, level12Cost.TargetLevel, "Target level should be 12")
|
|
assert.Greater(t, level12Cost.EP, 0, "EP cost should be greater than 0 for level 12")
|
|
|
|
fmt.Printf("Level 12 cost: EP=%d, GoldCost=%d, LE=%d\n",
|
|
level12Cost.EP, level12Cost.GoldCost, level12Cost.LE)
|
|
} else {
|
|
fmt.Printf("No cost found for level 12. Available levels: ")
|
|
for _, cost := range response {
|
|
fmt.Printf("%d ", cost.TargetLevel)
|
|
}
|
|
fmt.Println()
|
|
}
|
|
|
|
// Verify all target levels are sequential and start from current level + 1
|
|
expectedLevel := 10 // Current level 9 + 1
|
|
for _, cost := range response {
|
|
assert.Equal(t, expectedLevel, cost.TargetLevel,
|
|
"Target levels should be sequential starting from %d", expectedLevel)
|
|
assert.Equal(t, "Athletik", cost.SkillName, "All entries should have correct skill name")
|
|
assert.Equal(t, "Kr", cost.CharacterClass, "All entries should have correct character class")
|
|
expectedLevel++
|
|
}
|
|
})
|
|
|
|
t.Run("GetLernCost Athletik - Detailed Cost Analysis for Each Level", func(t *testing.T) {
|
|
requestData := gsmaster.LernCostRequest{
|
|
CharId: 20,
|
|
Name: "Athletik",
|
|
CurrentLevel: 9,
|
|
Type: "skill",
|
|
Action: "improve",
|
|
TargetLevel: 0, // Calculate all levels
|
|
UsePP: 0,
|
|
UseGold: 0,
|
|
Reward: &[]string{"default"}[0],
|
|
}
|
|
requestBody, _ := json.Marshal(requestData)
|
|
|
|
req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
|
|
GetLernCost(c)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code, "Request should succeed")
|
|
|
|
var response []gsmaster.SkillCostResultNew
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Response should be valid JSON")
|
|
|
|
fmt.Printf("\n=== Detailed Cost Analysis for Athletik (Levels 10-18) ===\n")
|
|
fmt.Printf("Level | EP Cost | Gold Cost | LE Cost | PP Used | Gold Used\n")
|
|
fmt.Printf("------|---------|-----------|---------|---------|----------\n")
|
|
|
|
for _, cost := range response {
|
|
fmt.Printf("%5d | %7d | %9d | %7d | %7d | %9d\n",
|
|
cost.TargetLevel, cost.EP, cost.GoldCost, cost.LE, cost.PPUsed, cost.GoldUsed)
|
|
|
|
// Validate each level's costs
|
|
assert.Greater(t, cost.EP, 0, "EP cost should be positive for level %d", cost.TargetLevel)
|
|
assert.GreaterOrEqual(t, cost.GoldCost, 0, "Gold cost should be non-negative for level %d", cost.TargetLevel)
|
|
assert.GreaterOrEqual(t, cost.LE, 0, "LE cost should be non-negative for level %d", cost.TargetLevel)
|
|
assert.Equal(t, 0, cost.PPUsed, "PP Used should be 0 when UsePP=0 for level %d", cost.TargetLevel)
|
|
assert.Equal(t, 0, cost.GoldUsed, "Gold Used should be 0 when UseGold=0 for level %d", cost.TargetLevel)
|
|
|
|
// Verify cost progression (higher levels should generally cost more)
|
|
if cost.TargetLevel > 10 {
|
|
prevLevel := cost.TargetLevel - 1
|
|
var prevCost *gsmaster.SkillCostResultNew
|
|
for i := range response {
|
|
if response[i].TargetLevel == prevLevel {
|
|
prevCost = &response[i]
|
|
break
|
|
}
|
|
}
|
|
if prevCost != nil {
|
|
assert.GreaterOrEqual(t, cost.EP, prevCost.EP,
|
|
"EP cost should not decrease from level %d to %d", prevLevel, cost.TargetLevel)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("GetLernCost Athletik - With Practice Points Usage", func(t *testing.T) {
|
|
testCases := []struct {
|
|
usePP int
|
|
description string
|
|
}{
|
|
{5, "Using 5 Practice Points"},
|
|
{10, "Using 10 Practice Points"},
|
|
{20, "Using 20 Practice Points"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.description, func(t *testing.T) {
|
|
requestData := gsmaster.LernCostRequest{
|
|
CharId: 20,
|
|
Name: "Athletik",
|
|
CurrentLevel: 9,
|
|
Type: "skill",
|
|
Action: "improve",
|
|
TargetLevel: 0,
|
|
UsePP: tc.usePP,
|
|
UseGold: 0,
|
|
Reward: &[]string{"default"}[0],
|
|
}
|
|
requestBody, _ := json.Marshal(requestData)
|
|
|
|
req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
|
|
GetLernCost(c)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code, "Request should succeed for UsePP=%d", tc.usePP)
|
|
|
|
var response []gsmaster.SkillCostResultNew
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Response should be valid JSON")
|
|
|
|
fmt.Printf("\n=== Cost Analysis with %d Practice Points ===\n", tc.usePP)
|
|
fmt.Printf("Level | EP Cost | Gold Cost | LE Cost | PP Used | Gold Used\n")
|
|
fmt.Printf("------|---------|-----------|---------|---------|----------\n")
|
|
|
|
for i, cost := range response {
|
|
fmt.Printf("%5d | %7d | %9d | %7d | %7d | %9d\n",
|
|
cost.TargetLevel, cost.EP, cost.GoldCost, cost.LE, cost.PPUsed, cost.GoldUsed)
|
|
|
|
// Simple validation: PP should be reasonable and Gold should be 0
|
|
assert.LessOrEqual(t, cost.PPUsed, 50, "PP Used should be reasonable for level %d", cost.TargetLevel)
|
|
assert.Equal(t, 0, cost.GoldUsed, "Gold Used should be 0 when UseGold=0 for level %d", cost.TargetLevel)
|
|
|
|
// EP cost should be non-negative
|
|
assert.GreaterOrEqual(t, cost.EP, 0, "EP cost should be non-negative for level %d", cost.TargetLevel)
|
|
|
|
// When enough PP are available, early levels should have 0 EP cost
|
|
if i == 0 && tc.usePP >= 2 {
|
|
assert.Equal(t, 0, cost.EP, "Level 10 should have 0 EP cost when enough PP available")
|
|
}
|
|
|
|
// EP cost validation
|
|
if cost.PPUsed > 0 {
|
|
// When PP are used, EP should be reduced or zero
|
|
assert.GreaterOrEqual(t, cost.EP, 0, "EP cost should be non-negative for level %d", cost.TargetLevel)
|
|
} else {
|
|
// When no PP are used, EP should be positive
|
|
assert.Greater(t, cost.EP, 0, "EP cost should be positive when no PP used for level %d", cost.TargetLevel)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("GetLernCost Athletik - With Gold Usage", func(t *testing.T) {
|
|
testCases := []struct {
|
|
useGold int
|
|
description string
|
|
}{
|
|
{50, "Using 50 Gold"},
|
|
{100, "Using 100 Gold"},
|
|
{200, "Using 200 Gold"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.description, func(t *testing.T) {
|
|
requestData := gsmaster.LernCostRequest{
|
|
CharId: 20,
|
|
Name: "Athletik",
|
|
CurrentLevel: 9,
|
|
Type: "skill",
|
|
Action: "improve",
|
|
TargetLevel: 0,
|
|
UsePP: 0,
|
|
UseGold: tc.useGold,
|
|
Reward: &[]string{"default"}[0],
|
|
}
|
|
requestBody, _ := json.Marshal(requestData)
|
|
|
|
req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
|
|
GetLernCost(c)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code, "Request should succeed for UseGold=%d", tc.useGold)
|
|
|
|
var response []gsmaster.SkillCostResultNew
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Response should be valid JSON")
|
|
|
|
fmt.Printf("\n=== Cost Analysis with %d Gold ===\n", tc.useGold)
|
|
fmt.Printf("Level | EP Cost | Gold Cost | LE Cost | PP Used | Gold Used\n")
|
|
fmt.Printf("------|---------|-----------|---------|---------|----------\n")
|
|
|
|
for i, cost := range response {
|
|
fmt.Printf("%5d | %7d | %9d | %7d | %7d | %9d\n",
|
|
cost.TargetLevel, cost.EP, cost.GoldCost, cost.LE, cost.PPUsed, cost.GoldUsed)
|
|
|
|
// Validate Gold usage based on EP needs and cumulative usage
|
|
remainingGold := tc.useGold
|
|
|
|
// Calculate cumulative Gold usage for previous levels
|
|
for j := 0; j < i; j++ {
|
|
if j < len(response) {
|
|
remainingGold -= response[j].GoldUsed
|
|
}
|
|
}
|
|
|
|
// Current level's expected Gold usage
|
|
epCostWithoutGold := cost.EP + (cost.GoldUsed / 10) // Reverse calculate original EP
|
|
maxGoldUsable := epCostWithoutGold * 10 // Max gold that can be used (10 gold = 1 EP)
|
|
|
|
expectedGoldUsed := 0
|
|
if remainingGold > 0 {
|
|
if remainingGold >= maxGoldUsable {
|
|
expectedGoldUsed = maxGoldUsable
|
|
} else {
|
|
expectedGoldUsed = remainingGold
|
|
}
|
|
}
|
|
|
|
assert.Equal(t, expectedGoldUsed, cost.GoldUsed, "Gold Used should match calculated value for level %d (remaining Gold: %d, max usable: %d)", cost.TargetLevel, remainingGold, maxGoldUsable)
|
|
assert.Equal(t, 0, cost.PPUsed, "PP Used should be 0 when UsePP=0 for level %d", cost.TargetLevel)
|
|
|
|
// EP cost validation
|
|
assert.GreaterOrEqual(t, cost.EP, 0, "EP cost should be non-negative for level %d", cost.TargetLevel)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("GetLernCost Athletik - Combined PP and Gold Usage", func(t *testing.T) {
|
|
testCases := []struct {
|
|
usePP int
|
|
useGold int
|
|
description string
|
|
}{
|
|
{10, 50, "Using 10 PP and 50 Gold"},
|
|
{15, 100, "Using 15 PP and 100 Gold"},
|
|
{25, 200, "Using 25 PP and 200 Gold"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.description, func(t *testing.T) {
|
|
requestData := gsmaster.LernCostRequest{
|
|
CharId: 20,
|
|
Name: "Athletik",
|
|
CurrentLevel: 9,
|
|
Type: "skill",
|
|
Action: "improve",
|
|
TargetLevel: 0,
|
|
UsePP: tc.usePP,
|
|
UseGold: tc.useGold,
|
|
Reward: &[]string{"default"}[0],
|
|
}
|
|
requestBody, _ := json.Marshal(requestData)
|
|
|
|
req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
|
|
GetLernCost(c)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code, "Request should succeed for UsePP=%d, UseGold=%d", tc.usePP, tc.useGold)
|
|
|
|
var response []gsmaster.SkillCostResultNew
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Response should be valid JSON")
|
|
|
|
fmt.Printf("\n=== Cost Analysis with %d PP and %d Gold ===\n", tc.usePP, tc.useGold)
|
|
fmt.Printf("Level | EP Cost | Gold Cost | LE Cost | PP Used | Gold Used\n")
|
|
fmt.Printf("------|---------|-----------|---------|---------|----------\n")
|
|
|
|
for _, cost := range response {
|
|
fmt.Printf("%5d | %7d | %9d | %7d | %7d | %9d\n",
|
|
cost.TargetLevel, cost.EP, cost.GoldCost, cost.LE, cost.PPUsed, cost.GoldUsed)
|
|
|
|
// Calculate original TE needed (LE + PP Used = original TE)
|
|
teNeeded := cost.LE + cost.PPUsed
|
|
|
|
// Calculate original EP before gold usage (EP + Gold/10 = original EP)
|
|
epAfterPP := cost.EP + (cost.GoldUsed / 10)
|
|
maxGoldUsable := epAfterPP * 10
|
|
|
|
// Validate that resources are used reasonably
|
|
assert.LessOrEqual(t, cost.PPUsed, teNeeded, "PP Used should not exceed TE needed for level %d", cost.TargetLevel)
|
|
assert.LessOrEqual(t, cost.GoldUsed, maxGoldUsable, "Gold Used should not exceed max usable for level %d", cost.TargetLevel)
|
|
|
|
// EP cost should be non-negative
|
|
assert.GreaterOrEqual(t, cost.EP, 0, "EP cost should be non-negative for level %d", cost.TargetLevel)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("GetLernCost Athletik - Cost Comparison Baseline vs Resources", func(t *testing.T) {
|
|
// First get baseline costs (no resources used)
|
|
baselineRequest := gsmaster.LernCostRequest{
|
|
CharId: 20,
|
|
Name: "Athletik",
|
|
CurrentLevel: 9,
|
|
Type: "skill",
|
|
Action: "improve",
|
|
TargetLevel: 0,
|
|
UsePP: 0,
|
|
UseGold: 0,
|
|
Reward: &[]string{"default"}[0],
|
|
}
|
|
baselineBody, _ := json.Marshal(baselineRequest)
|
|
|
|
req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(baselineBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
|
|
GetLernCost(c)
|
|
|
|
var baselineResponse []gsmaster.SkillCostResultNew
|
|
err := json.Unmarshal(w.Body.Bytes(), &baselineResponse)
|
|
assert.NoError(t, err, "Baseline response should be valid JSON")
|
|
|
|
// Now get costs with resources
|
|
resourceRequest := gsmaster.LernCostRequest{
|
|
CharId: 20,
|
|
Name: "Athletik",
|
|
CurrentLevel: 9,
|
|
Type: "skill",
|
|
Action: "improve",
|
|
TargetLevel: 0,
|
|
UsePP: 15,
|
|
UseGold: 100,
|
|
Reward: &[]string{"default"}[0],
|
|
}
|
|
resourceBody, _ := json.Marshal(resourceRequest)
|
|
|
|
req2, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(resourceBody))
|
|
req2.Header.Set("Content-Type", "application/json")
|
|
|
|
w2 := httptest.NewRecorder()
|
|
c2, _ := gin.CreateTestContext(w2)
|
|
c2.Request = req2
|
|
|
|
GetLernCost(c2)
|
|
|
|
var resourceResponse []gsmaster.SkillCostResultNew
|
|
err = json.Unmarshal(w2.Body.Bytes(), &resourceResponse)
|
|
assert.NoError(t, err, "Resource response should be valid JSON")
|
|
|
|
// Compare the results
|
|
fmt.Printf("\n=== Cost Comparison: Baseline vs Using Resources ===\n")
|
|
fmt.Printf("Level | Baseline EP | Resource EP | EP Saved | PP Used | Gold Used\n")
|
|
fmt.Printf("------|-------------|-------------|----------|---------|----------\n")
|
|
|
|
assert.Equal(t, len(baselineResponse), len(resourceResponse), "Both responses should have same number of levels")
|
|
|
|
for i, baseline := range baselineResponse {
|
|
if i < len(resourceResponse) {
|
|
resource := resourceResponse[i]
|
|
assert.Equal(t, baseline.TargetLevel, resource.TargetLevel, "Target levels should match")
|
|
|
|
epSaved := baseline.EP - resource.EP
|
|
fmt.Printf("%5d | %11d | %11d | %8d | %7d | %9d\n",
|
|
baseline.TargetLevel, baseline.EP, resource.EP, epSaved, resource.PPUsed, resource.GoldUsed)
|
|
|
|
// Validate that using resources reduces EP cost (or at least doesn't increase it)
|
|
assert.LessOrEqual(t, resource.EP, baseline.EP,
|
|
"EP cost should not increase when using resources for level %d", baseline.TargetLevel)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("GetLernCost with invalid character ID", func(t *testing.T) {
|
|
// Test with non-existent character ID
|
|
requestData := gsmaster.LernCostRequest{
|
|
CharId: 999, // Non-existent character
|
|
Name: "Athletik",
|
|
CurrentLevel: 9,
|
|
Type: "skill",
|
|
Action: "improve",
|
|
TargetLevel: 0,
|
|
UsePP: 0,
|
|
Reward: &[]string{"default"}[0],
|
|
}
|
|
requestBody, _ := json.Marshal(requestData)
|
|
|
|
req, _ := http.NewRequest("POST", "/api/characters/999/lerncost", bytes.NewBuffer(requestBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
c.Params = []gin.Param{{Key: "id", Value: "999"}}
|
|
|
|
GetLernCost(c)
|
|
|
|
// Should return 404 Not Found
|
|
assert.Equal(t, http.StatusNotFound, w.Code, "Status code should be 404 Not Found")
|
|
|
|
var response map[string]interface{}
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Response should be valid JSON")
|
|
assert.Contains(t, response, "error", "Response should contain error message")
|
|
|
|
fmt.Printf("Error case - Invalid character ID: %s\n", response["error"])
|
|
})
|
|
|
|
t.Run("GetLernCost with invalid request structure", func(t *testing.T) {
|
|
// Test with missing required fields
|
|
requestData := map[string]interface{}{
|
|
"char_id": "invalid", // Invalid type - should be uint
|
|
"name": "", // Empty name
|
|
}
|
|
requestBody, _ := json.Marshal(requestData)
|
|
|
|
req, _ := http.NewRequest("POST", "/api/characters/20/lerncost", bytes.NewBuffer(requestBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
c.Params = []gin.Param{{Key: "id", Value: "20"}}
|
|
|
|
GetLernCost(c)
|
|
|
|
// Should return 400 Bad Request
|
|
assert.Equal(t, http.StatusBadRequest, w.Code, "Status code should be 400 Bad Request")
|
|
|
|
var response map[string]interface{}
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Response should be valid JSON")
|
|
assert.Contains(t, response, "error", "Response should contain error message")
|
|
|
|
fmt.Printf("Error case - Invalid request: %s\n", response["error"])
|
|
})
|
|
}
|