package character import ( "bytes" "encoding/json" "fmt" "net/http" "net/http/httptest" "os" "testing" "bamort/database" "bamort/bmrt/models" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func setupSessionTestEnv(t *testing.T) { t.Helper() original := os.Getenv("ENVIRONMENT") os.Setenv("ENVIRONMENT", "test") t.Cleanup(func() { if original != "" { os.Setenv("ENVIRONMENT", original) } else { os.Unsetenv("ENVIRONMENT") } }) database.SetupTestDB(true, true) t.Cleanup(database.ResetTestDB) require.NoError(t, models.MigrateStructure()) gin.SetMode(gin.TestMode) } const testUserIDSession = uint(4) func createTestSession(t *testing.T) string { t.Helper() w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("userID", testUserIDSession) c.Request = httptest.NewRequest(http.MethodPost, "/api/characters/create-session", nil) CreateCharacterSession(c) require.Equal(t, http.StatusCreated, w.Code) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) sessionID, ok := resp["session_id"].(string) require.True(t, ok, "session_id should be a string") return sessionID } func TestCreateCharacterSession(t *testing.T) { setupSessionTestEnv(t) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("userID", testUserIDSession) c.Request = httptest.NewRequest(http.MethodPost, "/api/characters/create-session", nil) CreateCharacterSession(c) assert.Equal(t, http.StatusCreated, w.Code) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.NotEmpty(t, resp["session_id"]) assert.NotEmpty(t, resp["expires_at"]) } func TestCreateCharacterSessionUnauthorized(t *testing.T) { setupSessionTestEnv(t) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) // userID NOT set - simulates missing authentication c.Request = httptest.NewRequest(http.MethodPost, "/api/characters/create-session", nil) CreateCharacterSession(c) assert.Equal(t, http.StatusUnauthorized, w.Code) } func TestListCharacterSessions(t *testing.T) { setupSessionTestEnv(t) // Create a session first createTestSession(t) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("userID", testUserIDSession) c.Request = httptest.NewRequest(http.MethodGet, "/api/characters/create-sessions", nil) ListCharacterSessions(c) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.NotNil(t, resp["sessions"]) count, ok := resp["count"].(float64) assert.True(t, ok) assert.GreaterOrEqual(t, int(count), 1) } func TestGetCharacterSession(t *testing.T) { setupSessionTestEnv(t) sessionID := createTestSession(t) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("userID", testUserIDSession) c.Params = gin.Params{{Key: "sessionId", Value: sessionID}} c.Request = httptest.NewRequest(http.MethodGet, "/api/characters/create-session/"+sessionID, nil) GetCharacterSession(c) assert.Equal(t, http.StatusOK, w.Code) var resp models.CharacterCreationSession require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.Equal(t, sessionID, resp.ID) } func TestGetCharacterSessionNotFound(t *testing.T) { setupSessionTestEnv(t) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("userID", testUserIDSession) c.Params = gin.Params{{Key: "sessionId", Value: "nonexistent_session_id"}} c.Request = httptest.NewRequest(http.MethodGet, "/", nil) GetCharacterSession(c) assert.Equal(t, http.StatusNotFound, w.Code) } func TestUpdateCharacterBasicInfo(t *testing.T) { setupSessionTestEnv(t) sessionID := createTestSession(t) reqBody := map[string]interface{}{ "name": "Torin Eisenstein", "geschlecht": "male", "rasse": "Mensch", "typ": "Kr", "herkunft": "Norden", "stand": "Händler", "glaube": "Gott1", } body, _ := json.Marshal(reqBody) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("userID", testUserIDSession) c.Params = gin.Params{{Key: "sessionId", Value: sessionID}} c.Request = httptest.NewRequest(http.MethodPut, "/", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") UpdateCharacterBasicInfo(c) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.Equal(t, float64(2), resp["current_step"]) } func TestUpdateCharacterBasicInfoMissingField(t *testing.T) { setupSessionTestEnv(t) sessionID := createTestSession(t) // Missing required fields reqBody := map[string]interface{}{ "name": "Incomplete", } body, _ := json.Marshal(reqBody) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("userID", testUserIDSession) c.Params = gin.Params{{Key: "sessionId", Value: sessionID}} c.Request = httptest.NewRequest(http.MethodPut, "/", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") UpdateCharacterBasicInfo(c) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestUpdateCharacterAttributes(t *testing.T) { setupSessionTestEnv(t) sessionID := createTestSession(t) reqBody := map[string]interface{}{ "st": 50, "gs": 40, "gw": 45, "ko": 55, "in": 60, "zt": 30, "au": 35, } body, _ := json.Marshal(reqBody) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("userID", testUserIDSession) c.Params = gin.Params{{Key: "sessionId", Value: sessionID}} c.Request = httptest.NewRequest(http.MethodPut, "/", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") UpdateCharacterAttributes(c) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.Equal(t, float64(3), resp["current_step"]) } func TestUpdateCharacterAttributesOutOfRange(t *testing.T) { setupSessionTestEnv(t) sessionID := createTestSession(t) // Value out of range (max 100) reqBody := map[string]interface{}{ "st": 150, "gs": 40, "gw": 45, "ko": 55, "in": 60, "zt": 30, "au": 35, } body, _ := json.Marshal(reqBody) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("userID", testUserIDSession) c.Params = gin.Params{{Key: "sessionId", Value: sessionID}} c.Request = httptest.NewRequest(http.MethodPut, "/", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") UpdateCharacterAttributes(c) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestUpdateCharacterDerivedValues(t *testing.T) { setupSessionTestEnv(t) sessionID := createTestSession(t) reqBody := map[string]interface{}{ "pa": 10, "wk": 8, "lp_max": 20, "ap_max": 100, "b_max": 10, "resistenz_koerper": 5, "resistenz_geist": 5, "resistenz_bonus_koerper": 0, "resistenz_bonus_geist": 0, "abwehr": 5, "abwehr_bonus": 0, "ausdauer_bonus": 0, "angriffs_bonus": 0, "zaubern": 5, "zauber_bonus": 0, "raufen": 5, "schadens_bonus": 0, "sg": 3, "gg": 0, "gp": 0, } body, _ := json.Marshal(reqBody) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("userID", testUserIDSession) c.Params = gin.Params{{Key: "sessionId", Value: sessionID}} c.Request = httptest.NewRequest(http.MethodPut, "/", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") UpdateCharacterDerivedValues(c) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.Equal(t, float64(4), resp["current_step"]) } func TestUpdateCharacterSkills(t *testing.T) { setupSessionTestEnv(t) sessionID := createTestSession(t) reqBody := map[string]interface{}{ "skills": []map[string]interface{}{ {"name": "Athletik", "level": 5, "category": "Körper"}, }, "spells": []map[string]interface{}{}, "skill_points": map[string]interface{}{}, } body, _ := json.Marshal(reqBody) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("userID", testUserIDSession) c.Params = gin.Params{{Key: "sessionId", Value: sessionID}} c.Request = httptest.NewRequest(http.MethodPut, "/", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") UpdateCharacterSkills(c) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.Equal(t, float64(5), resp["current_step"]) } func TestDeleteCharacterSession(t *testing.T) { setupSessionTestEnv(t) sessionID := createTestSession(t) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("userID", testUserIDSession) c.Params = gin.Params{{Key: "sessionId", Value: sessionID}} c.Request = httptest.NewRequest(http.MethodDelete, "/api/characters/create-session/"+sessionID, nil) DeleteCharacterSession(c) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.Equal(t, "Session gelöscht", resp["message"]) // Verify session no longer exists var count int64 database.DB.Model(&models.CharacterCreationSession{}).Where("id = ?", sessionID).Count(&count) assert.Equal(t, int64(0), count) } func TestDeleteCharacterSessionNotFound(t *testing.T) { setupSessionTestEnv(t) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("userID", testUserIDSession) c.Params = gin.Params{{Key: "sessionId", Value: "nonexistent_session"}} c.Request = httptest.NewRequest(http.MethodDelete, "/", nil) DeleteCharacterSession(c) assert.Equal(t, http.StatusNotFound, w.Code) } func TestFinalizeCharacterCreationIncomplete(t *testing.T) { setupSessionTestEnv(t) sessionID := createTestSession(t) // Session is at step 1 (incomplete) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("userID", testUserIDSession) c.Params = gin.Params{{Key: "sessionId", Value: sessionID}} c.Request = httptest.NewRequest(http.MethodPost, "/", nil) FinalizeCharacterCreation(c) assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, w.Body.String(), "not complete") } func TestFinalizeCharacterCreationViaSteps(t *testing.T) { setupSessionTestEnv(t) sessionID := createTestSession(t) // Progress through all steps steps := []struct { handler func(*gin.Context) body map[string]interface{} }{ { UpdateCharacterBasicInfo, map[string]interface{}{ "name": "Torin Test", "geschlecht": "male", "rasse": "Mensch", "typ": "Kr", "herkunft": "Norden", "stand": "Händler", }, }, { UpdateCharacterAttributes, map[string]interface{}{ "st": 50, "gs": 40, "gw": 45, "ko": 55, "in": 60, "zt": 30, "au": 35, }, }, { UpdateCharacterDerivedValues, map[string]interface{}{ "pa": 10, "wk": 8, "lp_max": 20, "ap_max": 100, "b_max": 10, "resistenz_koerper": 5, "resistenz_geist": 5, "resistenz_bonus_koerper": 0, "resistenz_bonus_geist": 0, "abwehr": 5, "abwehr_bonus": 0, "ausdauer_bonus": 0, "angriffs_bonus": 0, "zaubern": 5, "zauber_bonus": 0, "raufen": 5, "schadens_bonus": 0, "sg": 3, "gg": 0, "gp": 0, }, }, { UpdateCharacterSkills, map[string]interface{}{ "skills": []map[string]interface{}{}, "spells": []map[string]interface{}{}, "skill_points": map[string]interface{}{}, }, }, } for _, step := range steps { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("userID", testUserIDSession) c.Params = gin.Params{{Key: "sessionId", Value: sessionID}} body, _ := json.Marshal(step.body) c.Request = httptest.NewRequest(http.MethodPut, "/", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") step.handler(c) require.Equal(t, http.StatusOK, w.Code, fmt.Sprintf("Step failed: %s", w.Body.String())) } // Now finalize w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("userID", testUserIDSession) c.Params = gin.Params{{Key: "sessionId", Value: sessionID}} c.Request = httptest.NewRequest(http.MethodPost, "/", nil) FinalizeCharacterCreation(c) assert.Equal(t, http.StatusCreated, w.Code, "Finalize response: "+w.Body.String()) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.NotNil(t, resp["character_id"]) }