diff --git a/backend/doc/Testing.md b/backend/doc/Testing.md index 7e8992e..15292da 100644 --- a/backend/doc/Testing.md +++ b/backend/doc/Testing.md @@ -12,4 +12,44 @@ go test -bench=BenchmarkSource -benchmem ./models go test -bench=BenchmarkSimple -benchmem ./models ### Run parallel benchmarks -go test -bench=Parallel -benchmem ./models \ No newline at end of file +go test -bench=Parallel -benchmem ./models + +## Coverage + +go test ./equipment/... -cover +go test ./equipment/... -coverprofile=coverage.out +go tool cover -func=coverage.out +go tool cover -html=coverage.out -o coverage.html +### eval Coverage + mode: set + bamort/equipment/handlers.go:18.67,20.2 1 1 + bamort/equipment/handlers.go:22.40,24.55 2 1 + bamort/equipment/handlers.go:24.55,27.3 2 1 + bamort/equipment/handlers.go:29.2,29.63 1 1 + bamort/equipment/handlers.go:29.63,32.3 2 0 + bamort/equipment/handlers.go:34.2,34.41 1 1 + bamort/equipment/handlers.go:37.38,41.100 3 1 + bamort/equipment/handlers.go:41.100,44.3 2 0 + bamort/equipment/handlers.go:46.2,46.36 1 1 + bamort/equipment/handlers.go:49.40,53.77 3 1 + bamort/equipment/handlers.go:53.77,56.3 2 1 + bamort/equipment/handlers.go:58.2,58.55 1 1 + bamort/equipment/handlers.go:58.55,61.3 2 1 + bamort/equipment/handlers.go:63.2,63.61 1 1 + bamort/equipment/handlers.go:63.61,66.3 2 0 + bamort/equipment/handlers.go:68.2,68.36 1 1 + bamort/equipment/handlers.go:71.40,73.89 2 1 + bamort/equipment/handlers.go:73.89,76.3 2 1 + bamort/equipment/handlers.go:78.2,78.77 1 1 + +Looking at the coverage.out file, you can see which lines are covered. The format is: +file:startLine.startCol,endLine.endCol numStatements covered + +Where the last number indicates if the line is covered: +1 = covered +0 = not covered + +From your coverage.out, I can see these uncovered lines (marked with 0): +Line 29-32: bamort/equipment/handlers.go:29.63,32.3 2 0 - Database error handling in CreateAusruestung +Line 41-44: bamort/equipment/handlers.go:41.100,44.3 2 0 - Database error handling in ListAusruestung +Line 63-66: bamort/equipment/handlers.go:63.61,66.3 2 0 - Database save error handling in UpdateAusruestung \ No newline at end of file diff --git a/backend/equipment/handlers_test.go b/backend/equipment/handlers_test.go new file mode 100644 index 0000000..79f383d --- /dev/null +++ b/backend/equipment/handlers_test.go @@ -0,0 +1,406 @@ +package equipment + +import ( + "bamort/database" + "bamort/models" + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRespondWithError(t *testing.T) { + gin.SetMode(gin.TestMode) + + tests := []struct { + name string + status int + message string + expectedStatus int + expectedBody map[string]string + }{ + { + name: "Bad Request Error", + status: http.StatusBadRequest, + message: "Invalid request", + expectedStatus: http.StatusBadRequest, + expectedBody: map[string]string{"error": "Invalid request"}, + }, + { + name: "Internal Server Error", + status: http.StatusInternalServerError, + message: "Database connection failed", + expectedStatus: http.StatusInternalServerError, + expectedBody: map[string]string{"error": "Database connection failed"}, + }, + { + name: "Not Found Error", + status: http.StatusNotFound, + message: "Resource not found", + expectedStatus: http.StatusNotFound, + expectedBody: map[string]string{"error": "Resource not found"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + respondWithError(c, tt.status, tt.message) + + assert.Equal(t, tt.expectedStatus, w.Code) + + var response map[string]string + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, tt.expectedBody, response) + }) + } +} + +func TestCreateAusruestung(t *testing.T) { + database.SetupTestDB(true) + gin.SetMode(gin.TestMode) + + tests := []struct { + name string + payload interface{} + expectedStatus int + shouldContain string + }{ + { + name: "Valid Ausruestung Creation", + payload: models.EqAusruestung{ + BamortCharTrait: models.BamortCharTrait{ + BamortBase: models.BamortBase{ + Name: "Test Sword", + }, + CharacterID: 1, + UserID: 1, + }, + Magisch: models.Magisch{ + IstMagisch: false, + Abw: 0, + Ausgebrannt: false, + }, + Beschreibung: "A test sword", + Anzahl: 1, + BeinhaltetIn: "", + ContainedIn: 0, + ContainerType: "", + Bonus: 0, + Gewicht: 2.5, + Wert: 100.0, + }, + expectedStatus: http.StatusCreated, + shouldContain: "Test Sword", + }, + { + name: "Invalid JSON", + payload: "invalid json", + expectedStatus: http.StatusBadRequest, + shouldContain: "error", + }, + { + name: "Empty JSON", + payload: map[string]interface{}{}, + expectedStatus: http.StatusCreated, + shouldContain: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + var body bytes.Buffer + if tt.name == "Invalid JSON" { + body = *bytes.NewBufferString("invalid json") + } else { + jsonData, _ := json.Marshal(tt.payload) + body = *bytes.NewBuffer(jsonData) + } + + c.Request = httptest.NewRequest("POST", "/ausruestung", &body) + c.Request.Header.Set("Content-Type", "application/json") + + CreateAusruestung(c) + + assert.Equal(t, tt.expectedStatus, w.Code) + if tt.shouldContain != "" { + assert.Contains(t, w.Body.String(), tt.shouldContain) + } + }) + } +} + +func TestListAusruestung(t *testing.T) { + database.SetupTestDB(true) + gin.SetMode(gin.TestMode) + + // Create test data + testAusruestung := models.EqAusruestung{ + BamortCharTrait: models.BamortCharTrait{ + BamortBase: models.BamortBase{ + Name: "Test Equipment", + }, + CharacterID: 123, + UserID: 1, + }, + Magisch: models.Magisch{ + IstMagisch: false, + Abw: 0, + Ausgebrannt: false, + }, + Beschreibung: "Test equipment description", + Anzahl: 1, + BeinhaltetIn: "", + ContainedIn: 0, + ContainerType: "", + Bonus: 0, + Gewicht: 1.0, + Wert: 50.0, + } + + err := database.DB.Create(&testAusruestung).Error + require.NoError(t, err) + + tests := []struct { + name string + characterID string + expectedStatus int + expectedCount int + }{ + { + name: "Valid Character ID", + characterID: "123", + expectedStatus: http.StatusOK, + expectedCount: 1, + }, + { + name: "Non-existent Character ID", + characterID: "999", + expectedStatus: http.StatusOK, + expectedCount: 0, + }, + { + name: "Invalid Character ID", + characterID: "invalid", + expectedStatus: http.StatusOK, + expectedCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + c.Params = gin.Params{ + {Key: "character_id", Value: tt.characterID}, + } + + ListAusruestung(c) + + assert.Equal(t, tt.expectedStatus, w.Code) + + var response []models.EqAusruestung + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Len(t, response, tt.expectedCount) + + if tt.expectedCount > 0 { + assert.Equal(t, "Test Equipment", response[0].Name) + assert.Equal(t, uint(123), response[0].CharacterID) + } + }) + } +} + +func TestUpdateAusruestung(t *testing.T) { + database.SetupTestDB(true) + gin.SetMode(gin.TestMode) + + // Create test data + testAusruestung := models.EqAusruestung{ + BamortCharTrait: models.BamortCharTrait{ + BamortBase: models.BamortBase{ + Name: "Original Equipment", + }, + CharacterID: 123, + UserID: 1, + }, + Magisch: models.Magisch{ + IstMagisch: false, + Abw: 0, + Ausgebrannt: false, + }, + Beschreibung: "Original description", + Anzahl: 1, + BeinhaltetIn: "", + ContainedIn: 0, + ContainerType: "", + Bonus: 0, + Gewicht: 1.0, + Wert: 50.0, + } + + err := database.DB.Create(&testAusruestung).Error + require.NoError(t, err) + + tests := []struct { + name string + ausruestungID string + payload interface{} + expectedStatus int + shouldContain string + }{ + { + name: "Valid Update", + ausruestungID: strconv.Itoa(int(testAusruestung.ID)), + payload: map[string]interface{}{ + "name": "Updated Equipment", + "beschreibung": "Updated description", + "wert": 75.0, + }, + expectedStatus: http.StatusOK, + shouldContain: "Updated Equipment", + }, + { + name: "Non-existent Ausruestung", + ausruestungID: "999", + payload: map[string]interface{}{ + "name": "Updated Equipment", + }, + expectedStatus: http.StatusNotFound, + shouldContain: "error", + }, + { + name: "Invalid JSON", + ausruestungID: strconv.Itoa(int(testAusruestung.ID)), + payload: "invalid json", + expectedStatus: http.StatusBadRequest, + shouldContain: "error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + c.Params = gin.Params{ + {Key: "ausruestung_id", Value: tt.ausruestungID}, + } + + var body bytes.Buffer + if tt.name == "Invalid JSON" { + body = *bytes.NewBufferString("invalid json") + } else { + jsonData, _ := json.Marshal(tt.payload) + body = *bytes.NewBuffer(jsonData) + } + + c.Request = httptest.NewRequest("PUT", "/ausruestung/"+tt.ausruestungID, &body) + c.Request.Header.Set("Content-Type", "application/json") + + UpdateAusruestung(c) + + assert.Equal(t, tt.expectedStatus, w.Code) + if tt.shouldContain != "" { + assert.Contains(t, w.Body.String(), tt.shouldContain) + } + }) + } +} + +func TestDeleteAusruestung(t *testing.T) { + database.SetupTestDB(true) + gin.SetMode(gin.TestMode) + + // Create test data + testAusruestung := models.EqAusruestung{ + BamortCharTrait: models.BamortCharTrait{ + BamortBase: models.BamortBase{ + Name: "Equipment to Delete", + }, + CharacterID: 123, + UserID: 1, + }, + Magisch: models.Magisch{ + IstMagisch: false, + Abw: 0, + Ausgebrannt: false, + }, + Beschreibung: "Equipment for deletion test", + Anzahl: 1, + BeinhaltetIn: "", + ContainedIn: 0, + ContainerType: "", + Bonus: 0, + Gewicht: 1.0, + Wert: 50.0, + } + + err := database.DB.Create(&testAusruestung).Error + require.NoError(t, err) + + tests := []struct { + name string + ausruestungID string + expectedStatus int + shouldContain string + }{ + { + name: "Valid Deletion", + ausruestungID: strconv.Itoa(int(testAusruestung.ID)), + expectedStatus: http.StatusOK, + shouldContain: "deleted successfully", + }, + { + name: "Non-existent Ausruestung", + ausruestungID: "999", + expectedStatus: http.StatusOK, // GORM doesn't fail on deleting non-existent records + shouldContain: "deleted successfully", + }, + { + name: "Invalid Ausruestung ID", + ausruestungID: "invalid", + expectedStatus: http.StatusInternalServerError, + shouldContain: "error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + c.Params = gin.Params{ + {Key: "ausruestung_id", Value: tt.ausruestungID}, + } + + DeleteAusruestung(c) + + assert.Equal(t, tt.expectedStatus, w.Code) + if tt.shouldContain != "" { + assert.Contains(t, w.Body.String(), tt.shouldContain) + } + + // For successful deletion, verify the record is actually deleted + if tt.name == "Valid Deletion" && tt.expectedStatus == http.StatusOK { + var count int64 + database.DB.Model(&models.EqAusruestung{}).Where("id = ?", tt.ausruestungID).Count(&count) + assert.Equal(t, int64(0), count, "Equipment should be deleted from database") + } + }) + } +}