Testing Ownership
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
package character
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"bamort/database"
|
||||
"bamort/models"
|
||||
"bamort/testutils"
|
||||
"bamort/user"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestOwnershipGuardsBlockMutations(t *testing.T) {
|
||||
testutils.SetupTestEnvironment(t)
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
database.SetupTestDB(true, true)
|
||||
t.Cleanup(database.ResetTestDB)
|
||||
|
||||
require.NoError(t, models.MigrateStructure())
|
||||
|
||||
t.Run("UpdateCharacter blocks non-owner", func(t *testing.T) {
|
||||
owner := ensureUserExists(t, 101)
|
||||
char := createCharacterOwnedBy(t, owner.UserID)
|
||||
originalName := char.Name
|
||||
|
||||
ctx, w := buildJSONContext(t, http.MethodPut, map[string]any{"name": "New Name"}, char.UserID+1, map[string]string{"id": fmt.Sprint(char.ID)})
|
||||
|
||||
UpdateCharacter(ctx)
|
||||
|
||||
require.Equal(t, http.StatusForbidden, w.Code)
|
||||
reloaded := reloadCharacter(t, char.ID)
|
||||
require.Equal(t, originalName, reloaded.Name)
|
||||
})
|
||||
|
||||
t.Run("DeleteCharacter blocks non-owner", func(t *testing.T) {
|
||||
owner := ensureUserExists(t, 102)
|
||||
char := createCharacterOwnedBy(t, owner.UserID)
|
||||
|
||||
ctx, w := buildJSONContext(t, http.MethodDelete, nil, char.UserID+1, map[string]string{"id": fmt.Sprint(char.ID)})
|
||||
|
||||
DeleteCharacter(ctx)
|
||||
|
||||
require.Equal(t, http.StatusForbidden, w.Code)
|
||||
var exists models.Char
|
||||
err := database.DB.First(&exists, char.ID).Error
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("UpdateCharacterExperience blocks non-owner", func(t *testing.T) {
|
||||
owner := ensureUserExists(t, 103)
|
||||
char := createCharacterOwnedBy(t, owner.UserID)
|
||||
seedExperience(t, char, 25)
|
||||
|
||||
ctx, w := buildJSONContext(t, http.MethodPost, map[string]any{"experience_points": 50}, char.UserID+1, map[string]string{"id": fmt.Sprint(char.ID)})
|
||||
|
||||
UpdateCharacterExperience(ctx)
|
||||
|
||||
require.Equal(t, http.StatusForbidden, w.Code)
|
||||
reloaded := reloadCharacterWithPreloads(t, char.ID)
|
||||
require.Equal(t, 25, reloaded.Erfahrungsschatz.EP)
|
||||
})
|
||||
|
||||
t.Run("UpdateCharacterWealth blocks non-owner", func(t *testing.T) {
|
||||
owner := ensureUserExists(t, 104)
|
||||
char := createCharacterOwnedBy(t, owner.UserID)
|
||||
seedWealth(t, char, 5, 4, 3)
|
||||
|
||||
ctx, w := buildJSONContext(t, http.MethodPost, map[string]any{"goldstücke": 50}, char.UserID+1, map[string]string{"id": fmt.Sprint(char.ID)})
|
||||
|
||||
UpdateCharacterWealth(ctx)
|
||||
|
||||
require.Equal(t, http.StatusForbidden, w.Code)
|
||||
reloaded := reloadCharacterWithPreloads(t, char.ID)
|
||||
require.Equal(t, 5, reloaded.Vermoegen.Goldstuecke)
|
||||
require.Equal(t, 4, reloaded.Vermoegen.Silberstuecke)
|
||||
require.Equal(t, 3, reloaded.Vermoegen.Kupferstuecke)
|
||||
})
|
||||
|
||||
t.Run("LearnSkill blocks non-owner", func(t *testing.T) {
|
||||
owner := ensureUserExists(t, 105)
|
||||
char := createCharacterOwnedBy(t, owner.UserID)
|
||||
before := countSkills(t, char.ID)
|
||||
|
||||
ctx, w := buildJSONContext(t, http.MethodPost, map[string]any{"name": "Test Skill", "target_level": 1, "type": "skill"}, char.UserID+1, map[string]string{"id": fmt.Sprint(char.ID)})
|
||||
|
||||
LearnSkill(ctx)
|
||||
|
||||
require.Equal(t, http.StatusForbidden, w.Code)
|
||||
after := countSkills(t, char.ID)
|
||||
require.Equal(t, before, after)
|
||||
})
|
||||
|
||||
t.Run("ImproveSkill blocks non-owner", func(t *testing.T) {
|
||||
owner := ensureUserExists(t, 106)
|
||||
char := createCharacterOwnedBy(t, owner.UserID)
|
||||
seedSkill(t, char, "Athletik", 1, 0)
|
||||
before := countSkills(t, char.ID)
|
||||
|
||||
payload := map[string]any{
|
||||
"char_id": char.ID,
|
||||
"name": "Athletik",
|
||||
"current_level": 1,
|
||||
"target_level": 2,
|
||||
"type": "skill",
|
||||
"action": "improve",
|
||||
"reward": "default",
|
||||
}
|
||||
ctx, w := buildJSONContext(t, http.MethodPost, payload, char.UserID+1, nil)
|
||||
|
||||
ImproveSkill(ctx)
|
||||
|
||||
require.Equal(t, http.StatusForbidden, w.Code)
|
||||
after := countSkills(t, char.ID)
|
||||
require.Equal(t, before, after)
|
||||
})
|
||||
|
||||
t.Run("LearnSpell blocks non-owner", func(t *testing.T) {
|
||||
owner := ensureUserExists(t, 107)
|
||||
char := createCharacterOwnedBy(t, owner.UserID)
|
||||
before := countSpells(t, char.ID)
|
||||
|
||||
ctx, w := buildJSONContext(t, http.MethodPost, map[string]any{"name": "Test Spell"}, char.UserID+1, map[string]string{"id": fmt.Sprint(char.ID)})
|
||||
|
||||
LearnSpell(ctx)
|
||||
|
||||
require.Equal(t, http.StatusForbidden, w.Code)
|
||||
after := countSpells(t, char.ID)
|
||||
require.Equal(t, before, after)
|
||||
})
|
||||
|
||||
t.Run("UpdatePracticePoints blocks non-owner", func(t *testing.T) {
|
||||
owner := ensureUserExists(t, 108)
|
||||
char := createCharacterOwnedBy(t, owner.UserID)
|
||||
seedSkill(t, char, "Menschenkenntnis", 1, 2)
|
||||
before := fetchSkillPp(t, char.ID, "Menschenkenntnis")
|
||||
|
||||
payload := []map[string]any{{"skill_name": "Menschenkenntnis", "amount": 0}}
|
||||
ctx, w := buildJSONContext(t, http.MethodPost, payload, char.UserID+1, map[string]string{"id": fmt.Sprint(char.ID)})
|
||||
|
||||
UpdatePracticePoints(ctx)
|
||||
|
||||
require.Equal(t, http.StatusForbidden, w.Code)
|
||||
after := fetchSkillPp(t, char.ID, "Menschenkenntnis")
|
||||
require.Equal(t, before, after)
|
||||
})
|
||||
|
||||
t.Run("AddPracticePoint blocks non-owner", func(t *testing.T) {
|
||||
owner := ensureUserExists(t, 109)
|
||||
char := createCharacterOwnedBy(t, owner.UserID)
|
||||
seedSkill(t, char, "Athletik", 1, 1)
|
||||
before := fetchSkillPp(t, char.ID, "Athletik")
|
||||
|
||||
payload := map[string]any{"skill_name": "Athletik", "amount": 3}
|
||||
ctx, w := buildJSONContext(t, http.MethodPost, payload, char.UserID+1, map[string]string{"id": fmt.Sprint(char.ID)})
|
||||
|
||||
AddPracticePoint(ctx)
|
||||
|
||||
require.Equal(t, http.StatusForbidden, w.Code)
|
||||
after := fetchSkillPp(t, char.ID, "Athletik")
|
||||
require.Equal(t, before, after)
|
||||
})
|
||||
|
||||
t.Run("UsePracticePoint blocks non-owner", func(t *testing.T) {
|
||||
owner := ensureUserExists(t, 110)
|
||||
char := createCharacterOwnedBy(t, owner.UserID)
|
||||
seedSkill(t, char, "Athletik", 1, 2)
|
||||
before := fetchSkillPp(t, char.ID, "Athletik")
|
||||
|
||||
payload := map[string]any{"skill_name": "Athletik", "amount": 1}
|
||||
ctx, w := buildJSONContext(t, http.MethodPost, payload, char.UserID+1, map[string]string{"id": fmt.Sprint(char.ID)})
|
||||
|
||||
UsePracticePoint(ctx)
|
||||
|
||||
require.Equal(t, http.StatusForbidden, w.Code)
|
||||
after := fetchSkillPp(t, char.ID, "Athletik")
|
||||
require.Equal(t, before, after)
|
||||
})
|
||||
|
||||
t.Run("UpdateCharacterShares blocks non-owner", func(t *testing.T) {
|
||||
owner := ensureUserExists(t, 111)
|
||||
shareTarget := ensureUserExists(t, 112)
|
||||
char := createCharacterOwnedBy(t, owner.UserID)
|
||||
|
||||
payload := map[string]any{"user_ids": []uint{shareTarget.UserID}}
|
||||
ctx, w := buildJSONContext(t, http.MethodPost, payload, char.UserID+1, map[string]string{"id": fmt.Sprint(char.ID)})
|
||||
|
||||
UpdateCharacterShares(ctx)
|
||||
|
||||
require.Equal(t, http.StatusForbidden, w.Code)
|
||||
var shares []models.CharShare
|
||||
err := database.DB.Where("character_id = ?", char.ID).Find(&shares).Error
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, shares)
|
||||
})
|
||||
|
||||
t.Run("UpdateCharacterImage blocks non-owner", func(t *testing.T) {
|
||||
owner := ensureUserExists(t, 113)
|
||||
char := createCharacterOwnedBy(t, owner.UserID)
|
||||
char.Image = "initial.png"
|
||||
require.NoError(t, database.DB.Save(&char).Error)
|
||||
|
||||
ctx, w := buildJSONContext(t, http.MethodPost, map[string]any{"image": "new.png"}, char.UserID+1, map[string]string{"id": fmt.Sprint(char.ID)})
|
||||
|
||||
UpdateCharacterImage(ctx)
|
||||
|
||||
require.Equal(t, http.StatusForbidden, w.Code)
|
||||
reloaded := reloadCharacter(t, char.ID)
|
||||
require.Equal(t, "initial.png", reloaded.Image)
|
||||
})
|
||||
}
|
||||
|
||||
func ensureUserExists(t *testing.T, id uint) user.User {
|
||||
var existing user.User
|
||||
err := database.DB.First(&existing, "user_id = ?", id).Error
|
||||
if err == nil {
|
||||
return existing
|
||||
}
|
||||
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
newUser := user.User{
|
||||
UserID: id,
|
||||
Username: fmt.Sprintf("user-%d-%d", id, time.Now().UnixNano()),
|
||||
Email: fmt.Sprintf("user-%d@example.com", id),
|
||||
Role: user.RoleStandardUser,
|
||||
}
|
||||
require.NoError(t, database.DB.Create(&newUser).Error)
|
||||
return newUser
|
||||
}
|
||||
|
||||
func createCharacterOwnedBy(t *testing.T, ownerID uint) models.Char {
|
||||
char := models.Char{
|
||||
BamortBase: models.BamortBase{Name: fmt.Sprintf("Char-%d", time.Now().UnixNano())},
|
||||
UserID: ownerID,
|
||||
Typ: "Krieger",
|
||||
Rasse: "Mensch",
|
||||
Grad: 1,
|
||||
}
|
||||
require.NoError(t, database.DB.Create(&char).Error)
|
||||
return char
|
||||
}
|
||||
|
||||
func seedExperience(t *testing.T, char models.Char, ep int) {
|
||||
exp := models.Erfahrungsschatz{
|
||||
BamortCharTrait: models.BamortCharTrait{CharacterID: char.ID, UserID: char.UserID},
|
||||
EP: ep,
|
||||
}
|
||||
require.NoError(t, database.DB.Create(&exp).Error)
|
||||
}
|
||||
|
||||
func seedWealth(t *testing.T, char models.Char, gold, silver, copper int) {
|
||||
wealth := models.Vermoegen{
|
||||
BamortCharTrait: models.BamortCharTrait{CharacterID: char.ID, UserID: char.UserID},
|
||||
Goldstuecke: gold,
|
||||
Silberstuecke: silver,
|
||||
Kupferstuecke: copper,
|
||||
}
|
||||
require.NoError(t, database.DB.Create(&wealth).Error)
|
||||
}
|
||||
|
||||
func seedSkill(t *testing.T, char models.Char, name string, level, pp int) {
|
||||
skill := models.SkFertigkeit{
|
||||
BamortCharTrait: models.BamortCharTrait{
|
||||
BamortBase: models.BamortBase{Name: name},
|
||||
CharacterID: char.ID,
|
||||
UserID: char.UserID,
|
||||
},
|
||||
Fertigkeitswert: level,
|
||||
Pp: pp,
|
||||
Improvable: true,
|
||||
Category: "Test",
|
||||
}
|
||||
require.NoError(t, database.DB.Create(&skill).Error)
|
||||
}
|
||||
|
||||
func countSkills(t *testing.T, charID uint) int {
|
||||
var skills []models.SkFertigkeit
|
||||
require.NoError(t, database.DB.Where("character_id = ?", charID).Find(&skills).Error)
|
||||
return len(skills)
|
||||
}
|
||||
|
||||
func countSpells(t *testing.T, charID uint) int {
|
||||
var spells []models.SkZauber
|
||||
require.NoError(t, database.DB.Where("character_id = ?", charID).Find(&spells).Error)
|
||||
return len(spells)
|
||||
}
|
||||
|
||||
func fetchSkillPp(t *testing.T, charID uint, name string) int {
|
||||
var skill models.SkFertigkeit
|
||||
err := database.DB.Where("character_id = ? AND name = ?", charID, name).First(&skill).Error
|
||||
require.NoError(t, err)
|
||||
return skill.Pp
|
||||
}
|
||||
|
||||
func reloadCharacter(t *testing.T, id uint) models.Char {
|
||||
var char models.Char
|
||||
require.NoError(t, database.DB.First(&char, id).Error)
|
||||
return char
|
||||
}
|
||||
|
||||
func reloadCharacterWithPreloads(t *testing.T, id uint) models.Char {
|
||||
var char models.Char
|
||||
require.NoError(t, database.DB.Preload("Erfahrungsschatz").Preload("Vermoegen").First(&char, id).Error)
|
||||
return char
|
||||
}
|
||||
|
||||
func buildJSONContext(t *testing.T, method string, body any, userID uint, params map[string]string) (*gin.Context, *httptest.ResponseRecorder) {
|
||||
var buf bytes.Buffer
|
||||
if body != nil {
|
||||
require.NoError(t, json.NewEncoder(&buf).Encode(body))
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, "/", &buf)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
ctx, _ := gin.CreateTestContext(w)
|
||||
ctx.Request = req
|
||||
ctx.Set("userID", userID)
|
||||
|
||||
for k, v := range params {
|
||||
ctx.Params = append(ctx.Params, gin.Param{Key: k, Value: v})
|
||||
}
|
||||
|
||||
return ctx, w
|
||||
}
|
||||
@@ -82,8 +82,8 @@ func TestCreateAusruestung(t *testing.T) {
|
||||
BamortBase: models.BamortBase{
|
||||
Name: "Test Sword",
|
||||
},
|
||||
CharacterID: 1,
|
||||
UserID: 1,
|
||||
CharacterID: 21,
|
||||
UserID: 4,
|
||||
},
|
||||
Magisch: models.Magisch{
|
||||
IstMagisch: false,
|
||||
@@ -111,13 +111,17 @@ func TestCreateAusruestung(t *testing.T) {
|
||||
{
|
||||
name: "Empty JSON",
|
||||
payload: map[string]interface{}{},
|
||||
expectedStatus: http.StatusCreated,
|
||||
expectedStatus: http.StatusNotFound,
|
||||
shouldContain: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// u := user.User{}
|
||||
// u.FirstId(1)
|
||||
// token := user.GenerateToken(&u)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
|
||||
@@ -129,9 +133,12 @@ func TestCreateAusruestung(t *testing.T) {
|
||||
body = *bytes.NewBuffer(jsonData)
|
||||
}
|
||||
|
||||
c.Set("userID", uint(4)) // Simulate logged-in user with ID 4
|
||||
c.Request = httptest.NewRequest("POST", "/ausruestung", &body)
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
//c.Request.Header.Set("Authorization", "Bearer "+token)
|
||||
|
||||
CreateAusruestung(c)
|
||||
|
||||
assert.Equal(t, tt.expectedStatus, w.Code)
|
||||
@@ -235,8 +242,8 @@ func TestUpdateAusruestung(t *testing.T) {
|
||||
BamortBase: models.BamortBase{
|
||||
Name: "Original Equipment",
|
||||
},
|
||||
CharacterID: 123,
|
||||
UserID: 1,
|
||||
CharacterID: 21,
|
||||
UserID: 4,
|
||||
},
|
||||
Magisch: models.Magisch{
|
||||
IstMagisch: false,
|
||||
@@ -297,6 +304,8 @@ func TestUpdateAusruestung(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
|
||||
c.Set("userID", uint(4))
|
||||
|
||||
c.Params = gin.Params{
|
||||
{Key: "ausruestung_id", Value: tt.ausruestungID},
|
||||
}
|
||||
@@ -332,8 +341,8 @@ func TestDeleteAusruestung(t *testing.T) {
|
||||
BamortBase: models.BamortBase{
|
||||
Name: "Equipment to Delete",
|
||||
},
|
||||
CharacterID: 123,
|
||||
UserID: 1,
|
||||
CharacterID: 21,
|
||||
UserID: 4,
|
||||
},
|
||||
Magisch: models.Magisch{
|
||||
IstMagisch: false,
|
||||
@@ -368,14 +377,14 @@ func TestDeleteAusruestung(t *testing.T) {
|
||||
{
|
||||
name: "Non-existent Ausruestung",
|
||||
ausruestungID: "999",
|
||||
expectedStatus: http.StatusOK, // GORM doesn't fail on deleting non-existent records
|
||||
shouldContain: "deleted successfully",
|
||||
expectedStatus: http.StatusNotFound, // GORM doesn't fail on deleting non-existent records
|
||||
shouldContain: "Ausruestung not found",
|
||||
},
|
||||
{
|
||||
name: "Invalid Ausruestung ID",
|
||||
ausruestungID: "invalid",
|
||||
expectedStatus: http.StatusInternalServerError,
|
||||
shouldContain: "error",
|
||||
expectedStatus: http.StatusNotFound,
|
||||
shouldContain: "Ausruestung not found",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -383,11 +392,11 @@ func TestDeleteAusruestung(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Set("userID", uint(4))
|
||||
|
||||
c.Params = gin.Params{
|
||||
{Key: "ausruestung_id", Value: tt.ausruestungID},
|
||||
}
|
||||
|
||||
DeleteAusruestung(c)
|
||||
|
||||
assert.Equal(t, tt.expectedStatus, w.Code)
|
||||
|
||||
Reference in New Issue
Block a user