Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codecoverage improve middlewares #19

Merged
merged 4 commits into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading