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
395 lines
13 KiB
Go
395 lines
13 KiB
Go
package importer
|
|
|
|
import (
|
|
"bamort/database"
|
|
"bamort/bmrt/models"
|
|
"bytes"
|
|
"encoding/json"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func defaultGameSystem(t *testing.T) *models.GameSystem {
|
|
gs := models.GetGameSystem(0, "midgard")
|
|
require.NotNil(t, gs)
|
|
require.NotZero(t, gs.ID)
|
|
return gs
|
|
}
|
|
|
|
func NoT_estImportCsv2Spell(t *testing.T) {
|
|
// Clear source cache to ensure clean test state
|
|
ClearSourceCache()
|
|
|
|
// Setup test database
|
|
database.SetupTestDB(true, false) // Use in-memory SQLite, no test data loading
|
|
defer database.ResetTestDB()
|
|
models.MigrateStructure()
|
|
/*
|
|
// Create test source data
|
|
testSources := []models.Source{
|
|
{Code: "ARK", Name: "Arkanum", GameSystemId: 1},
|
|
{Code: "MYS", Name: "Mysterium", GameSystemId: 1},
|
|
{Code: "KOD", Name: "Kodex", GameSystemId: 1},
|
|
}
|
|
for _, source := range testSources {
|
|
source.Create()
|
|
}
|
|
*/
|
|
t.Run("Import Zauber-Arkanum.csv", func(t *testing.T) {
|
|
// Test if file exists
|
|
csvPath := "/data/dev/bamort/backend/doc/Zauber-Arkanum.csv"
|
|
if _, err := os.Stat(csvPath); os.IsNotExist(err) {
|
|
t.Skipf("CSV file %s not found, skipping test", csvPath)
|
|
return
|
|
}
|
|
|
|
// Test import
|
|
err := ImportCsv2Spell(csvPath)
|
|
assert.NoError(t, err, "Import should succeed")
|
|
|
|
// Verify some spells were imported
|
|
var count int64
|
|
database.DB.Model(&models.Spell{}).Count(&count)
|
|
assert.Greater(t, count, int64(0), "Should have imported some spells")
|
|
|
|
// Test specific spell with more detailed debugging
|
|
var spell models.Spell
|
|
err = spell.First("Angst")
|
|
if err != nil {
|
|
// Let's check what spells actually exist
|
|
var allSpells []models.Spell
|
|
database.DB.Limit(10).Find(&allSpells)
|
|
t.Logf("Found %d spells in database, first 10:", count)
|
|
for i, s := range allSpells {
|
|
t.Logf("Spell %d: %s (Category: %s, Level: %d)", i+1, s.Name, s.Category, s.Stufe)
|
|
}
|
|
}
|
|
assert.NoError(t, err, "Should find 'Angst' spell")
|
|
assert.Equal(t, "Beherrschen", spell.Category, "Spell category should match")
|
|
assert.Equal(t, 2, spell.Stufe, "Spell level should be 2")
|
|
|
|
// Test that source_id was set correctly (ARK should have been looked up)
|
|
if spell.SourceID != 0 {
|
|
var source models.Source
|
|
err = database.DB.First(&source, spell.SourceID).Error
|
|
assert.NoError(t, err, "Should find source by ID")
|
|
assert.Equal(t, "ARK", source.Code, "Source code should be ARK")
|
|
}
|
|
})
|
|
|
|
t.Run("Import Zauber-Mysterium.csv", func(t *testing.T) {
|
|
// Test if file exists
|
|
csvPath := "/data/dev/bamort/backend/doc/Zauber-Mysterium.csv"
|
|
if _, err := os.Stat(csvPath); os.IsNotExist(err) {
|
|
t.Skipf("CSV file %s not found, skipping test", csvPath)
|
|
return
|
|
}
|
|
|
|
// Get current spell count
|
|
var countBefore int64
|
|
database.DB.Model(&models.Spell{}).Count(&countBefore)
|
|
|
|
// Test import
|
|
err := ImportCsv2Spell(csvPath)
|
|
assert.NoError(t, err, "Import should succeed")
|
|
|
|
// Verify more spells were imported
|
|
var countAfter int64
|
|
database.DB.Model(&models.Spell{}).Count(&countAfter)
|
|
assert.Greater(t, countAfter, countBefore, "Should have imported additional spells")
|
|
})
|
|
|
|
t.Run("Test update existing spell", func(t *testing.T) {
|
|
// Create a test spell first
|
|
testSpell := models.Spell{
|
|
GameSystemId: 1,
|
|
Name: "Test Zauber",
|
|
Beschreibung: "Original description",
|
|
Stufe: 1,
|
|
}
|
|
err := testSpell.Create()
|
|
assert.NoError(t, err, "Should create test spell")
|
|
|
|
// Create temporary CSV with updated data
|
|
csvContent := `name,Beschreibung,stufe
|
|
Test Zauber,Updated description,2`
|
|
|
|
tmpFile, err := os.CreateTemp("", "test_spell_*.csv")
|
|
assert.NoError(t, err, "Should create temp file")
|
|
defer os.Remove(tmpFile.Name())
|
|
|
|
_, err = tmpFile.WriteString(csvContent)
|
|
assert.NoError(t, err, "Should write to temp file")
|
|
tmpFile.Close()
|
|
|
|
// Import the updated data
|
|
err = ImportCsv2Spell(tmpFile.Name())
|
|
assert.NoError(t, err, "Import should succeed")
|
|
|
|
// Verify the spell was updated
|
|
var updatedSpell models.Spell
|
|
err = updatedSpell.First("Test Zauber")
|
|
assert.NoError(t, err, "Should find updated spell")
|
|
assert.Equal(t, "Updated description", updatedSpell.Beschreibung, "Description should be updated")
|
|
assert.Equal(t, 2, updatedSpell.Stufe, "Level should be updated")
|
|
})
|
|
|
|
t.Run("Test source lookup function", func(t *testing.T) {
|
|
gs := defaultGameSystem(t)
|
|
// Clear cache to ensure fresh lookups
|
|
ClearSourceCache()
|
|
|
|
// Test successful lookup of existing source
|
|
sourceID, err := lookupSourceID("ARK")
|
|
assert.NoError(t, err, "Should find ARK source")
|
|
assert.Greater(t, sourceID, uint(0), "Source ID should be greater than 0")
|
|
|
|
// Test auto-creation of non-existent source
|
|
newSourceID, err := lookupSourceID("NEWCODE")
|
|
assert.NoError(t, err, "Should auto-create NEWCODE source")
|
|
assert.Greater(t, newSourceID, uint(0), "New source ID should be greater than 0")
|
|
|
|
// Verify the source was actually created in the database
|
|
var newSource models.Source
|
|
err = database.DB.First(&newSource, newSourceID).Error
|
|
assert.NoError(t, err, "Should find newly created source")
|
|
assert.Equal(t, "NEWCODE", newSource.Code, "Source code should match")
|
|
assert.Equal(t, "NEWCODE", newSource.Name, "Source name should default to code")
|
|
assert.Equal(t, gs.ID, newSource.GameSystemId, "Game system ID should match default")
|
|
assert.True(t, newSource.IsActive, "New source should be active")
|
|
|
|
// Test that the second lookup uses cache (should return same ID)
|
|
cachedSourceID, err := lookupSourceID("NEWCODE")
|
|
assert.NoError(t, err, "Should find cached source")
|
|
assert.Equal(t, newSourceID, cachedSourceID, "Cached lookup should return same ID")
|
|
|
|
// Test empty source code
|
|
_, err = lookupSourceID("")
|
|
assert.Error(t, err, "Should return error for empty source code")
|
|
})
|
|
|
|
t.Run("Test auto-creation of sources during CSV import", func(t *testing.T) {
|
|
// Clear cache and check initial source count
|
|
ClearSourceCache()
|
|
var initialSourceCount int64
|
|
database.DB.Model(&models.Source{}).Count(&initialSourceCount)
|
|
|
|
// Create temporary CSV with a new source code
|
|
csvContent := `name,quelle,stufe,category
|
|
Test Spell,TESTSRC,1,Beherrschen`
|
|
|
|
tmpFile, err := os.CreateTemp("", "test_auto_source_*.csv")
|
|
assert.NoError(t, err, "Should create temp file")
|
|
defer os.Remove(tmpFile.Name())
|
|
|
|
_, err = tmpFile.WriteString(csvContent)
|
|
assert.NoError(t, err, "Should write to temp file")
|
|
tmpFile.Close()
|
|
|
|
// Import the CSV
|
|
err = ImportCsv2Spell(tmpFile.Name())
|
|
assert.NoError(t, err, "Import should succeed")
|
|
|
|
// Verify new source was created
|
|
var finalSourceCount int64
|
|
database.DB.Model(&models.Source{}).Count(&finalSourceCount)
|
|
assert.Greater(t, finalSourceCount, initialSourceCount, "Should have created new source")
|
|
|
|
// Verify the spell was imported with correct source
|
|
var importedSpell models.Spell
|
|
err = importedSpell.First("Test Spell")
|
|
assert.NoError(t, err, "Should find imported spell")
|
|
assert.Equal(t, "TESTSRC", importedSpell.Quelle, "Spell quelle should match")
|
|
assert.Greater(t, importedSpell.SourceID, uint(0), "Spell should have source ID")
|
|
|
|
// Verify the source details
|
|
var createdSource models.Source
|
|
err = database.DB.First(&createdSource, importedSpell.SourceID).Error
|
|
assert.NoError(t, err, "Should find created source")
|
|
assert.Equal(t, "TESTSRC", createdSource.Code, "Source code should match")
|
|
})
|
|
}
|
|
|
|
func NoT_estImportSpellCSVHandler(t *testing.T) {
|
|
// Setup test database
|
|
database.SetupTestDB(true, false)
|
|
defer database.ResetTestDB()
|
|
models.MigrateStructure()
|
|
|
|
// Create test source
|
|
testSource := models.Source{Code: "ARK", Name: "Arkanum", GameSystemId: 1}
|
|
testSource.Create()
|
|
|
|
// Setup Gin in test mode
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
t.Run("Successful CSV import via HTTP", func(t *testing.T) {
|
|
// Create temporary test CSV file
|
|
csvContent := `name,quelle,stufe,category
|
|
Test Spell HTTP,ARK,3,Beherrschen`
|
|
|
|
tmpFile, err := os.CreateTemp("", "test_http_*.csv")
|
|
assert.NoError(t, err, "Should create temp file")
|
|
defer os.Remove(tmpFile.Name())
|
|
|
|
_, err = tmpFile.WriteString(csvContent)
|
|
assert.NoError(t, err, "Should write to temp file")
|
|
tmpFile.Close()
|
|
|
|
// Create test request
|
|
router := gin.New()
|
|
router.POST("/test", ImportSpellCSVHandler)
|
|
|
|
req, err := http.NewRequest("POST", "/test?file="+tmpFile.Name(), nil)
|
|
assert.NoError(t, err, "Should create request")
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Assert response
|
|
assert.Equal(t, http.StatusOK, w.Code, "Should return 200 OK")
|
|
|
|
var response map[string]interface{}
|
|
err = json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Should parse JSON response")
|
|
assert.True(t, response["success"].(bool), "Should indicate success")
|
|
assert.Contains(t, response, "total_spells", "Should contain spell count")
|
|
|
|
// Verify spell was imported
|
|
var spell models.Spell
|
|
err = spell.First("Test Spell HTTP")
|
|
assert.NoError(t, err, "Should find imported spell")
|
|
assert.Equal(t, 3, spell.Stufe, "Spell level should be 3")
|
|
})
|
|
|
|
t.Run("Missing file parameter", func(t *testing.T) {
|
|
router := gin.New()
|
|
router.POST("/test", ImportSpellCSVHandler)
|
|
|
|
req, err := http.NewRequest("POST", "/test", nil)
|
|
assert.NoError(t, err, "Should create request")
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusBadRequest, w.Code, "Should return 400 Bad Request")
|
|
|
|
var response map[string]interface{}
|
|
err = json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Should parse JSON response")
|
|
assert.Equal(t, "Missing file parameter", response["error"], "Should return correct error")
|
|
})
|
|
|
|
t.Run("File not found", func(t *testing.T) {
|
|
router := gin.New()
|
|
router.POST("/test", ImportSpellCSVHandler)
|
|
|
|
req, err := http.NewRequest("POST", "/test?file=/nonexistent/file.csv", nil)
|
|
assert.NoError(t, err, "Should create request")
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusBadRequest, w.Code, "Should return 400 Bad Request")
|
|
|
|
var response map[string]interface{}
|
|
err = json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Should parse JSON response")
|
|
assert.Equal(t, "File not found", response["error"], "Should return correct error")
|
|
})
|
|
|
|
t.Run("Invalid file type", func(t *testing.T) {
|
|
// Create temporary non-CSV file
|
|
tmpFile, err := os.CreateTemp("", "test_*.txt")
|
|
assert.NoError(t, err, "Should create temp file")
|
|
defer os.Remove(tmpFile.Name())
|
|
tmpFile.Close()
|
|
|
|
router := gin.New()
|
|
router.POST("/test", ImportSpellCSVHandler)
|
|
|
|
req, err := http.NewRequest("POST", "/test?file="+tmpFile.Name(), nil)
|
|
assert.NoError(t, err, "Should create request")
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusBadRequest, w.Code, "Should return 400 Bad Request")
|
|
|
|
var response map[string]interface{}
|
|
err = json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Should parse JSON response")
|
|
assert.Equal(t, "Invalid file type", response["error"], "Should return correct error")
|
|
})
|
|
|
|
t.Run("File upload with multipart form", func(t *testing.T) {
|
|
// Setup test database
|
|
database.SetupTestDB(true, false)
|
|
models.MigrateStructure()
|
|
|
|
// Create test CSV content
|
|
csvContent := `name,beschreibung,quelle,stufe,ap
|
|
Test Spell Upload,Test description,ARK,1,2`
|
|
|
|
// Create temporary file
|
|
tmpFile, err := os.CreateTemp("", "test_spell_upload_*.csv")
|
|
assert.NoError(t, err, "Should create temp file")
|
|
defer os.Remove(tmpFile.Name())
|
|
|
|
_, err = tmpFile.WriteString(csvContent)
|
|
assert.NoError(t, err, "Should write CSV content")
|
|
tmpFile.Close()
|
|
|
|
// Create multipart form data
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
|
|
// Add file field
|
|
file, err := os.Open(tmpFile.Name())
|
|
assert.NoError(t, err, "Should open temp file")
|
|
defer file.Close()
|
|
|
|
part, err := writer.CreateFormFile("file", "test_spells.csv")
|
|
assert.NoError(t, err, "Should create form file")
|
|
|
|
_, err = io.Copy(part, file)
|
|
assert.NoError(t, err, "Should copy file content")
|
|
|
|
err = writer.Close()
|
|
assert.NoError(t, err, "Should close writer")
|
|
|
|
// Create request
|
|
router := gin.New()
|
|
router.POST("/test", ImportSpellCSVHandler)
|
|
|
|
req, err := http.NewRequest("POST", "/test", body)
|
|
assert.NoError(t, err, "Should create request")
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code, "Should return 200 OK")
|
|
|
|
var response map[string]interface{}
|
|
err = json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err, "Should parse JSON response")
|
|
assert.True(t, response["success"].(bool), "Should be successful")
|
|
assert.Contains(t, response["message"], "imported successfully", "Should contain success message")
|
|
|
|
// Verify spell was imported
|
|
var spell models.Spell
|
|
err = database.DB.Where("name = ?", "Test Spell Upload").First(&spell).Error
|
|
assert.NoError(t, err, "Should find imported spell")
|
|
assert.Equal(t, "Test description", spell.Beschreibung, "Should have correct description")
|
|
})
|
|
}
|