veza/veza-backend-api/internal/response/response_test.go
senke 8e9ee2f3a5 fix: stabilize builds, tests, and lint across all stacks
Complete stabilization pass bringing all 3 stacks to green:

Frontend (apps/web/):
- Fix TypeScript nullability in useSeason.ts, useTimeOfDay.ts hooks
- Disable no-undef in ESLint config (TypeScript handles it; JSX misidentified)
- Rename 306 story imports from @storybook/react to @storybook/react-vite
- Fix conditional hook call in useMediaQuery.ts useIsTablet
- Move useQuery to top of LoginPage.tsx component
- Remove useless try/catch in GearFormModal.tsx
- Fix stale closure in ResetPasswordPage.tsx handleChange
- Make Storybook decorators (withRouter, withQueryClient, withToast, withAudio)
  no-ops since global StorybookDecorator already provides these — prevents
  nested Router / duplicate provider crashes in vitest-browser
- Fix nested MemoryRouter in 3 page stories (TrackDetail, PlaylistDetail, UserProfile)
- Update i18n initialization in test setup (await init before changeLanguage)
- Update ~30 test assertions from English to French to match i18n translations
- Update test assertions to match SUMI V3 design changes (shadow vs border)
- Fix remaining story type errors (PlayerError, PlaylistBatchActions,
  TrackFilters, VirtualizedChatMessages)

Backend (veza-backend-api/):
- Fix response_test.go RespondWithAppError signature (2 args, not 3)
- Fix TestErrorContractAuthEndpoints expected error codes
  (ErrCodeUnauthorized vs ErrCodeInvalidCredentials)
- Fix TestTrackHandler_GetTrackLikes_Success missing auth middleware setup
- Fix TestPlaybackAnalyticsService_GetTrackStats k-anonymity threshold
  (needs 5 unique users, not 1)
- Replace NOW() PostgreSQL function with time.Now() parameter in marketplace
  service for SQLite test compatibility
- Add missing AutoMigrate entries in marketplace_test.go
  (ProductImage, ProductPreview, ProductLicense, ProductReview)

Results:
- Frontend TypeCheck: 617 errors -> 0 errors
- Frontend ESLint: 349 errors -> 0 errors
- Frontend Vitest: 196 failing tests -> 1 skipped (3396/3397 passing)
- Backend go vet: 1 error -> 0 errors
- Backend tests: 5 failing -> all 13 packages passing
- Rust: 150/150 tests passing (unchanged)
- Storybook audit: 0 errors across 1244 stories

Triage report: docs/TRIAGE_REPORT.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:48:07 +02:00

331 lines
10 KiB
Go

package response
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
apperrors "veza-backend-api/internal/errors"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestResponseFormat_Success verifies that Success() returns wrapped format
// Action 1.3.2.5: Test wrapped format for success responses
func TestResponseFormat_Success(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
router.GET("/test", func(c *gin.Context) {
Success(c, gin.H{"id": "123", "name": "Test"})
})
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
// Verify wrapped format
assert.True(t, response["success"].(bool), "Response should have success: true")
assert.Contains(t, response, "data", "Response should have data field")
data, ok := response["data"].(map[string]interface{})
require.True(t, ok, "Data should be an object")
assert.Equal(t, "123", data["id"])
assert.Equal(t, "Test", data["name"])
}
// TestResponseFormat_SuccessWithMessage verifies Success() with message
// Action 1.3.2.5: Test wrapped format with message field
func TestResponseFormat_SuccessWithMessage(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
router.GET("/test", func(c *gin.Context) {
Success(c, gin.H{"id": "123"}, "Operation successful")
})
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
// Verify wrapped format
assert.True(t, response["success"].(bool))
assert.Contains(t, response, "data")
assert.Equal(t, "Operation successful", response["message"])
}
// TestResponseFormat_Created verifies that Created() returns wrapped format
// Action 1.3.2.5: Test wrapped format for created responses
func TestResponseFormat_Created(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
router.POST("/test", func(c *gin.Context) {
Created(c, gin.H{"id": "456", "name": "New Resource"})
})
req := httptest.NewRequest("POST", "/test", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
// Verify wrapped format
assert.True(t, response["success"].(bool), "Response should have success: true")
assert.Contains(t, response, "data", "Response should have data field")
data, ok := response["data"].(map[string]interface{})
require.True(t, ok, "Data should be an object")
assert.Equal(t, "456", data["id"])
assert.Equal(t, "New Resource", data["name"])
}
// TestResponseFormat_Error verifies that Error() returns wrapped format
// Action 1.3.2.5: Test wrapped format for error responses
func TestResponseFormat_Error(t *testing.T) {
gin.SetMode(gin.TestMode)
tests := []struct {
name string
statusCode int
message string
expectedStatus int
}{
{
name: "BadRequest",
statusCode: http.StatusBadRequest,
message: "Validation failed",
expectedStatus: http.StatusBadRequest,
},
{
name: "Unauthorized",
statusCode: http.StatusUnauthorized,
message: "Unauthorized",
expectedStatus: http.StatusUnauthorized,
},
{
name: "Forbidden",
statusCode: http.StatusForbidden,
message: "Forbidden",
expectedStatus: http.StatusForbidden,
},
{
name: "NotFound",
statusCode: http.StatusNotFound,
message: "Not found",
expectedStatus: http.StatusNotFound,
},
{
name: "InternalServerError",
statusCode: http.StatusInternalServerError,
message: "Internal server error",
expectedStatus: http.StatusInternalServerError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
router := gin.New()
router.GET("/test", func(c *gin.Context) {
Error(c, tt.statusCode, tt.message)
})
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, tt.expectedStatus, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
// Verify wrapped format
assert.False(t, response["success"].(bool), "Error response should have success: false")
assert.Nil(t, response["data"], "Error response should have null data")
assert.Contains(t, response, "error", "Error response should have error field")
// Verify error structure
errorObj, ok := response["error"].(map[string]interface{})
require.True(t, ok, "Error should be an object")
assert.Contains(t, errorObj, "code", "Error should have code field")
assert.Contains(t, errorObj, "message", "Error should have message field")
assert.Contains(t, errorObj, "timestamp", "Error should have timestamp field")
assert.Equal(t, tt.message, errorObj["message"])
})
}
}
// TestResponseFormat_RespondWithAppError verifies RespondWithAppError() returns wrapped format
// Action 1.3.2.5: Test wrapped format for AppError responses
func TestResponseFormat_RespondWithAppError(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
router.GET("/test", func(c *gin.Context) {
appErr := apperrors.New(apperrors.ErrCodeValidation, "Validation failed")
RespondWithAppError(c, appErr)
})
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
// Verify wrapped format
assert.False(t, response["success"].(bool), "Error response should have success: false")
assert.Nil(t, response["data"], "Error response should have null data")
assert.Contains(t, response, "error", "Error response should have error field")
// Verify error structure
errorObj, ok := response["error"].(map[string]interface{})
require.True(t, ok, "Error should be an object")
assert.Equal(t, float64(apperrors.ErrCodeValidation), errorObj["code"])
assert.Equal(t, "Validation failed", errorObj["message"])
assert.Contains(t, errorObj, "timestamp")
}
// TestResponseFormat_ValidationError verifies ValidationError() returns wrapped format
// Action 1.3.2.5: Test wrapped format for validation error responses
func TestResponseFormat_ValidationError(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
router.POST("/test", func(c *gin.Context) {
details := map[string]string{
"email": "invalid email format",
"password": "password too short",
}
ValidationError(c, "Validation failed", details)
})
req := httptest.NewRequest("POST", "/test", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
// Verify wrapped format
assert.False(t, response["success"].(bool), "Error response should have success: false")
assert.Nil(t, response["data"], "Error response should have null data")
assert.Contains(t, response, "error", "Error response should have error field")
// Verify error structure with details
errorObj, ok := response["error"].(map[string]interface{})
require.True(t, ok, "Error should be an object")
assert.Contains(t, errorObj, "details", "Error should have details field")
details, ok := errorObj["details"].([]interface{})
require.True(t, ok, "Details should be an array")
assert.Len(t, details, 2, "Should have 2 validation errors")
}
// TestResponseFormat_AllHelpersUseWrappedFormat verifies all helper functions use wrapped format
// Action 1.3.2.5: Comprehensive test to ensure all response helpers return wrapped format
func TestResponseFormat_AllHelpersUseWrappedFormat(t *testing.T) {
gin.SetMode(gin.TestMode)
helpers := []struct {
name string
handler gin.HandlerFunc
}{
{
name: "Success",
handler: func(c *gin.Context) {
Success(c, gin.H{"test": "data"})
},
},
{
name: "Created",
handler: func(c *gin.Context) {
Created(c, gin.H{"test": "data"})
},
},
{
name: "BadRequest",
handler: func(c *gin.Context) {
BadRequest(c, "Bad request")
},
},
{
name: "Unauthorized",
handler: func(c *gin.Context) {
Unauthorized(c, "Unauthorized")
},
},
{
name: "Forbidden",
handler: func(c *gin.Context) {
Forbidden(c, "Forbidden")
},
},
{
name: "NotFound",
handler: func(c *gin.Context) {
NotFound(c, "Not found")
},
},
{
name: "InternalServerError",
handler: func(c *gin.Context) {
InternalServerError(c, "Internal server error")
},
},
}
for _, helper := range helpers {
t.Run(helper.name, func(t *testing.T) {
router := gin.New()
router.GET("/test", helper.handler)
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err, "Response should be valid JSON")
// All responses must have 'success' field (wrapped format)
assert.Contains(t, response, "success", "Response must have 'success' field (wrapped format)")
// Success responses should have success: true and data field
if helper.name == "Success" || helper.name == "Created" {
assert.True(t, response["success"].(bool), "Success response should have success: true")
assert.Contains(t, response, "data", "Success response should have data field")
} else {
// Error responses should have success: false and error field
assert.False(t, response["success"].(bool), "Error response should have success: false")
assert.Contains(t, response, "error", "Error response should have error field")
}
})
}
}