042a1d4773
* introduced central package registry by package init function * dynamic registration of routes, model, migrations and initializers. * setting a docker compose project name to prevent shutdown of other containers with the same (composer)name * ai documentation * app template * Create tests for ALL API entpoints in ALL packages Based on current data. Ensure that all API endpoints used in frontend are tested. These tests are crucial for the next refactoring tasks. * adopting agent instructions for a more consistent coding style * added desired module layout and debugging information * Fix All Failing tests All failing tests are fixed now that makes the refactoring more easy since all tests must pass * restored routes for maintenance * added common translations * added new tests for API Endpoint * Merge branch 'separate_business_logic' * added lern and skill improvement cost editing * Set Docker image tag when building to prevent rebuild when nothing has changed * add and remove PP for Weaponskill fixed * add and remove PP for same named skills fixed * add new task
394 lines
11 KiB
Go
394 lines
11 KiB
Go
package character
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"testing"
|
|
|
|
"bamort/bmrt/models"
|
|
"bamort/database"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func setupPPTestEnv(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)
|
|
}
|
|
|
|
func createCharWithSkillPP(t *testing.T, skillName string, ppAmount int) *models.Char {
|
|
t.Helper()
|
|
char := &models.Char{
|
|
BamortBase: models.BamortBase{Name: "PP Test Char"},
|
|
UserID: 4,
|
|
Rasse: "Mensch",
|
|
Typ: "Kr",
|
|
}
|
|
require.NoError(t, database.DB.Create(char).Error)
|
|
|
|
skill := &models.SkFertigkeit{
|
|
BamortCharTrait: models.BamortCharTrait{
|
|
BamortBase: models.BamortBase{Name: skillName},
|
|
CharacterID: char.ID,
|
|
UserID: 4,
|
|
},
|
|
Fertigkeitswert: 5,
|
|
Pp: ppAmount,
|
|
Improvable: true,
|
|
}
|
|
require.NoError(t, database.DB.Create(skill).Error)
|
|
|
|
return char
|
|
}
|
|
|
|
func TestGetPracticePoints(t *testing.T) {
|
|
setupPPTestEnv(t)
|
|
|
|
char := createCharWithSkillPP(t, "Athletik", 3)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("userID", uint(4))
|
|
c.Params = gin.Params{{Key: "id", Value: fmt.Sprintf("%d", char.ID)}}
|
|
c.Request = httptest.NewRequest(http.MethodGet, "/", nil)
|
|
|
|
GetPracticePoints(c)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var resp []PracticePointResponse
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
|
require.Len(t, resp, 1)
|
|
assert.Equal(t, "Athletik", resp[0].SkillName)
|
|
assert.Equal(t, 3, resp[0].Amount)
|
|
}
|
|
|
|
func TestGetPracticePointsEmpty(t *testing.T) {
|
|
setupPPTestEnv(t)
|
|
|
|
// A character with zero PP skills
|
|
char := createCharWithSkillPP(t, "Athletik", 0)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("userID", uint(4))
|
|
c.Params = gin.Params{{Key: "id", Value: fmt.Sprintf("%d", char.ID)}}
|
|
c.Request = httptest.NewRequest(http.MethodGet, "/", nil)
|
|
|
|
GetPracticePoints(c)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
// Should return null or empty
|
|
body := w.Body.String()
|
|
assert.True(t, body == "null" || body == "[]", "Empty PP list should return null or empty array, got: "+body)
|
|
}
|
|
|
|
func TestGetPracticePointsCharNotFound(t *testing.T) {
|
|
setupPPTestEnv(t)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("userID", uint(4))
|
|
c.Params = gin.Params{{Key: "id", Value: "99999"}}
|
|
c.Request = httptest.NewRequest(http.MethodGet, "/", nil)
|
|
|
|
GetPracticePoints(c)
|
|
|
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
|
}
|
|
|
|
func TestUpdatePracticePoints(t *testing.T) {
|
|
setupPPTestEnv(t)
|
|
|
|
char := createCharWithSkillPP(t, "Athletik", 1)
|
|
|
|
updates := []PracticePointResponse{
|
|
{SkillName: "Athletik", Amount: 5},
|
|
}
|
|
body, _ := json.Marshal(updates)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("userID", uint(4))
|
|
c.Params = gin.Params{{Key: "id", Value: fmt.Sprintf("%d", char.ID)}}
|
|
c.Request = httptest.NewRequest(http.MethodPut, "/", bytes.NewBuffer(body))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
UpdatePracticePoints(c)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
}
|
|
|
|
func TestUpdatePracticePointsNotOwner(t *testing.T) {
|
|
setupPPTestEnv(t)
|
|
|
|
char := createCharWithSkillPP(t, "Athletik", 1)
|
|
|
|
updates := []PracticePointResponse{{SkillName: "Athletik", Amount: 5}}
|
|
body, _ := json.Marshal(updates)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("userID", uint(99))
|
|
c.Params = gin.Params{{Key: "id", Value: fmt.Sprintf("%d", char.ID)}}
|
|
c.Request = httptest.NewRequest(http.MethodPut, "/", bytes.NewBuffer(body))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
UpdatePracticePoints(c)
|
|
|
|
assert.Equal(t, http.StatusForbidden, w.Code)
|
|
}
|
|
|
|
func TestAddPracticePoint(t *testing.T) {
|
|
setupPPTestEnv(t)
|
|
|
|
char := createCharWithSkillPP(t, "Athletik", 0)
|
|
|
|
reqBody := map[string]interface{}{
|
|
"skill_name": "Athletik",
|
|
"amount": 1,
|
|
}
|
|
body, _ := json.Marshal(reqBody)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("userID", uint(4))
|
|
c.Params = gin.Params{{Key: "id", Value: fmt.Sprintf("%d", char.ID)}}
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(body))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
AddPracticePoint(c)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var resp PracticePointActionResponse
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
|
assert.True(t, resp.Success)
|
|
assert.Equal(t, "Athletik", resp.TargetSkill)
|
|
}
|
|
|
|
func TestAddPracticePointSkillNotFound(t *testing.T) {
|
|
setupPPTestEnv(t)
|
|
|
|
char := createCharWithSkillPP(t, "Athletik", 0)
|
|
|
|
reqBody := map[string]interface{}{
|
|
"skill_name": "Zauberei",
|
|
"amount": 1,
|
|
}
|
|
body, _ := json.Marshal(reqBody)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("userID", uint(4))
|
|
c.Params = gin.Params{{Key: "id", Value: fmt.Sprintf("%d", char.ID)}}
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(body))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
AddPracticePoint(c)
|
|
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func TestUsePracticePoint(t *testing.T) {
|
|
setupPPTestEnv(t)
|
|
|
|
char := createCharWithSkillPP(t, "Athletik", 3)
|
|
|
|
reqBody := map[string]interface{}{
|
|
"skill_name": "Athletik",
|
|
"amount": 1,
|
|
}
|
|
body, _ := json.Marshal(reqBody)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("userID", uint(4))
|
|
c.Params = gin.Params{{Key: "id", Value: fmt.Sprintf("%d", char.ID)}}
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(body))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
UsePracticePoint(c)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var resp PracticePointActionResponse
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
|
assert.True(t, resp.Success)
|
|
}
|
|
|
|
func TestUsePracticePointInsufficientPP(t *testing.T) {
|
|
setupPPTestEnv(t)
|
|
|
|
char := createCharWithSkillPP(t, "Athletik", 0)
|
|
|
|
reqBody := map[string]interface{}{
|
|
"skill_name": "Athletik",
|
|
"amount": 5,
|
|
}
|
|
body, _ := json.Marshal(reqBody)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("userID", uint(4))
|
|
c.Params = gin.Params{{Key: "id", Value: fmt.Sprintf("%d", char.ID)}}
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(body))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
UsePracticePoint(c)
|
|
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
// createCharWithTwoSameNameSkills creates a character with two "Sprache" skills
|
|
// distinguished only by their Beschreibung ("Elfisch" and "Zwergisch").
|
|
func createCharWithTwoSameNameSkills(t *testing.T) (*models.Char, *models.SkFertigkeit, *models.SkFertigkeit) {
|
|
t.Helper()
|
|
char := &models.Char{
|
|
BamortBase: models.BamortBase{Name: "Duplicate Skill Char"},
|
|
UserID: 4,
|
|
Rasse: "Mensch",
|
|
Typ: "Kr",
|
|
}
|
|
require.NoError(t, database.DB.Create(char).Error)
|
|
|
|
skill1 := &models.SkFertigkeit{
|
|
BamortCharTrait: models.BamortCharTrait{
|
|
BamortBase: models.BamortBase{Name: "Sprache"},
|
|
CharacterID: char.ID,
|
|
UserID: 4,
|
|
},
|
|
Beschreibung: "Elfisch",
|
|
Fertigkeitswert: 5,
|
|
Pp: 0,
|
|
Improvable: true,
|
|
}
|
|
require.NoError(t, database.DB.Create(skill1).Error)
|
|
|
|
skill2 := &models.SkFertigkeit{
|
|
BamortCharTrait: models.BamortCharTrait{
|
|
BamortBase: models.BamortBase{Name: "Sprache"},
|
|
CharacterID: char.ID,
|
|
UserID: 4,
|
|
},
|
|
Beschreibung: "Zwergisch",
|
|
Fertigkeitswert: 3,
|
|
Pp: 0,
|
|
Improvable: true,
|
|
}
|
|
require.NoError(t, database.DB.Create(skill2).Error)
|
|
|
|
return char, skill1, skill2
|
|
}
|
|
|
|
func TestAddPracticePoint_DuplicateSkillName_WithDescription(t *testing.T) {
|
|
setupPPTestEnv(t)
|
|
|
|
char, skill1, skill2 := createCharWithTwoSameNameSkills(t)
|
|
_ = skill1
|
|
|
|
reqBody := map[string]interface{}{
|
|
"skill_name": "Sprache",
|
|
"skill_description": "Zwergisch",
|
|
"amount": 1,
|
|
}
|
|
body, _ := json.Marshal(reqBody)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("userID", uint(4))
|
|
c.Params = gin.Params{{Key: "id", Value: fmt.Sprintf("%d", char.ID)}}
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(body))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
AddPracticePoint(c)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
|
|
// Verify only the Zwergisch skill got the PP
|
|
var updatedSkill2 models.SkFertigkeit
|
|
require.NoError(t, database.DB.First(&updatedSkill2, skill2.ID).Error)
|
|
assert.Equal(t, 1, updatedSkill2.Pp, "Zwergisch should have 1 PP")
|
|
|
|
var updatedSkill1 models.SkFertigkeit
|
|
require.NoError(t, database.DB.First(&updatedSkill1, skill1.ID).Error)
|
|
assert.Equal(t, 0, updatedSkill1.Pp, "Elfisch should still have 0 PP")
|
|
}
|
|
|
|
func TestUsePracticePoint_DuplicateSkillName_WithDescription(t *testing.T) {
|
|
setupPPTestEnv(t)
|
|
|
|
char, skill1, _ := createCharWithTwoSameNameSkills(t)
|
|
|
|
// Give only the Elfisch skill some PP
|
|
require.NoError(t, database.DB.Model(skill1).Update("pp", 3).Error)
|
|
|
|
reqBody := map[string]interface{}{
|
|
"skill_name": "Sprache",
|
|
"skill_description": "Elfisch",
|
|
"amount": 1,
|
|
}
|
|
body, _ := json.Marshal(reqBody)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("userID", uint(4))
|
|
c.Params = gin.Params{{Key: "id", Value: fmt.Sprintf("%d", char.ID)}}
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(body))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
UsePracticePoint(c)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
|
|
var updatedSkill1 models.SkFertigkeit
|
|
require.NoError(t, database.DB.First(&updatedSkill1, skill1.ID).Error)
|
|
assert.Equal(t, 2, updatedSkill1.Pp, "Elfisch should have 2 PP after using 1")
|
|
}
|
|
|
|
func TestGetPracticePoints_IncludesDescription(t *testing.T) {
|
|
setupPPTestEnv(t)
|
|
|
|
char, _, _ := createCharWithTwoSameNameSkills(t)
|
|
|
|
// Give both skills some PP
|
|
require.NoError(t, database.DB.Model(&models.SkFertigkeit{}).
|
|
Where("character_id = ? AND beschreibung = ?", char.ID, "Elfisch").
|
|
Update("pp", 2).Error)
|
|
require.NoError(t, database.DB.Model(&models.SkFertigkeit{}).
|
|
Where("character_id = ? AND beschreibung = ?", char.ID, "Zwergisch").
|
|
Update("pp", 1).Error)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("userID", uint(4))
|
|
c.Params = gin.Params{{Key: "id", Value: fmt.Sprintf("%d", char.ID)}}
|
|
c.Request = httptest.NewRequest(http.MethodGet, "/", nil)
|
|
|
|
GetPracticePoints(c)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var resp []PracticePointResponse
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
|
require.Len(t, resp, 2)
|
|
// Both entries should have SkillDescription set
|
|
for _, pp := range resp {
|
|
assert.NotEmpty(t, pp.SkillDescription, "SkillDescription must be set for disambiguation")
|
|
}
|
|
}
|