Skip to content

Commit

Permalink
Codecoverage improve middlewares (#19)
Browse files Browse the repository at this point in the history
* codecov
* codecove middleware
* testing code coverage
* deleting token cache test for now
  • Loading branch information
nicobistolfi authored Sep 22, 2024
1 parent 4a02065 commit f41c697
Show file tree
Hide file tree
Showing 11 changed files with 491 additions and 25 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
name: Run tests and upload coverage

on:
push
on: push

jobs:
test:
Expand Down
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ go 1.22.5
require (
github.com/aws/aws-lambda-go v1.47.0
github.com/awslabs/aws-lambda-go-api-proxy v0.16.2
github.com/gin-contrib/cors v1.7.2
github.com/gin-gonic/gin v1.9.1
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/joho/godotenv v1.5.1
Expand All @@ -14,6 +13,12 @@ require (
golang.org/x/time v0.5.0
)

require (
github.com/kr/pretty v0.3.0 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)

require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
Expand Down
11 changes: 9 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw=
github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
Expand Down Expand Up @@ -46,8 +45,12 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
Expand All @@ -67,8 +70,10 @@ github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg=
github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down Expand Up @@ -111,8 +116,10 @@ golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=
google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
51 changes: 51 additions & 0 deletions internal/api/middleware/auth_middleware_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package middleware

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/gin-gonic/gin"
)

func TestAuthMiddlewareFunc(t *testing.T) {
// Set Gin to Test Mode
gin.SetMode(gin.TestMode)

// Create a new Gin engine
r := gin.New()

// Use the AuthMiddleware
protected := r.Group("/")
protected.Use(AuthMiddleware())
// Define a test route
protected.GET("/protected", func(c *gin.Context) {
c.String(http.StatusOK, "protected")
})

tests := []struct {
name string
token string
expectedStatus int
}{
{"Valid Token", "Bearer valid_token", http.StatusOK},
{"Invalid Token", "Bearer invalid_token", http.StatusOK}, // This is expected to pass, as the auth only checks for the token presence
{"No Token", "", http.StatusUnauthorized},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req, _ := http.NewRequest("GET", "/protected", nil)
if tt.token != "" {
req.Header.Set("Authorization", tt.token)
}
resp := httptest.NewRecorder()

r.ServeHTTP(resp, req)

if resp.Code != tt.expectedStatus {
t.Errorf("Expected status %d; got %d", tt.expectedStatus, resp.Code)
}
})
}
}
30 changes: 18 additions & 12 deletions internal/api/middleware/cors.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
package middleware

import (
"net/http"
"os"
"strings"

"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)

func CORSMiddleware() gin.HandlerFunc {
config := cors.DefaultConfig()
return func(c *gin.Context) {
origin := c.Request.Header.Get("Origin")
allowedOrigins := os.Getenv("ALLOWED_ORIGINS")

allowedOrigins := os.Getenv("ALLOWED_ORIGINS")
if allowedOrigins != "" {
config.AllowOrigins = strings.Split(allowedOrigins, ",")
} else {
config.AllowOriginFunc = func(origin string) bool {
return strings.HasPrefix(origin, "http://localhost") || strings.HasPrefix(origin, "https://localhost")
if origin != "" && (allowedOrigins == "*" || strings.Contains(allowedOrigins, origin)) {
c.Header("Access-Control-Allow-Origin", origin)
} else {
c.Header("Access-Control-Allow-Origin", "*")
}
}

config.AllowMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}
config.AllowHeaders = []string{"Origin", "Content-Type", "Accept", "Authorization"}
c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
c.Header("Access-Control-Allow-Headers", "Authorization,Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")

if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusOK)
return
}

return cors.New(config)
c.Next()
}
}
77 changes: 77 additions & 0 deletions internal/api/middleware/cors_middleware_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package middleware

import (
"net/http"
"net/http/httptest"
"os"
"testing"

"github.com/gin-gonic/gin"
)

func TestCORSMiddlewareFunc(t *testing.T) {
// Set Gin to Test Mode
gin.SetMode(gin.TestMode)

// Create a new Gin engine
r := gin.New()

// Use the CORSMiddleware
r.Use(CORSMiddleware())

// Define a test route
r.GET("/test", func(c *gin.Context) {
c.String(http.StatusOK, "test")
})

tests := []struct {
name string
method string
origin string
expectedStatus int
expectedHeaders map[string]string
}{
{
name: "OPTIONS request",
method: "OPTIONS",
origin: "http://example.com",
expectedStatus: http.StatusOK,
expectedHeaders: map[string]string{
"Access-Control-Allow-Origin": "http://example.com",
"Access-Control-Allow-Methods": "GET,POST,PUT,PATCH,DELETE,OPTIONS",
"Access-Control-Allow-Headers": "Authorization,Content-Type",
"Access-Control-Allow-Credentials": "true",
},
},
{
name: "GET request",
method: "GET",
origin: "http://example.com",
expectedStatus: http.StatusOK,
expectedHeaders: map[string]string{
"Access-Control-Allow-Origin": "http://example.com",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
os.Setenv("ALLOWED_ORIGINS", tt.origin)
req, _ := http.NewRequest(tt.method, "/test", nil)
req.Header.Set("Origin", tt.origin)
resp := httptest.NewRecorder()

r.ServeHTTP(resp, req)

if resp.Code != tt.expectedStatus {
t.Errorf("Expected status %d; got %d", tt.expectedStatus, resp.Code)
}

for key, value := range tt.expectedHeaders {
if resp.Header().Get(key) != value {
t.Errorf("Expected header %s to be %s; got %s", key, value, resp.Header().Get(key))
}
}
})
}
}
46 changes: 46 additions & 0 deletions internal/api/middleware/logging_middleware_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package middleware

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/gin-gonic/gin"
customLogger "github.com/nicobistolfi/go-rest-api/pkg"
)

func TestLoggerMiddlewareFunc(t *testing.T) {
customLogger.Init()
// Set Gin to Test Mode
gin.SetMode(gin.TestMode)

// Create a new Gin engine
r := gin.New()

// Create a custom logger
logger := customLogger.Log

// Use the LoggerMiddleware
r.Use(LoggerMiddleware(logger))

// Define a test route
r.GET("/test", func(c *gin.Context) {
c.String(http.StatusOK, "test")
})

// Create a test request
req, _ := http.NewRequest("GET", "/test", nil)
resp := httptest.NewRecorder()

// Serve the request
r.ServeHTTP(resp, req)

// Check the status code
if resp.Code != http.StatusOK {
t.Errorf("Expected status %d; got %d", http.StatusOK, resp.Code)
}

// Add more assertions here to check logging behavior
// For example, you could use a custom io.Writer to capture log output
// and assert on its contents
}
17 changes: 16 additions & 1 deletion internal/api/middleware/rate_limiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"golang.org/x/time/rate"
)

func RateLimiter(r rate.Limit, b int) gin.HandlerFunc {
func RateLimiter(r rate.Limit, b int, keyPrefixes ...string) gin.HandlerFunc {
type client struct {
limiter *rate.Limiter
lastSeen int64 //lint:ignore U1000 This field is currently unused but may be used in future implementations
Expand All @@ -23,10 +23,25 @@ func RateLimiter(r rate.Limit, b int) gin.HandlerFunc {
return func(c *gin.Context) {
// Use only IP if Authorization header is empty
key := c.ClientIP()
// Check if c.ClientIP() is empty
if key == "" {
// get the first X-Real-Ip header
key = c.GetHeader("X-Real-Ip")
}
if key == "" {
// get the first X-Forwarded-For header
key = c.GetHeader("X-Forwarded-For")
}

if auth := c.GetHeader("Authorization"); auth != "" {
key += ":" + auth
}

// Add the optional keyPrefix to the key if provided
if len(keyPrefixes) > 0 && keyPrefixes[0] != "" {
key = keyPrefixes[0] + ":" + key
}

mu.Lock()
if _, found := clients[key]; !found {
clients[key] = &client{limiter: rate.NewLimiter(r, b)}
Expand Down
Loading

0 comments on commit f41c697

Please sign in to comment.