feat: Implement error handling and response structure for API
- Added standardized error response structure in `errors.go` for consistent error handling across the API. - Implemented specific response functions for various HTTP status codes (400, 401, 403, 404, 500) to enhance error reporting. - Introduced validation error handling to provide detailed feedback on input validation issues. test: Add comprehensive tests for share handler functionality - Created a suite of tests for share handler endpoints, covering scenarios for creating, listing, deleting shares, and managing share links. - Included tests for permission checks, validation errors, and edge cases such as unauthorized access and invalid document IDs. chore: Set up test utilities and database for integration tests - Established a base handler suite for common setup tasks in tests, including database initialization and teardown. - Implemented test data seeding to facilitate consistent testing across different scenarios. migration: Add public sharing support in the database schema - Modified the `documents` table to include `share_token` and `is_public` columns for managing public document sharing. - Added constraints to ensure data integrity, preventing public documents from lacking a share token.
This commit is contained in:
129
backend/internal/handlers/suite_test.go
Normal file
129
backend/internal/handlers/suite_test.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/M1ngdaXie/realtime-collab/internal/store"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
// BaseHandlerSuite provides common setup for all handler tests
|
||||
type BaseHandlerSuite struct {
|
||||
suite.Suite
|
||||
store *store.PostgresStore
|
||||
cleanup func()
|
||||
testData *store.TestData
|
||||
jwtSecret string
|
||||
frontendURL string
|
||||
}
|
||||
|
||||
// SetupSuite runs once before all tests in the suite
|
||||
func (s *BaseHandlerSuite) SetupSuite() {
|
||||
s.store, s.cleanup = store.SetupTestDB(s.T())
|
||||
s.jwtSecret = "test-secret-key-do-not-use-in-production"
|
||||
s.frontendURL = "http://localhost:5173"
|
||||
gin.SetMode(gin.TestMode)
|
||||
}
|
||||
|
||||
// SetupTest runs before each test
|
||||
func (s *BaseHandlerSuite) SetupTest() {
|
||||
ctx := context.Background()
|
||||
err := store.TruncateAllTables(ctx, s.store)
|
||||
s.Require().NoError(err, "Failed to truncate tables")
|
||||
|
||||
testData, err := store.SeedTestData(ctx, s.store)
|
||||
s.Require().NoError(err, "Failed to seed test data")
|
||||
s.testData = testData
|
||||
}
|
||||
|
||||
// TearDownSuite runs once after all tests in the suite
|
||||
func (s *BaseHandlerSuite) TearDownSuite() {
|
||||
if s.cleanup != nil {
|
||||
s.cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: makeAuthRequest creates an authenticated HTTP request and returns recorder + request
|
||||
func (s *BaseHandlerSuite) makeAuthRequest(method, path string, body interface{}, userID uuid.UUID) (*httptest.ResponseRecorder, *http.Request, error) {
|
||||
var bodyReader *bytes.Reader
|
||||
if body != nil {
|
||||
jsonBody, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to marshal body: %w", err)
|
||||
}
|
||||
bodyReader = bytes.NewReader(jsonBody)
|
||||
} else {
|
||||
bodyReader = bytes.NewReader([]byte{})
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(method, path, bodyReader)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Generate JWT token
|
||||
token, err := store.GenerateTestJWT(userID, s.jwtSecret)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to generate JWT: %w", err)
|
||||
}
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
return w, req, nil
|
||||
}
|
||||
|
||||
// Helper: makePublicRequest creates an unauthenticated HTTP request
|
||||
func (s *BaseHandlerSuite) makePublicRequest(method, path string, body interface{}) (*httptest.ResponseRecorder, *http.Request, error) {
|
||||
var bodyReader *bytes.Reader
|
||||
if body != nil {
|
||||
jsonBody, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to marshal body: %w", err)
|
||||
}
|
||||
bodyReader = bytes.NewReader(jsonBody)
|
||||
} else {
|
||||
bodyReader = bytes.NewReader([]byte{})
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(method, path, bodyReader)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
return w, req, nil
|
||||
}
|
||||
|
||||
// Helper: parseErrorResponse parses an ErrorResponse from the response
|
||||
func (s *BaseHandlerSuite) parseErrorResponse(w *httptest.ResponseRecorder) ErrorResponse {
|
||||
var errResp ErrorResponse
|
||||
err := json.Unmarshal(w.Body.Bytes(), &errResp)
|
||||
s.Require().NoError(err, "Failed to parse error response")
|
||||
return errResp
|
||||
}
|
||||
|
||||
// Helper: assertErrorResponse checks that the response matches expected error
|
||||
func (s *BaseHandlerSuite) assertErrorResponse(w *httptest.ResponseRecorder, statusCode int, errorType string, messageContains string) {
|
||||
s.Equal(statusCode, w.Code, "Status code mismatch")
|
||||
|
||||
errResp := s.parseErrorResponse(w)
|
||||
s.Equal(errorType, errResp.Error, "Error type mismatch")
|
||||
|
||||
if messageContains != "" {
|
||||
s.Contains(errResp.Message, messageContains, "Error message should contain expected text")
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: assertSuccessResponse checks that the response is successful
|
||||
func (s *BaseHandlerSuite) assertSuccessResponse(w *httptest.ResponseRecorder, statusCode int) {
|
||||
s.Equal(statusCode, w.Code, "Status code mismatch. Body: %s", w.Body.String())
|
||||
}
|
||||
|
||||
// Helper: parseJSONResponse parses a JSON response into the target struct
|
||||
func (s *BaseHandlerSuite) parseJSONResponse(w *httptest.ResponseRecorder, target interface{}) {
|
||||
err := json.Unmarshal(w.Body.Bytes(), target)
|
||||
s.Require().NoError(err, "Failed to parse JSON response: %s", w.Body.String())
|
||||
}
|
||||
Reference in New Issue
Block a user