2025-07-25 20:43:28 +02:00
package character
import (
"bytes"
"encoding/json"
2025-08-28 00:46:55 +02:00
"errors"
2026-01-14 18:52:29 +01:00
"fmt"
2025-07-25 20:43:28 +02:00
"net/http"
"net/http/httptest"
2025-08-10 21:01:18 +02:00
"os"
2025-07-25 20:43:28 +02:00
"testing"
"bamort/database"
"bamort/models"
2025-08-27 21:36:06 +02:00
"bamort/user"
2025-07-25 20:43:28 +02:00
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
2025-08-28 00:46:55 +02:00
"gorm.io/gorm"
2025-07-25 20:43:28 +02:00
)
func TestImproveSkillHandler ( t * testing . T ) {
2025-08-10 21:01:18 +02:00
// Setup test environment
original := os . Getenv ( "ENVIRONMENT" )
os . Setenv ( "ENVIRONMENT" , "test" )
t . Cleanup ( func ( ) {
if original != "" {
os . Setenv ( "ENVIRONMENT" , original )
} else {
os . Unsetenv ( "ENVIRONMENT" )
}
} )
2025-07-25 20:43:28 +02:00
// Setup test database
database . SetupTestDB ( true , true )
defer database . ResetTestDB ( )
2025-07-28 21:35:29 +02:00
err := models . MigrateStructure ( )
2025-07-25 20:43:28 +02:00
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
2025-07-28 22:08:19 +02:00
athletikSkill := models.Fertigkeit{
2025-07-25 20:43:28 +02:00
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
2025-08-05 21:12:03 +02:00
ImproveSkill ( c )
2025-07-25 20:43:28 +02:00
// 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
2026-01-12 16:36:35 +01:00
// Note: Athletik is now in "Körper" category (lowest ID from learning_skill_category_difficulties)
// which has different costs than the previous "Kampf" category
2025-07-25 20:43:28 +02:00
expectedResponse := map [ string ] interface { } {
2026-01-12 16:36:35 +01:00
"ep_cost" : float64 ( 0 ) , // JSON numbers are float64
2025-07-25 20:43:28 +02:00
"from_level" : float64 ( 9 ) ,
2026-01-12 16:36:35 +01:00
"gold_cost" : float64 ( 0 ) ,
2025-07-25 20:43:28 +02:00
"message" : "Fertigkeit erfolgreich verbessert" ,
2026-01-12 16:36:35 +01:00
"remaining_ep" : float64 ( 260 ) ,
"remaining_gold" : float64 ( 310 ) ,
2025-07-25 20:43:28 +02:00
"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
2025-07-28 21:35:29 +02:00
var updatedChar models . Char
2025-07-25 20:43:28 +02:00
err = updatedChar . FirstID ( "20" )
assert . NoError ( t , err )
// Check that EP was deducted correctly
2026-01-12 16:36:35 +01:00
// Athletik is now in "Körper" category which has different costs
assert . Equal ( t , 260 , updatedChar . Erfahrungsschatz . EP , "Character should have 260 EP remaining" )
2025-07-25 20:43:28 +02:00
// Check that Gold was deducted correctly
2026-01-12 16:36:35 +01:00
assert . Equal ( t , 310 , updatedChar . Vermoegen . Goldstuecke , "Character should have 310 Gold remaining" )
2025-07-25 20:43:28 +02:00
t . Logf ( "Test completed successfully!" )
t . Logf ( "EP: %d -> %d (cost: %.0f)" , 326 , updatedChar . Erfahrungsschatz . EP , response [ "ep_cost" ] )
2025-12-19 17:04:20 +01:00
t . Logf ( "Gold: %d -> %d (cost: %.0f)" , 390 , updatedChar . Vermoegen . Goldstuecke , response [ "gold_cost" ] )
2025-07-25 20:43:28 +02:00
} )
}
2025-08-21 20:57:55 +02:00
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 {
2025-10-03 21:23:08 +02:00
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" ] )
2025-08-21 20:57:55 +02:00
2025-10-03 21:23:08 +02:00
learnCost := skillMap [ "leCost" ] . ( float64 )
2025-08-21 20:57:55 +02:00
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" )
}
} )
}
}
2025-08-27 21:36:06 +02:00
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 ,
2026-01-27 09:12:09 +01:00
expectError : false ,
2025-08-27 21:36:06 +02:00
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" )
}
} )
}
}
2025-08-28 00:46:55 +02:00
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",
2026-01-02 12:07:28 +01:00
Role: "admin",
2025-08-28 00:46:55 +02:00
}
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
2025-12-27 08:33:42 +01:00
err = database . DB . Preload ( "Lp" ) . Preload ( "Ap" ) . Preload ( "B" ) . Preload ( "Eigenschaften" ) . Preload ( "Fertigkeiten" ) . Preload ( "Waffenfertigkeiten" ) . Preload ( "Zauber" ) .
2025-08-28 00:46:55 +02:00
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
2026-01-27 09:12:09 +01:00
assert . Equal ( t , 87 , attrMap [ "In" ] , "Intelligence should match session" )
assert . Equal ( t , 89 , attrMap [ "St" ] , "Strength should match session" )
assert . Equal ( t , 64 , attrMap [ "Gs" ] , "Dexterity should match session" )
assert . Equal ( t , 77 , attrMap [ "Gw" ] , "Agility should match session" )
assert . Equal ( t , 71 , attrMap [ "Ko" ] , "Constitution should match session" )
assert . Equal ( t , 44 , attrMap [ "Zt" ] , "Magic Talent should match session" )
assert . Equal ( t , 87 , attrMap [ "Au" ] , "Charisma should match session" )
assert . Equal ( t , 33 , attrMap [ "pA" ] , "Personal Charisma should match session" )
assert . Equal ( t , 27 , attrMap [ "Wk" ] , "Willpower should match session" )
2025-08-28 00:46:55 +02:00
// Validate derived values
2026-01-27 09:12:09 +01:00
assert . Equal ( t , 11 , createdChar . Lp . Max , "LP Max should match session" )
assert . Equal ( t , 11 , createdChar . Lp . Value , "LP Value should equal Max initially" )
assert . Equal ( t , 14 , createdChar . Ap . Max , "AP Max should match session" )
assert . Equal ( t , 14 , createdChar . Ap . Value , "AP Value should equal Max initially" )
assert . Equal ( t , 26 , createdChar . B . Max , "B Max should match session" )
assert . Equal ( t , 26 , createdChar . B . Value , "B Value should equal Max initially" )
2025-08-28 00:46:55 +02:00
2025-12-27 08:33:42 +01:00
// 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" )
2025-08-28 00:46:55 +02:00
// 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" ] )
} )
}
2025-08-28 18:22:52 +02:00
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 )
2025-10-22 10:56:55 +02:00
type AllCharacters struct {
SelfOwned [ ] models . CharList ` json:"self_owned" `
Others [ ] models . CharList ` json:"others" `
}
2025-08-28 18:22:52 +02:00
2025-10-22 10:56:55 +02:00
var response AllCharacters
2025-08-28 18:22:52 +02:00
err = json . Unmarshal ( w . Body . Bytes ( ) , & response )
assert . NoError ( t , err )
// Response should be an array (could be empty if no characters exist)
2025-10-22 10:56:55 +02:00
assert . IsType ( t , AllCharacters { } , response )
2025-08-28 18:22:52 +02:00
} )
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 )
2025-10-22 10:56:55 +02:00
type AllCharacters struct {
SelfOwned [ ] models . CharList ` json:"self_owned" `
Others [ ] models . CharList ` json:"others" `
}
2025-08-28 18:22:52 +02:00
2025-10-22 10:56:55 +02:00
var response AllCharacters
2025-08-28 18:22:52 +02:00
err = json . Unmarshal ( w . Body . Bytes ( ) , & response )
assert . NoError ( t , err )
2025-10-22 10:56:55 +02:00
assert . Equal ( t , 0 , len ( response . SelfOwned ) , "Should return empty list for user with no characters" )
2025-08-28 18:22:52 +02:00
} )
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 )
2025-10-22 10:56:55 +02:00
type AllCharacters struct {
SelfOwned [ ] models . CharList ` json:"self_owned" `
Others [ ] models . CharList ` json:"others" `
}
2025-08-28 18:22:52 +02:00
2025-10-22 10:56:55 +02:00
var response AllCharacters
2025-08-28 18:22:52 +02:00
err = json . Unmarshal ( w . Body . Bytes ( ) , & response )
assert . NoError ( t , err )
2025-10-22 10:56:55 +02:00
assert . Equal ( t , 0 , len ( response . SelfOwned ) , "Should return empty list for userID 0" )
2025-08-28 18:22:52 +02:00
} )
}
2026-01-14 18:52:29 +01:00
func TestGetDatasheetOptions ( 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 )
/*
// Populate misc lookup data
2026-01-14 21:13:25 +01:00
err = gsmaster.PopulateMiscLookupData()
2026-01-14 18:52:29 +01:00
assert.NoError(t, err)
*/
// Create test character with weapon skill
testChar := & models . Char {
BamortBase : models . BamortBase {
Name : "Test Character" ,
} ,
Typ : "Krieger" ,
Rasse : "Mensch" ,
Waffenfertigkeiten : [ ] models . SkWaffenfertigkeit {
{
SkFertigkeit : models . SkFertigkeit {
BamortCharTrait : models . BamortCharTrait {
BamortBase : models . BamortBase {
Name : "Langschwert" ,
} ,
} ,
} ,
} ,
} ,
}
err = testChar . Create ( )
assert . NoError ( t , err )
assert . NotZero ( t , testChar . ID , "Character ID should be set after Create" )
// Setup Gin context
gin . SetMode ( gin . TestMode )
w := httptest . NewRecorder ( )
c , _ := gin . CreateTestContext ( w )
// Use string conversion of actual character ID
c . Params = gin . Params { { Key : "id" , Value : fmt . Sprintf ( "%d" , testChar . ID ) } }
// Call the handler
GetDatasheetOptions ( c )
// Assert response
assert . Equal ( t , http . StatusOK , w . Code )
var response map [ string ] interface { }
err = json . Unmarshal ( w . Body . Bytes ( ) , & response )
assert . NoError ( t , err )
// Verify all expected keys exist
assert . Contains ( t , response , "gender" )
assert . Contains ( t , response , "races" )
assert . Contains ( t , response , "origins" )
assert . Contains ( t , response , "social_classes" )
assert . Contains ( t , response , "faiths" )
assert . Contains ( t , response , "handedness" )
assert . Contains ( t , response , "specializations" )
// Verify data from database
genders := response [ "gender" ] . ( [ ] interface { } )
assert . Equal ( t , 3 , len ( genders ) )
assert . Contains ( t , genders , "divers" )
assert . Contains ( t , genders , "männlich" )
assert . Contains ( t , genders , "weiblich" )
races := response [ "races" ] . ( [ ] interface { } )
assert . Equal ( t , 5 , len ( races ) )
assert . Contains ( t , races , "Elf" )
assert . Contains ( t , races , "Mensch" )
origins := response [ "origins" ] . ( [ ] interface { } )
assert . Equal ( t , 15 , len ( origins ) )
2026-01-27 09:12:09 +01:00
assert . Contains ( t , origins , "Alba" )
2026-01-14 18:52:29 +01:00
socialClasses := response [ "social_classes" ] . ( [ ] interface { } )
2026-01-27 09:12:09 +01:00
assert . Equal ( t , 4 , len ( socialClasses ) )
2026-01-14 18:52:29 +01:00
assert . Contains ( t , socialClasses , "Adel" )
assert . Contains ( t , socialClasses , "Mittelschicht" )
faiths := response [ "faiths" ] . ( [ ] interface { } )
2026-02-03 17:21:43 +01:00
assert . Equal ( t , 15 , len ( faiths ) )
2026-01-14 18:52:29 +01:00
assert . Contains ( t , faiths , "Druide" )
assert . Contains ( t , faiths , "Keine" )
2026-02-03 17:21:43 +01:00
assert . Contains ( t , faiths , "Torkin" )
assert . NotContains ( t , faiths , "" )
2026-01-14 18:52:29 +01:00
handedness := response [ "handedness" ] . ( [ ] interface { } )
assert . Equal ( t , 3 , len ( handedness ) )
assert . Contains ( t , handedness , "beidhändig" )
assert . Contains ( t , handedness , "links" )
assert . Contains ( t , handedness , "rechts" )
}
func TestGetDatasheetOptions_CharacterNotFound ( 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 )
// Setup Gin context with non-existent character ID
gin . SetMode ( gin . TestMode )
w := httptest . NewRecorder ( )
c , _ := gin . CreateTestContext ( w )
c . Params = gin . Params { { Key : "id" , Value : "99999" } }
// Call the handler
GetDatasheetOptions ( c )
// Assert error response
assert . Equal ( t , http . StatusNotFound , w . Code )
}
2026-01-27 08:19:18 +01:00
func TestSearchBeliefs ( 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 )
defer database . ResetTestDB ( )
err := models . MigrateStructure ( )
assert . NoError ( t , err )
// Ensure game systems exist in the test DB
// Ensure game system rows exist (use INSERT OR IGNORE to avoid unique constraint errors)
database . DB . Exec ( "INSERT OR IGNORE INTO game_systems(code,name,description,is_active,created_at,modified_at) VALUES (?,?,?,?,strftime('%s','now'),strftime('%s','now'))" , "M5" , "M5" , "" , true )
2026-02-01 17:00:40 +01:00
// Create some test believes for GameSystemId: 1
2026-01-29 09:14:33 +01:00
b1 := & models . Believe { GameSystemId : 1 , Name : "TestFaithOne" , SourceID : 1 }
b2 := & models . Believe { GameSystemId : 1 , Name : "OtherFaith" , SourceID : 1 }
2026-01-27 08:19:18 +01:00
err = b1 . Create ( )
assert . NoError ( t , err )
err = b2 . Create ( )
assert . NoError ( t , err )
gin . SetMode ( gin . TestMode )
tests := [ ] struct {
name string
q string
gameSystem string
expectHits int
expectError bool
} {
2026-01-29 10:05:07 +01:00
{ name : "GameSystemID 1" , q : "Test" , gameSystem : "M5" , expectHits : 1 , expectError : false } ,
{ name : "GameSystem M5" , q : "Test" , gameSystem : "M5" , expectHits : 2 , expectError : false } ,
2026-01-27 08:19:18 +01:00
{ name : "GameSystem XYZ" , q : "Test" , gameSystem : "XYZ" , expectHits : 0 , expectError : true } ,
2026-01-29 10:05:07 +01:00
{ name : "GameSystem not set (default)" , q : "Test" , gameSystem : "M5" , expectHits : 0 , expectError : false } ,
2026-01-27 08:19:18 +01:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
// Build request URL with query params
url := "/api/characters/beliefs?q=" + tt . q
if tt . gameSystem != "" {
url = url + "&game_system=" + tt . gameSystem
}
req , _ := http . NewRequest ( "GET" , url , nil )
w := httptest . NewRecorder ( )
c , _ := gin . CreateTestContext ( w )
c . Request = req
SearchBeliefs ( c )
if tt . expectError {
assert . NotEqual ( t , http . StatusOK , w . Code )
return
} else {
assert . Equal ( t , http . StatusOK , w . Code )
}
var resp map [ string ] [ ] string
err := json . Unmarshal ( w . Body . Bytes ( ) , & resp )
assert . NoError ( t , err )
beliefs , ok := resp [ "beliefs" ]
assert . True ( t , ok , "response should contain beliefs" )
assert . IsType ( t , [ ] string { } , beliefs )
if tt . expectHits > 0 {
assert . Greater ( t , len ( beliefs ) , 0 )
}
} )
}
}
2026-02-17 23:05:42 +01:00
func TestToFeChar ( t * testing . T ) {
// Setup test database
database . SetupTestDB ( true )
defer database . ResetTestDB ( )
char := & models . Char { }
char . FirstID ( "18" )
feChar := ToFeChar ( char )
assert . Equal ( t , "18" , feChar . ID )
assert . Equal ( t , 2 , feChar . Fertigkeiten [ 6 ] . Bonus )
}