Files
bamort/backend/character/handlers_test.go
T
Frank a547d850a2 TestCopyLiveDatabaseToFile now runs
after connection to local MySQL database is working for go tests
2026-01-02 12:07:28 +01:00

975 lines
32 KiB
Go

package character
import (
"bytes"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"os"
"testing"
"bamort/database"
"bamort/models"
"bamort/user"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"gorm.io/gorm"
)
func TestImproveSkillHandler(t *testing.T) {
// Setup test environment
original := os.Getenv("ENVIRONMENT")
os.Setenv("ENVIRONMENT", "test")
t.Cleanup(func() {
if original != "" {
os.Setenv("ENVIRONMENT", original)
} else {
os.Unsetenv("ENVIRONMENT")
}
})
// Setup test database
database.SetupTestDB(true, true)
defer database.ResetTestDB()
err := models.MigrateStructure()
assert.NoError(t, err)
// Create test character with ID 20
/*
testChar := Char{
BamortBase: models.BamortBase{
ID: 20,
Name: "Test Krieger",
},
Typ: "Krieger",
Rasse: "Mensch",
Grad: 1,
Erfahrungsschatz: Erfahrungsschatz{
BamortCharTrait: models.BamortCharTrait{
CharacterID: 20,
},
Value: 326, // Starting EP (should end with 316 after spending 10)
},
Vermoegen: Vermoegen{
BamortCharTrait: models.BamortCharTrait{
CharacterID: 20,
},
Goldstücke: 390, // Starting Gold (should end with 370 after spending 20)
},
}
// Add Athletik skill at level 9
athletikSkill := models.Fertigkeit{
BamortCharTrait: models.BamortCharTrait{
BamortBase: models.BamortBase{
Name: "Athletik",
},
CharacterID: 20,
},
Fertigkeitswert: 9,
}
testChar.Fertigkeiten = append(testChar.Fertigkeiten, athletikSkill)
err = testChar.Create()
assert.NoError(t, err)
*/
// Setup Gin in test mode
gin.SetMode(gin.TestMode)
t.Run("ImproveSkill Athletik from level 9 to 10", func(t *testing.T) {
// Create request body matching gsmaster.LernCostRequest structure
requestData := map[string]interface{}{
"char_id": 20,
"name": "Athletik",
"current_level": 9,
"target_level": 10,
"type": "skill",
"action": "improve",
"reward": "default",
"use_pp": 1,
"use_gold": 0,
"levels_to_learn": []int{10},
"notes": "Fertigkeit Athletik von 9 auf 10 verbessert (1 Level)",
}
requestBody, _ := json.Marshal(requestData)
// Create HTTP request
req, _ := http.NewRequest("POST", "/api/characters/improve-skill", 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
// Call the actual handler function
ImproveSkill(c)
// Print the actual response to see what we get
t.Logf("Response Status: %d", w.Code)
t.Logf("Response Body: %s", w.Body.String())
// Check if we got a successful response
assert.Equal(t, http.StatusOK, w.Code, "Status code should be 200 OK")
// Parse and validate response
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err, "Response should be valid JSON")
// Validate expected response values
expectedResponse := map[string]interface{}{
"ep_cost": float64(10), // JSON numbers are float64
"from_level": float64(9),
"gold_cost": float64(20),
"message": "Fertigkeit erfolgreich verbessert",
"remaining_ep": float64(250),
"remaining_gold": float64(290),
"skill_name": "Athletik",
"to_level": float64(10),
}
// Check each expected field
for key, expectedValue := range expectedResponse {
actualValue, exists := response[key]
assert.True(t, exists, "Response should contain field: %s", key)
assert.Equal(t, expectedValue, actualValue, "Field %s should match expected value", key)
}
// Additional validations
assert.Contains(t, response, "message", "Response should contain success message")
assert.Equal(t, "Fertigkeit erfolgreich verbessert", response["message"])
// Verify character state was updated correctly
var updatedChar models.Char
err = updatedChar.FirstID("20")
assert.NoError(t, err)
// Check that EP was deducted correctly
assert.Equal(t, 250, updatedChar.Erfahrungsschatz.EP, "Character should have 316 EP remaining")
// Check that Gold was deducted correctly
assert.Equal(t, 290, updatedChar.Vermoegen.Goldstuecke, "Character should have 370 Gold remaining")
t.Logf("Test completed successfully!")
t.Logf("EP: %d -> %d (cost: %.0f)", 326, updatedChar.Erfahrungsschatz.EP, response["ep_cost"])
t.Logf("Gold: %d -> %d (cost: %.0f)", 390, updatedChar.Vermoegen.Goldstuecke, response["gold_cost"])
})
}
func TestGetAvailableSkillsNewSystem(t *testing.T) {
// Setup test environment
original := os.Getenv("ENVIRONMENT")
os.Setenv("ENVIRONMENT", "test")
t.Cleanup(func() {
if original != "" {
os.Setenv("ENVIRONMENT", original)
} else {
os.Unsetenv("ENVIRONMENT")
}
})
// Setup test database
database.SetupTestDB(true, true)
defer database.ResetTestDB()
err := models.MigrateStructure()
assert.NoError(t, err)
t.Run("GetAvailableSkillsForCharacterCreation", func(t *testing.T) {
// Test data - the exact request format for character creation
requestData := map[string]interface{}{
"CharId": 0,
"name": "",
"current_level": 0,
"target_level": 1,
"type": "skill",
"action": "learn",
"use_pp": 0,
"use_gold": 0,
"reward": "default",
}
requestBody, err := json.Marshal(requestData)
assert.NoError(t, err)
// Create test request
req, _ := http.NewRequest("POST", "/api/characters/available-skills-new", bytes.NewBuffer(requestBody))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = req
// Call the handler
GetAvailableSkillsNewSystem(c)
// Verify response
assert.Equal(t, http.StatusOK, w.Code, "Should return 200 OK for character creation request")
var response map[string]interface{}
err = json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
// Check response structure
assert.Contains(t, response, "skills_by_category", "Response should contain skills_by_category")
skillsByCategory, ok := response["skills_by_category"].(map[string]interface{})
assert.True(t, ok, "skills_by_category should be a map")
// Verify we have reasonable costs for character creation
assert.Greater(t, len(skillsByCategory), 0, "Should have at least some skill categories")
// Check that costs are reasonable for character creation (not the high fallback values)
for _, skills := range skillsByCategory {
if skillsList, ok := skills.([]interface{}); ok {
for _, skill := range skillsList {
if skillMap, ok := skill.(map[string]interface{}); ok {
epCost := skillMap["epCost"].(float64)
goldCost := skillMap["goldCost"].(float64)
// Verify costs are reasonable for character creation (not fallback values)
assert.Less(t, epCost, 1000.0, "EP cost should be reasonable for character creation")
assert.Less(t, goldCost, 1000.0, "Gold cost should be reasonable for character creation")
assert.Greater(t, epCost, 0.0, "EP cost should be positive")
assert.Greater(t, goldCost, 0.0, "Gold cost should be positive")
}
}
}
}
assert.Greater(t, len(skillsByCategory), 0, "Should return at least some skill categories")
// Check that each category contains skills with proper structure
for categoryName, categorySkills := range skillsByCategory {
assert.NotEmpty(t, categoryName, "Category name should not be empty")
skillsArray, ok := categorySkills.([]interface{})
assert.True(t, ok, "Category skills should be an array")
if len(skillsArray) > 0 {
// Check first skill structure
firstSkill, ok := skillsArray[0].(map[string]interface{})
assert.True(t, ok, "Skill should be a map")
// Verify skill has required fields
assert.Contains(t, firstSkill, "name", "Skill should have name field")
assert.Contains(t, firstSkill, "epCost", "Skill should have epCost field")
assert.Contains(t, firstSkill, "goldCost", "Skill should have goldCost field")
// Verify field types
assert.IsType(t, "", firstSkill["name"], "Name should be string")
assert.IsType(t, float64(0), firstSkill["epCost"], "epCost should be numeric")
assert.IsType(t, float64(0), firstSkill["goldCost"], "goldCost should be numeric")
}
}
})
t.Run("GetAvailableSkillsInvalidRequest", func(t *testing.T) {
// Test with missing required fields (type, action, reward)
requestData := map[string]interface{}{
"CharId": 0, // CharId 0 is valid for character creation
"name": "",
"current_level": 0,
"target_level": 1,
// Missing "type", "action", and "reward" - should fail
"use_pp": 0,
"use_gold": 0,
}
requestBody, err := json.Marshal(requestData)
assert.NoError(t, err)
req, _ := http.NewRequest("POST", "/api/characters/available-skills-new", bytes.NewBuffer(requestBody))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = req
GetAvailableSkillsNewSystem(c)
// Should return 400 Bad Request for missing required fields
assert.Equal(t, http.StatusBadRequest, w.Code, "Should return 400 for missing required fields")
var response map[string]interface{}
err = json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Contains(t, response, "error")
assert.True(t, len(response["error"].(string)) > 0, "Error message should not be empty")
})
t.Run("GetAvailableSkillsInvalidRewardType", func(t *testing.T) {
// Test with invalid reward type
requestData := map[string]interface{}{
"CharId": 0,
"name": "",
"current_level": 0,
"target_level": 1,
"type": "skill",
"action": "learn",
"use_pp": 0,
"use_gold": 0,
"reward": "invalid",
}
requestBody, err := json.Marshal(requestData)
assert.NoError(t, err)
req, _ := http.NewRequest("POST", "/api/characters/available-skills-new", bytes.NewBuffer(requestBody))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = req
GetAvailableSkillsNewSystem(c)
// Should return 400 Bad Request for invalid reward type
assert.Equal(t, http.StatusBadRequest, w.Code, "Should return 400 for invalid reward type")
var response map[string]interface{}
err = json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Contains(t, response, "error")
})
}
// TestGetAvailableSkillsForCreation tests the character creation skills endpoint
func TestGetAvailableSkillsForCreation(t *testing.T) {
// Setup test database
database.SetupTestDB(true, true)
defer database.ResetTestDB()
tests := []struct {
name string
characterClass string
expectStatus int
expectError bool
}{
{
name: "ValidCharacterClass",
characterClass: "As",
expectStatus: http.StatusOK,
expectError: false,
},
{
name: "MagierCharacterClass",
characterClass: "Ma",
expectStatus: http.StatusOK,
expectError: false,
},
{
name: "EmptyCharacterClass",
characterClass: "",
expectStatus: http.StatusBadRequest,
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create request
requestData := gin.H{
"characterClass": tt.characterClass,
}
requestBody, _ := json.Marshal(requestData)
// Create HTTP request
req, _ := http.NewRequest("POST", "/api/characters/available-skills-creation", bytes.NewBuffer(requestBody))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer test-token")
// Create response recorder
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = req
// Call the handler directly (it will handle JSON parsing internally)
GetAvailableSkillsForCreation(c)
// Verify response
assert.Equal(t, tt.expectStatus, w.Code)
if !tt.expectError {
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
// Check response structure
assert.Contains(t, response, "skills_by_category")
skillsByCategory, ok := response["skills_by_category"].(map[string]interface{})
assert.True(t, ok)
assert.Greater(t, len(skillsByCategory), 0, "Should have at least some skill categories")
// Verify skills have learnCost field
hasNonDefaultCost := false
for categoryName, skills := range skillsByCategory {
if skillsList, ok := skills.([]interface{}); ok {
for _, skill := range skillsList {
if skillMap, ok := skill.(map[string]interface{}); ok {
assert.Contains(t, skillMap, "name", "Skill should have name", skillMap["name"])
//assert.Contains(t, skillMap, "learnCost", "Skill should have learnCost", skillMap["name"])
assert.Contains(t, skillMap, "leCost", "Skill should have leCost", skillMap["name"])
learnCost := skillMap["leCost"].(float64)
assert.Greater(t, learnCost, 0.0, "Learn cost should be positive")
assert.Less(t, learnCost, 500.0, "Learn cost should be reasonable for character creation")
// Check if we have skills with non-default costs
if learnCost != 50 {
hasNonDefaultCost = true
}
// Log individual skill costs for debugging
if tt.characterClass == "Ma" {
t.Logf("Skill: %s, Category: %s, LearnCost: %.0f", skillMap["name"], categoryName, learnCost)
}
}
}
if tt.characterClass == "Ma" {
break // Only log first category for Ma
}
}
_ = categoryName // Mark as used
}
// For Ma class, we should get some skills with different costs than the default 50
if tt.characterClass == "Ma" {
// This is more of an informational test - we want to see what costs we get
t.Logf("Ma class - has skills with non-default costs: %v", hasNonDefaultCost)
} // Log some sample data for verification
t.Logf("Character creation skills loaded for class %s: %d categories", tt.characterClass, len(skillsByCategory))
} else {
// For error cases, verify error response
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Contains(t, response, "error")
}
})
}
}
func TestGetAvailableSpellsForCreation(t *testing.T) {
// Setup test database
database.SetupTestDB(true, true)
defer database.ResetTestDB()
tests := []struct {
name string
characterClass string
expectStatus int
expectError bool
findspells bool
}{
{
name: "ValidCharacterClass",
characterClass: "As",
expectStatus: http.StatusNotFound,
expectError: false,
findspells: false,
},
{
name: "MagierCharacterClass",
characterClass: "Ma",
expectStatus: http.StatusOK,
expectError: false,
findspells: true,
},
{
name: "NonMagicCharacterClass",
characterClass: "Kr",
expectStatus: http.StatusNotFound,
expectError: true,
findspells: false,
},
{
name: "EmptyCharacterClass",
characterClass: "",
expectStatus: http.StatusBadRequest,
expectError: true,
findspells: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create request
requestData := gin.H{
"characterClass": tt.characterClass,
}
requestBody, _ := json.Marshal(requestData)
u := user.User{}
u.FirstId(1)
token := user.GenerateToken(&u)
// Create HTTP request
req, _ := http.NewRequest("POST", "/api/characters/available-spells-creation", bytes.NewBuffer(requestBody))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
// Create response recorder
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = req
// Call the handler directly (it will handle JSON parsing internally)
GetAvailableSpellsForCreation(c)
// Verify response
assert.Equal(t, tt.expectStatus, w.Code)
if !tt.expectError {
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
// Check response structure
assert.Contains(t, response, "spells_by_category")
spellsByCategory, ok := response["spells_by_category"].(map[string]interface{})
assert.True(t, ok)
if tt.findspells {
assert.Greater(t, len(spellsByCategory), 0, "Should have at least some spell categories")
// Verify spells have learnCost field
for categoryName, spells := range spellsByCategory {
if spellsList, ok := spells.([]interface{}); ok {
for _, spell := range spellsList {
if spellMap, ok := spell.(map[string]interface{}); ok {
assert.Contains(t, spellMap, "name", "Spell should have name")
assert.Contains(t, spellMap, "le_cost", "Spell should have learnCost")
learnCost := spellMap["le_cost"].(float64)
assert.Greater(t, learnCost, 0.0, "Learn cost should be positive")
assert.Less(t, learnCost, 500.0, "Learn cost should be reasonable for character creation")
}
}
}
_ = categoryName // Mark as used
}
// Log some sample data for verification
t.Logf("Character creation spells loaded for class %s: %d categories", tt.characterClass, len(spellsByCategory))
} else {
assert.Equal(t, len(spellsByCategory), 0, "Should not have any spell categories")
}
} else {
// For error cases, verify error response
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Contains(t, response, "error")
}
})
}
}
func TestFinalizeCharacterCreation(t *testing.T) {
// Setup test environment
original := os.Getenv("ENVIRONMENT")
os.Setenv("ENVIRONMENT", "test")
t.Cleanup(func() {
if original != "" {
os.Setenv("ENVIRONMENT", original)
} else {
os.Unsetenv("ENVIRONMENT")
}
})
// Setup test database
database.SetupTestDB(true, true)
defer database.ResetTestDB()
err := models.MigrateStructure()
assert.NoError(t, err)
// Create test user (bebe)
testUser := user.User{}
testUser.FirstId(1)
/*
{
UserID: 1,
Username: "bebe",
Email: "frank@wuenscheonline.de",
Role: "admin",
}
err = database.DB.Create(&testUser).Error
assert.NoError(t, err)
*/
// Create test character creation session with data from testdata
testSession := models.CharacterCreationSession{
ID: "char_create_1_1756326371",
UserID: 1,
Name: "wergw5 z ",
Rasse: "Mensch",
Typ: "Priester Streiter",
Attributes: models.AttributesData{
ST: 89,
GS: 64,
GW: 77,
KO: 71,
IN: 87,
ZT: 44,
AU: 87,
},
DerivedValues: models.DerivedValuesData{
PA: 33,
WK: 27,
LPMax: 11,
APMax: 14,
BMax: 26,
ResistenzKoerper: 11,
ResistenzGeist: 11,
ResistenzBonusKoerper: 0,
ResistenzBonusGeist: 0,
Abwehr: 11,
AbwehrBonus: 0,
AusdauerBonus: 11,
AngriffsBonus: 0,
Zaubern: 11,
ZauberBonus: 0,
Raufen: 8,
SchadensBonus: 3,
SG: 0,
GG: 0,
GP: 0,
},
Skills: models.CharacterCreationSkills{
{Name: "Klettern", Level: 0, Category: "Alltag", Cost: 1},
{Name: "Reiten", Level: 0, Category: "Alltag", Cost: 1},
{Name: "Sprache", Level: 0, Category: "Alltag", Cost: 1},
{Name: "Athletik", Level: 0, Category: "Kampf", Cost: 2},
{Name: "Spießwaffen", Level: 0, Category: "Waffen", Cost: 2},
{Name: "Stielwurfwaffen", Level: 0, Category: "Waffen", Cost: 2},
{Name: "Waffenloser Kampf", Level: 0, Category: "Waffen", Cost: 2},
{Name: "Stichwaffen", Level: 0, Category: "Waffen", Cost: 2},
{Name: "Heilkunde", Level: 0, Category: "Wissen", Cost: 2},
{Name: "Naturkunde", Level: 0, Category: "Wissen", Cost: 2},
},
Spells: models.CharacterCreationSpells{
{Name: "Göttlicher Schutz v. d. Bösen", Cost: 1},
{Name: "Erkennen der Aura", Cost: 1},
{Name: "Heiliger Zorn", Cost: 1},
{Name: "Blutmeisterschaft", Cost: 1},
},
SkillPoints: models.SkillPointsData{},
CurrentStep: 5,
Geschlecht: "Männlich",
Herkunft: "Aran",
Stand: "Mittelschicht",
Glaube: "",
}
err = database.DB.Create(&testSession).Error
assert.NoError(t, err)
t.Run("FinalizeCharacterCreation with valid session", func(t *testing.T) {
// Create the HTTP request to finalize the character creation
// Using the session ID from the test data: 'char_create_1_1756326371'
req, err := http.NewRequest("POST", "/api/characters/sessions/char_create_1_1756326371/finalize", nil)
assert.NoError(t, err)
// Create response recorder
w := httptest.NewRecorder()
// Create Gin context with userID set to 1 (bebe user)
c, _ := gin.CreateTestContext(w)
c.Request = req
c.Set("userID", uint(1)) // Set the userID for bebe user
c.Params = gin.Params{
gin.Param{Key: "sessionId", Value: "char_create_1_1756326371"},
}
// Call the FinalizeCharacterCreation handler
FinalizeCharacterCreation(c)
// Log the response for debugging
t.Logf("Response Status: %d", w.Code)
t.Logf("Response Body: %s", w.Body.String())
// Check if we got a successful response
assert.Equal(t, http.StatusCreated, w.Code, "Status code should be 201 Created")
// Parse and validate response
var response map[string]interface{}
err = json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err, "Response should be valid JSON")
// Validate expected response structure
assert.Contains(t, response, "message", "Response should contain success message")
assert.Contains(t, response, "character_id", "Response should contain character_id")
assert.Contains(t, response, "session_id", "Response should contain session_id")
// Validate response values
assert.Equal(t, "Charakter erfolgreich erstellt", response["message"])
assert.Equal(t, "char_create_1_1756326371", response["session_id"])
// Verify that a character was actually created
characterID := response["character_id"]
assert.NotNil(t, characterID, "Character ID should not be nil")
// Verify character exists in database
var createdChar models.Char
err = database.DB.Preload("Lp").Preload("Ap").Preload("B").Preload("Eigenschaften").Preload("Fertigkeiten").Preload("Waffenfertigkeiten").Preload("Zauber").
First(&createdChar, "id = ?", characterID).Error
assert.NoError(t, err, "Created character should exist in database")
// Validate character data based on the session data
assert.Equal(t, "wergw5 z ", createdChar.Name, "Character name should match session")
assert.Equal(t, "Mensch", createdChar.Rasse, "Character race should match session")
assert.Equal(t, "Priester Streiter", createdChar.Typ, "Character type should match session")
assert.Equal(t, "Aran", createdChar.Herkunft, "Character origin should match session")
assert.Equal(t, "Männlich", createdChar.Gender, "Character gender should match session")
assert.Equal(t, "Mittelschicht", createdChar.SocialClass, "Character status should match session")
// Validate attributes
assert.Equal(t, 9, len(createdChar.Eigenschaften), "Should have 9 attributes (ST, GS, GW, KO, IN, ZT, AU, pA, WK)")
// Create a map for easier attribute validation
attrMap := make(map[string]int)
for _, attr := range createdChar.Eigenschaften {
attrMap[attr.Name] = attr.Value
}
// Validate each attribute matches the session data
assert.Equal(t, 87, attrMap["IN"], "Intelligence should match session")
assert.Equal(t, 74, attrMap["ST"], "Strength should match session")
assert.Equal(t, 65, attrMap["GS"], "Dexterity should match session")
assert.Equal(t, 76, attrMap["GW"], "Agility should match session")
assert.Equal(t, 58, attrMap["KO"], "Constitution should match session")
assert.Equal(t, 83, attrMap["ZT"], "Magic Talent should match session")
assert.Equal(t, 69, attrMap["AU"], "Charisma should match session")
assert.Equal(t, 59, attrMap["pA"], "Personal Charisma should match session")
assert.Equal(t, 72, attrMap["WK"], "Willpower should match session")
// Validate derived values
assert.Equal(t, 17, createdChar.Lp.Max, "LP Max should match session")
assert.Equal(t, 17, createdChar.Lp.Value, "LP Value should equal Max initially")
assert.Equal(t, 33, createdChar.Ap.Max, "AP Max should match session")
assert.Equal(t, 33, createdChar.Ap.Value, "AP Value should equal Max initially")
assert.Equal(t, 8, createdChar.B.Max, "B Max should match session")
assert.Equal(t, 8, createdChar.B.Value, "B Value should equal Max initially")
// Validate static derived values (Resistenz, Abwehr, Zaubern, Raufen)
assert.Equal(t, 11, createdChar.ResistenzKoerper, "Resistenz Körper should match session")
assert.Equal(t, 11, createdChar.ResistenzGeist, "Resistenz Geist should match session")
assert.Equal(t, 11, createdChar.Abwehr, "Abwehr should match session")
assert.Equal(t, 11, createdChar.Zaubern, "Zaubern should match session")
assert.Equal(t, 8, createdChar.Raufen, "Raufen should match session")
// Validate skills were transferred (session has 10 skills: 6 regular skills + 4 weapon skills)
// Regular skills: Klettern, Reiten, Sprache, Athletik, Heilkunde, Naturkunde (6)
// Weapon skills: Spießwaffen, Stielwurfwaffen, Waffenloser Kampf, Stichwaffen (4)
assert.Equal(t, 6, len(createdChar.Fertigkeiten), "Should have 6 regular skills")
assert.Equal(t, 4, len(createdChar.Waffenfertigkeiten), "Should have 4 weapon skills")
// Validate that skills use database initial values (not session levels which were 0)
// Check a few specific skills exist
skillNames := make([]string, len(createdChar.Fertigkeiten))
for i, skill := range createdChar.Fertigkeiten {
skillNames[i] = skill.Name
}
assert.Contains(t, skillNames, "Klettern", "Should contain Klettern skill")
assert.Contains(t, skillNames, "Reiten", "Should contain Reiten skill")
assert.Contains(t, skillNames, "Athletik", "Should contain Athletik skill")
assert.Contains(t, skillNames, "Heilkunde", "Should contain Heilkunde skill")
// Check weapon skills
weaponSkillNames := make([]string, len(createdChar.Waffenfertigkeiten))
for i, skill := range createdChar.Waffenfertigkeiten {
weaponSkillNames[i] = skill.Name
}
assert.Contains(t, weaponSkillNames, "Spießwaffen", "Should contain Spießwaffen weapon skill")
assert.Contains(t, weaponSkillNames, "Stichwaffen", "Should contain Stichwaffen weapon skill")
// Validate spells were transferred (session has 4 spells)
assert.Equal(t, 4, len(createdChar.Zauber), "Should have 4 spells")
// Validate spell names
spellNames := make([]string, len(createdChar.Zauber))
for i, spell := range createdChar.Zauber {
spellNames[i] = spell.Name
}
assert.Contains(t, spellNames, "Göttlicher Schutz v. d. Bösen", "Should contain Göttlicher Schutz v. d. Bösen spell")
assert.Contains(t, spellNames, "Erkennen der Aura", "Should contain Erkennen der Aura spell")
assert.Contains(t, spellNames, "Heiliger Zorn", "Should contain Heiliger Zorn spell")
assert.Contains(t, spellNames, "Blutmeisterschaft", "Should contain Blutmeisterschaft spell")
// Verify session was deleted after successful creation
var deletedSession models.CharacterCreationSession
err = database.DB.Where("id = ?", "char_create_1_1756326371").First(&deletedSession).Error
assert.Error(t, err, "Session should be deleted after character creation")
assert.True(t, errors.Is(err, gorm.ErrRecordNotFound), "Session should not be found in database")
t.Logf("Character successfully created with ID: %.0f", characterID)
t.Logf("Character name: %s", createdChar.Name)
t.Logf("Character has %d skills, %d weapon skills, %d spells",
len(createdChar.Fertigkeiten), len(createdChar.Waffenfertigkeiten), len(createdChar.Zauber))
})
t.Run("FinalizeCharacterCreation with invalid session", func(t *testing.T) {
req, err := http.NewRequest("POST", "/api/characters/sessions/nonexistent_session/finalize", nil)
assert.NoError(t, err)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = req
c.Set("userID", uint(1))
c.Params = gin.Params{
gin.Param{Key: "sessionId", Value: "nonexistent_session"},
}
FinalizeCharacterCreation(c)
assert.Equal(t, http.StatusNotFound, w.Code, "Should return 404 for non-existent session")
var response map[string]interface{}
err = json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Contains(t, response, "error")
assert.Equal(t, "Session not found", response["error"])
})
t.Run("FinalizeCharacterCreation with unauthorized user", func(t *testing.T) {
req, err := http.NewRequest("POST", "/api/characters/sessions/char_create_1_1756326371/finalize", nil)
assert.NoError(t, err)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = req
c.Set("userID", uint(0)) // Invalid userID
c.Params = gin.Params{
gin.Param{Key: "sessionId", Value: "char_create_1_1756326371"},
}
FinalizeCharacterCreation(c)
assert.Equal(t, http.StatusUnauthorized, w.Code, "Should return 401 for unauthorized user")
var response map[string]interface{}
err = json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Contains(t, response, "error")
assert.Equal(t, "Unauthorized", response["error"])
})
}
func TestListCharacters(t *testing.T) {
// Setup test environment
original := os.Getenv("ENVIRONMENT")
os.Setenv("ENVIRONMENT", "test")
t.Cleanup(func() {
if original != "" {
os.Setenv("ENVIRONMENT", original)
} else {
os.Unsetenv("ENVIRONMENT")
}
})
// Setup test database
database.SetupTestDB(true, true)
defer database.ResetTestDB()
err := models.MigrateStructure()
assert.NoError(t, err)
t.Run("ListCharacters Success", func(t *testing.T) {
// Create a test user
u := user.User{}
u.FirstId(1)
token := user.GenerateToken(&u)
// Create a test HTTP request
req, _ := http.NewRequest("GET", "/api/characters", nil)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = req
c.Set("userID", uint(1)) // Set valid userID
// Call the handler
ListCharacters(c)
// Assert response
assert.Equal(t, http.StatusOK, w.Code)
type AllCharacters struct {
SelfOwned []models.CharList `json:"self_owned"`
Others []models.CharList `json:"others"`
}
var response AllCharacters
err = json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
// Response should be an array (could be empty if no characters exist)
assert.IsType(t, AllCharacters{}, response)
})
t.Run("ListCharacters with Invalid User", func(t *testing.T) {
// Create a test user with invalid ID
u := user.User{}
u.FirstId(999) // Non-existent user ID
token := user.GenerateToken(&u)
// Create a test HTTP request
req, _ := http.NewRequest("GET", "/api/characters", nil)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = req
c.Set("userID", uint(999)) // Set invalid userID
// Call the handler
ListCharacters(c)
// Should still return OK with empty list if user has no characters
assert.Equal(t, http.StatusOK, w.Code)
type AllCharacters struct {
SelfOwned []models.CharList `json:"self_owned"`
Others []models.CharList `json:"others"`
}
var response AllCharacters
err = json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, 0, len(response.SelfOwned), "Should return empty list for user with no characters")
})
t.Run("ListCharacters without UserID", func(t *testing.T) {
// Create a test HTTP request without setting userID in context
req, _ := http.NewRequest("GET", "/api/characters", nil)
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = req
// Don't set userID in context - this should trigger an error
// Call the handler
ListCharacters(c)
// Should still return OK with empty list since GetUint("userID") returns 0 for missing userID
assert.Equal(t, http.StatusOK, w.Code)
type AllCharacters struct {
SelfOwned []models.CharList `json:"self_owned"`
Others []models.CharList `json:"others"`
}
var response AllCharacters
err = json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, 0, len(response.SelfOwned), "Should return empty list for userID 0")
})
}