diff --git a/backend/database/testhelper.go b/backend/database/testhelper.go index e5e4485..00b4ff8 100644 --- a/backend/database/testhelper.go +++ b/backend/database/testhelper.go @@ -108,6 +108,7 @@ func SetupTestDB(opts ...bool) { logger.Debug("SetupTestDB: DB bereits initialisiert, überspringe Setup") } } + func ResetTestDB() { logger.Debug("ResetTestDB aufgerufen") diff --git a/backend/gsmaster/export_import.go b/backend/gsmaster/export_import.go index 5d1e320..05a7b76 100644 --- a/backend/gsmaster/export_import.go +++ b/backend/gsmaster/export_import.go @@ -88,6 +88,7 @@ type ExportableWeaponSkillCategoryDifficulty struct { type ExportableSpell struct { Name string `json:"name"` GameSystem string `json:"game_system"` + GameSystemId uint `json:"game_system_id"` Beschreibung string `json:"beschreibung"` SourceCode string `json:"source_code"` PageNumber int `json:"page_number"` @@ -786,6 +787,7 @@ func ExportSpells(outputDir string) error { exportable[i] = ExportableSpell{ Name: spell.Name, GameSystem: spell.GameSystem, + GameSystemId: spell.GameSystemId, Beschreibung: spell.Beschreibung, SourceCode: sourceMap[spell.SourceID], PageNumber: spell.PageNumber, @@ -816,16 +818,23 @@ func ImportSpells(inputDir string) error { sourceMap := buildSourceMapReverse() + if err := database.DB.AutoMigrate(&models.Spell{}); err != nil { + return fmt.Errorf("failed to migrate spells table: %w", err) + } + + gs := models.GetGameSystem(exportable[0].GameSystemId, exportable[0].GameSystem) for _, exp := range exportable { var spell models.Spell - result := database.DB.Where("name = ? AND game_system = ?", exp.Name, exp.GameSystem).First(&spell) + + result := database.DB.Where("name = ? AND (game_system = ? OR game_system_id = ?)", exp.Name, gs.Name, gs.ID).First(&spell) sourceID := sourceMap[exp.SourceCode] if result.Error == gorm.ErrRecordNotFound { spell = models.Spell{ Name: exp.Name, - GameSystem: exp.GameSystem, + GameSystem: gs.Name, + GameSystemId: gs.ID, Beschreibung: exp.Beschreibung, SourceID: sourceID, PageNumber: exp.PageNumber, @@ -864,6 +873,8 @@ func ImportSpells(inputDir string) error { spell.Ursprung = exp.Ursprung spell.Category = exp.Category spell.LearningCategory = exp.LearningCategory + spell.GameSystemId = gs.ID + spell.GameSystem = gs.Name if err := database.DB.Save(&spell).Error; err != nil { return fmt.Errorf("failed to update spell %s: %w", exp.Name, err) diff --git a/backend/gsmaster/export_import_test.go b/backend/gsmaster/export_import_test.go index c9f289b..b2e5fa9 100644 --- a/backend/gsmaster/export_import_test.go +++ b/backend/gsmaster/export_import_test.go @@ -347,6 +347,7 @@ func TestExportImportSkillDifficulties(t *testing.T) { func TestExportImportSpells(t *testing.T) { setupTestEnvironment(t) database.SetupTestDB() + models.MigrateStructure(database.DB) // Create test data source := getOrCreateSource("TEST_SP", "Test Spell Source") @@ -369,7 +370,8 @@ func TestExportImportSpells(t *testing.T) { Category: "normal", LearningCategory: "default", } - database.DB.Create(&spell) + //database.DB.Create(&spell) + spell.Create() // Export tempDir := t.TempDir() @@ -411,6 +413,9 @@ func TestExportImportSpells(t *testing.T) { if imported.Stufe != 3 { t.Errorf("Expected level 3, got %d", imported.Stufe) } + if imported.GameSystemId == 0 { + t.Errorf("Expected game_system_id to be set") + } } func TestExportImportAll(t *testing.T) { diff --git a/backend/gsmaster/misclookup.go b/backend/gsmaster/misclookup.go index 616084a..a4780eb 100644 --- a/backend/gsmaster/misclookup.go +++ b/backend/gsmaster/misclookup.go @@ -36,8 +36,28 @@ func GetMiscLookupByKeyForSystem(key string, gameSystemId uint, order ...string) } gs := models.GetGameSystem(gameSystemId, "") - err := database.DB.Where("`key` = ? AND game_system_id = ?", key, gs.ID).Order(orderBy).Find(&items).Error - return items, err + + query := database.DB.Where("`key` = ?", key) + if gs.ID != 0 { + query = query.Where("game_system_id = ? OR game_system_id = 0 OR game_system_id IS NULL", gs.ID) + } else { + query = query.Where("game_system_id = 0 OR game_system_id IS NULL") + } + + if err := query.Order(orderBy).Find(&items).Error; err != nil { + return items, err + } + + if gs.ID != 0 { + for i := range items { + if items[i].GameSystemId == 0 { + items[i].GameSystemId = gs.ID + database.DB.Model(&items[i]).Update("game_system_id", gs.ID) + } + } + } + + return items, nil } /* diff --git a/backend/models/model_gsmaster.go b/backend/models/model_gsmaster.go index 9e79e66..0e72f88 100644 --- a/backend/models/model_gsmaster.go +++ b/backend/models/model_gsmaster.go @@ -67,6 +67,7 @@ type WeaponSkill struct { type Spell struct { ID uint `gorm:"primaryKey" json:"id"` GameSystem string `gorm:"column:game_system;index;default:midgard" json:"game_system"` + GameSystemId uint `json:"game_system_id,omitempty"` Name string `gorm:"type:varchar(255);index" json:"name"` Beschreibung string `json:"beschreibung"` Quelle string `json:"quelle"` // Deprecated: Für Rückwärtskompatibilität @@ -325,8 +326,7 @@ func (object *Spell) TableName() string { } func (stamm *Spell) Create() error { - gameSystem := "midgard" - stamm.GameSystem = gameSystem + stamm.ensureGameSystem() err := database.DB.Transaction(func(tx *gorm.DB) error { // Save the main character record if err := tx.Create(&stamm).Error; err != nil { @@ -342,8 +342,8 @@ func (stamm *Spell) First(name string) error { if name == "" { return fmt.Errorf("name cannot be empty") } - gameSystem := "midgard" - err := database.DB.First(&stamm, "game_system=? AND name = ?", gameSystem, name).Error + gs := GetGameSystem(stamm.GameSystemId, stamm.GameSystem) + err := database.DB.First(&stamm, "(game_system=? OR game_system_id=?) AND name = ?", gs.Name, gs.ID, name).Error if err != nil { // zauber found return err @@ -352,8 +352,8 @@ func (stamm *Spell) First(name string) error { } func (object *Spell) FirstId(value uint) error { - gameSystem := "midgard" - err := database.DB.First(&object, "game_system=? AND id = ?", gameSystem, value).Error + gs := GetGameSystem(object.GameSystemId, object.GameSystem) + err := database.DB.First(&object, "(game_system=? OR game_system_id=?) AND id = ?", gs.Name, gs.ID, value).Error if err != nil { // zauber found return err @@ -362,6 +362,7 @@ func (object *Spell) FirstId(value uint) error { } func (object *Spell) Save() error { + object.ensureGameSystem() err := database.DB.Save(&object).Error if err != nil { // zauber found @@ -398,10 +399,10 @@ func SelectSpells(opts ...string) ([]Spell, error) { func (object *Spell) GetSpellCategories() ([]string, error) { var categories []string - gameSystem := "midgard" + gs := GetGameSystem(object.GameSystemId, object.GameSystem) result := database.DB.Model(&SpellSchool{}). - Where("game_system = ?", gameSystem). + Where("game_system = ? OR game_system_id = ?", gs.Name, gs.ID). Pluck("name", &categories) if result.Error != nil { @@ -411,6 +412,28 @@ func (object *Spell) GetSpellCategories() ([]string, error) { return categories, nil } +func (object *Spell) ensureGameSystem() { + gs := GetGameSystem(object.GameSystemId, object.GameSystem) + + if object.GameSystemId == 0 { + object.GameSystemId = gs.ID + } + + if object.GameSystem == "" { + object.GameSystem = gs.Name + } +} + +func (object *Spell) BeforeCreate(tx *gorm.DB) error { + object.ensureGameSystem() + return nil +} + +func (object *Spell) BeforeSave(tx *gorm.DB) error { + object.ensureGameSystem() + return nil +} + func (object *Equipment) TableName() string { dbPrefix := "gsm" return dbPrefix + "_" + "equipments" @@ -738,8 +761,15 @@ func GetGameSystem(id uint, name string) *GameSystem { if gs.ID == 0 { gs.FirstByName(name) } + if gs.ID == 0 { + gs.GetDefault() + gs.Name = name + } return gs } gs.FirstByID(uint(id)) + if gs.ID == 0 && name != "" { + gs.Name = name + } return gs } diff --git a/backend/scripts/run_all_tests.sh b/backend/scripts/run_all_tests.sh new file mode 100755 index 0000000..5556a1f --- /dev/null +++ b/backend/scripts/run_all_tests.sh @@ -0,0 +1,36 @@ +set -e +cd /data/dev/bamort/backend +echo "dont forget to reactivate skipped tests after fixing issues" +go test ./cmd -v |grep FAIL +go test ./database -v |grep FAIL +go test ./maintenance -v |grep FAIL +go test ./testutils -v |grep FAIL +go test ./router -v |grep FAIL +go test ./models -v |grep FAIL +go test ./api -v |grep FAIL +go test ./gamesystem -v |grep FAIL +go test ./transfer -v |grep FAIL +go test ./uploads -v |grep FAIL +go test ./user -v |grep FAIL +go test ./importer -v |grep FAIL +go test ./character -v |grep FAIL +go test ./gsmaster -v |grep FAIL +go test ./pdfrender -v |grep FAIL +go test ./config -v |grep FAIL +go test ./equipment -v |grep FAIL +go test ./logger -v |grep FAIL + +# Optional: generate coverage report for the whole backend module. +# Enable by setting RUN_COVERAGE=1 in the environment. +if [ "${RUN_COVERAGE:-}" = "1" ]; then + echo "Running coverage for backend (this may take a while)..." + # produce a single combined coverage profile + go test ./... -coverprofile=coverage.out -covermode=atomic + if [ -f coverage.out ]; then + # generate an HTML report (best viewed in a browser) + go tool cover -html=coverage.out -o coverage.html || true + echo "Coverage written to coverage.out and coverage.html" + else + echo "coverage.out not created" + fi +fi \ No newline at end of file