harperreed / micropub-service

MIT License
2 stars 0 forks source link

sweep: refactor, and add more tests to cmd/server/main.go: #13

Open harperreed opened 1 day ago

harperreed commented 1 day ago

Integration tests for the complete server setup Tests for middleware functionality (e.g., roleAuthorization) Tests for error handling in server initialization


Refactor and enhance testing for server initialization in cmd/server/main.go

Description:

Refactor the cmd/server/main.go file to improve testability and add comprehensive tests for server initialization, middleware functionality, and error handling.

Tasks:

Test:

In cmd/server/main_test.go, add the following test cases:

Additional tasks:

sweep-ai[bot] commented 1 day ago

🚀 Here's the PR! #20

Sweeping

Search Results

Response

To address your request for refactoring and enhancing the testing for server initialization in cmd/server/main.go, I'll guide you through the process of making the necessary changes and additions. Let's go through each task step by step.

  1. Refactoring cmd/server/main.go:

Let's start by extracting the server setup logic into separate functions and moving the middleware to a new file.

cmd/server/main.go

--- 
+++ 
@@ -1,3 +1,35 @@
+func initializeApp(cfg *config.Config) (*pocketbase.PocketBase, error) {
+   app := pocketbase.New()
+
+   // Initialize event emitter
+   eventEmitter := events.NewEventEmitter()
+   micropub.SetEventEmitter(eventEmitter)
+
+   // Initialize Git repository
+   if err := git.InitializeRepo(); err != nil {
+       return nil, fmt.Errorf("failed to initialize Git repository: %v", err)
+   }
+
+   // Set up file cleanup process
+   setupFileCleanup(eventEmitter)
+
+   return app, nil
+}
+
+func setupRoutes(app *pocketbase.PocketBase) {
+   app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
+       e.Router.POST("/micropub", echo.HandlerFunc(micropub.HandleMicropubCreate), middleware.RoleAuthorization("admin", "editor"))
+       e.Router.PUT("/micropub", echo.HandlerFunc(micropub.HandleMicropubUpdate), middleware.RoleAuthorization("admin", "editor"))
+       e.Router.DELETE("/micropub", echo.HandlerFunc(micropub.HandleMicropubDelete), middleware.RoleAuthorization("admin"))
+
+       // Add routes for login
+       e.Router.GET("/login", echo.HandlerFunc(handleLoginPage))
+       e.Router.POST("/login", echo.HandlerFunc(handleLogin))
+
+       return nil
+   })
+}
+
 func main() {
    // Load configuration
    cfg, err := config.Load()
@@ -8,31 +40,12 @@
    // Use the configuration
    log.Printf("Git repository path: %s", cfg.GitRepoPath)

-   app := pocketbase.New()
-
-   // Initialize event emitter
-   eventEmitter := events.NewEventEmitter()
-   micropub.SetEventEmitter(eventEmitter)
-
-   // Initialize Git repository
-   if err := git.InitializeRepo(); err != nil {
-       log.Fatalf("Failed to initialize Git repository: %v", err)
+   app, err := initializeApp(cfg)
+   if err != nil {
+       log.Fatalf("Failed to initialize application: %v", err)
    }

-   // Set up file cleanup process
-   setupFileCleanup(eventEmitter)
-
-   app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
-       e.Router.POST("/micropub", echo.HandlerFunc(micropub.HandleMicropubCreate), roleAuthorization("admin", "editor"))
-       e.Router.PUT("/micropub", echo.HandlerFunc(micropub.HandleMicropubUpdate), roleAuthorization("admin", "editor"))
-       e.Router.DELETE("/micropub", echo.HandlerFunc(micropub.HandleMicropubDelete), roleAuthorization("admin"))
-
-       // Add routes for login
-       e.Router.GET("/login", echo.HandlerFunc(handleLoginPage))
-       e.Router.POST("/login", echo.HandlerFunc(handleLogin))
-
-       return nil
-   })
+   setupRoutes(app)

    if err := app.Start(); err != nil {
        log.Fatal(err)

Now, let's create a new file for the middleware functions:

internal/middleware/middleware.go

--- 
+++ 
@@ -1 +1,49 @@
+package middleware

+import (
+   "net/http"
+   "time"
+
+   "github.com/labstack/echo/v5"
+   "github.com/patrickmn/go-cache"
+   "github.com/pocketbase/pocketbase/models"
+)
+
+var userRoleCache *cache.Cache
+
+func init() {
+   userRoleCache = cache.New(5*time.Minute, 10*time.Minute)
+}
+
+func RoleAuthorization(allowedRoles ...string) echo.MiddlewareFunc {
+   return func(next echo.HandlerFunc) echo.HandlerFunc {
+       return func(c echo.Context) error {
+           user, _ := c.Get("user").(*models.Record)
+
+           if user == nil {
+               return c.String(http.StatusUnauthorized, "You must be logged in to access this resource")
+           }
+
+           userRole := getUserRole(user.Id)
+           for _, role := range allowedRoles {
+               if userRole == role {
+                   return next(c)
+               }
+           }
+
+           return c.String(http.StatusForbidden, "You are not authorized to access this resource")
+       }
+   }
+}
+
+func getUserRole(userId string) string {
+   if cachedRole, found := userRoleCache.Get(userId); found {
+       return cachedRole.(string)
+   }
+
+   // If not found in cache, fetch from database and cache it
+   // This is a placeholder - replace with actual database query
+   role := "user" // Default role
+   userRoleCache.Set(userId, role, cache.DefaultExpiration)
+   return role
+}
  1. Creating cmd/server/main_test.go for integration tests:

cmd/server/main_test.go

--- 
+++ 
@@ -1 +1,115 @@
+package main

+import (
+   "net/http"
+   "net/http/httptest"
+   "testing"
+
+   "github.com/harperreed/micropub-service/internal/config"
+   "github.com/harperreed/micropub-service/internal/middleware"
+   "github.com/labstack/echo/v5"
+   "github.com/pocketbase/pocketbase"
+   "github.com/pocketbase/pocketbase/models"
+   "github.com/stretchr/testify/assert"
+   "github.com/stretchr/testify/mock"
+)
+
+type MockPocketBase struct {
+   mock.Mock
+}
+
+func (m *MockPocketBase) OnBeforeServe() *pocketbase.Hook {
+   args := m.Called()
+   return args.Get(0).(*pocketbase.Hook)
+}
+
+func (m *MockPocketBase) Start() error {
+   args := m.Called()
+   return args.Error(0)
+}
+
+func TestServerInitialization(t *testing.T) {
+   cfg := &config.Config{
+       GitRepoPath: "/tmp/test-repo",
+   }
+
+   app, err := initializeApp(cfg)
+   assert.NoError(t, err)
+   assert.NotNil(t, app)
+
+   mockApp := new(MockPocketBase)
+   mockApp.On("OnBeforeServe").Return(pocketbase.NewHook())
+   mockApp.On("Start").Return(nil)
+
+   setupRoutes(mockApp)
+
+   mockApp.AssertCalled(t, "OnBeforeServe")
+   mockApp.AssertCalled(t, "Start")
+}
+
+func TestRoleAuthorizationMiddleware(t *testing.T) {
+   e := echo.New()
+   req := httptest.NewRequest(http.MethodGet, "/", nil)
+   rec := httptest.NewRecorder()
+   c := e.NewContext(req, rec)
+
+   // Test with no user
+   handler := middleware.RoleAuthorization("admin")(func(c echo.Context) error {
+       return c.String(http.StatusOK, "OK")
+   })
+
+   err := handler(c)
+   assert.Error(t, err)
+   assert.Equal(t, "You must be logged in to access this resource", err.Error())
+
+   // Test with user but wrong role
+   c.Set("user", &models.Record{Id: "user123"})
+   err = handler(c)
+   assert.Error(t, err)
+   assert.Equal(t, "You are not authorized to access this resource", err.Error())
+
+   // Test with correct role
+   middleware.GetUserRole = func(userId string) string {
+       return "admin"
+   }
+   err = handler(c)
+   assert.NoError(t, err)
+}
+
+func TestErrorHandling(t *testing.T) {
+   // Test with invalid configuration
+   _, err := config.Load()
+   assert.Error(t, err)
+
+   // Test with invalid Git repository path
+   cfg := &config.Config{
+       GitRepoPath: "/invalid/path",
+   }
+   _, err = initializeApp(cfg)
+   assert.Error(t, err)
+}
+
+func TestLoginFunctionality(t *testing.T) {
+   e := echo.New()
+   
+   // Test successful login
+   req := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader("email=test@example.com&password=password"))
+   req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
+   rec := httptest.NewRecorder()
+   c := e.NewContext(req, rec)
+
+   err := handleLogin(c)
+   assert.NoError(t, err)
+
+   assert.Equal(t, http.StatusOK, rec.Code)
+
+   // Test login failure
+   req = httptest.NewRequest(http.MethodPost, "/login", strings.NewReader("email=wrong@example.com&password=wrongpassword"))
+   req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
+   rec = httptest.NewRecorder()
+   c = e.NewContext(req, rec)
+
+   err = handleLogin(c)
+   assert.Error(t, err)
+   assert.Equal(t, http.StatusUnauthorized, rec.Code)
+}
  1. Updating internal/config/config.go to support test configurations:

internal/config/config.go

--- 
+++ 
@@ -1,9 +1,15 @@
 // Load reads the configuration from a JSON file and returns a Config struct.
 // It returns an error if the file cannot be read or parsed.
 func Load() (*Config, error) {
-   log.Println("Loading configuration from config.json")
+   return LoadFromFile("config.json")
+}

-   file, err := os.Open("config.json")
+// LoadFromFile reads the configuration from a specified JSON file and returns a Config struct.
+// It returns an error if the file cannot be read or parsed.
+func LoadFromFile(filename string) (*Config, error) {
+   log.Printf("Loading configuration from %s", filename)
+
+   file, err := os.Open(filename)
    if err != nil {
        log.Printf("Error opening config file: %v", err)
        return nil, fmt.Errorf("failed to open config file: %w", err)
@@ -29,3 +35,10 @@
    log.Println("Configuration loaded successfully")
    return &config, nil
 }
+
+// LoadTestConfig creates a test configuration for use in unit tests.
+func LoadTestConfig() *Config {
+   return &Config{
+       GitRepoPath: "/tmp/test-repo",
+   }
+}
  1. Updating README.md to reflect the new testing approach:

README.md

--- 
+++ 
@@ -1,8 +1,10 @@
 ## Testing

-This project uses **Test-Driven Development (TDD)**.
+This project uses **Test-Driven Development (TDD)** and includes both unit tests and integration tests.

 - **Running Tests**
+
+  To run all tests, including unit tests and integration tests:

   ```bash
   go test ./...
@@ -10,4 +12,19 @@

 - **Test Coverage**

-  Ensure all tests pass before deploying or updating the application.
+  To run tests with coverage:
+
+  ```bash
+  go test -coverprofile=coverage.out ./...
+  go tool cover -html=coverage.out
+  ```
+
+- **Integration Tests**
+
+  Integration tests for server initialization and middleware functionality are located in `cmd/server/main_test.go`.
+
+- **Test Configuration**
+
+  The `internal/config/config.go` file includes support for test configurations. Use `LoadTestConfig()` for test setups.
+
+Ensure all tests pass before deploying or updating the application.
  1. Updating the CI pipeline:

.github/workflows/go-tests.yaml

--- 
+++ 
@@ -30,3 +30,24 @@

     - name: Run tests with race detector
       run: go test -race -v ./...
+
+    - name: Run tests with coverage
+
+      run: go test -coverprofile=coverage.out ./...
+
+    - name: Upload coverage report
+      uses: actions/upload-artifact@v2
+      with:
+        name: coverage-report
+        path: coverage.out
+
+    - name: Convert coverage to Cobertura format
+      run: |
+        go install github.com/t-yuki/gocover-cobertura@latest
+        gocover-cobertura < coverage.out > coverage.xml
+
+    - name: Upload Cobertura coverage report
+      uses: actions/upload-artifact@v2
+      with:
+        name: coverage-report-cobertura
+        path: coverage.xml

These changes refactor the cmd/server/main.go file, create a new file for middleware functions, add comprehensive integration tests, update the configuration handling to support test configurations, update the README with the new testing approach, and enhance the CI pipeline to include coverage reporting.

The refactoring improves the testability of the server initialization process by extracting the setup logic into separate functions. The new integration tests in cmd/server/main_test.go cover server initialization, middleware functionality, and error handling scenarios.

The updates to internal/config/config.go allow for easier testing by providing a way to load test configurations. The README has been updated to reflect these changes and provide more detailed information about running tests and checking coverage.

Finally, the CI pipeline has been updated to include coverage reporting, which will help maintain and improve code quality over time.

These changes should significantly improve the testability and maintainability of the codebase, particularly for the server initialization and middleware components.

Continue the conversation with Sweep here: https://app.sweep.dev/c/c05e8701-0224-4e48-8f7a-5a7058958ae8.

:book: For more information on how to use Sweep, please read our documentation.

Tracking ID: 3d734b9186