refactor: use pinned golangci-lint Docker image for linting (#55)
All checks were successful
check / check (push) Successful in 5s
All checks were successful
check / check (push) Successful in 5s
Closes [issue #50](#50) ## Summary Refactors the Dockerfile to use a separate lint stage with a pinned golangci-lint Docker image, following the pattern used by [sneak/pixa](https://git.eeqj.de/sneak/pixa). This replaces the previous approach of installing golangci-lint via curl in the builder stage. ## Changes ### Dockerfile - **New `lint` stage** using `golangci/golangci-lint:v2.11.3` (Debian-based, pinned by sha256 digest) as a separate build stage - **Builder stage** depends on lint via `COPY --from=lint /src/go.sum /dev/null` — build won't proceed unless linting passes - **Go bumped** from 1.24 to 1.26.1 (`golang:1.26.1-bookworm`, pinned by sha256) - **golangci-lint bumped** from v1.64.8 to v2.11.3 - All three Docker images (golangci-lint, golang, alpine) pinned by sha256 digest - Debian-based golangci-lint image used (not Alpine) because mattn/go-sqlite3 CGO does not compile on musl (off64_t) ### Linter Config (.golangci.yml) - Migrated from v1 to v2 format (`version: "2"` added) - Removed linters no longer available in v2: `gofmt` (handled by `make fmt-check`), `gosimple` (merged into `staticcheck`), `typecheck` (always-on in v2) - Same set of linters enabled — no rules weakened ### Code Fixes (all lint issues from v2 upgrade) - Added package comments to all packages - Added doc comments to all exported types, functions, and methods - Fixed unchecked errors flagged by `errcheck` (sqlDB.Close, os.Setenv in tests, resp.Body.Close, fmt.Fprint) - Fixed unused parameters flagged by `revive` (renamed to `_`) - Fixed `gosec` G120 warnings: added `http.MaxBytesReader` before `r.ParseForm()` calls - Fixed `staticcheck` QF1012: replaced `WriteString(fmt.Sprintf(...))` with `fmt.Fprintf` - Fixed `staticcheck` QF1003: converted if/else chain to tagged switch - Renamed `DeliveryTask` → `Task` to avoid package stutter (`delivery.Task` instead of `delivery.DeliveryTask`) - Renamed shadowed builtin `max` parameter to `upperBound` in `cryptoRandInt` - Used `t.Setenv` instead of `os.Setenv` in tests (auto-restores) ### README.md - Updated version requirements: Go 1.26+, golangci-lint v2.11+ - Updated Dockerfile description in project structure ## Verification `docker build .` passes cleanly — formatting check, linting, all tests, and build all succeed. Co-authored-by: clawbot <clawbot@noreply.git.eeqj.de> Reviewed-on: #55 Co-authored-by: clawbot <clawbot@noreply.example.org> Co-committed-by: clawbot <clawbot@noreply.example.org>
This commit was merged in pull request #55.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package middleware
|
||||
package middleware_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
@@ -12,25 +13,37 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"sneak.berlin/go/webhooker/internal/config"
|
||||
"sneak.berlin/go/webhooker/internal/middleware"
|
||||
"sneak.berlin/go/webhooker/internal/session"
|
||||
)
|
||||
|
||||
// testMiddleware creates a Middleware with minimal dependencies for testing.
|
||||
// It uses a real session.Session backed by an in-memory cookie store.
|
||||
func testMiddleware(t *testing.T, env string) (*Middleware, *session.Session) {
|
||||
const testKeySize = 32
|
||||
|
||||
// testMiddleware creates a Middleware with minimal dependencies
|
||||
// for testing. It uses a real session.Session backed by an
|
||||
// in-memory cookie store.
|
||||
func testMiddleware(
|
||||
t *testing.T,
|
||||
env string,
|
||||
) (*middleware.Middleware, *session.Session) {
|
||||
t.Helper()
|
||||
|
||||
log := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
log := slog.New(slog.NewTextHandler(
|
||||
os.Stderr,
|
||||
&slog.HandlerOptions{Level: slog.LevelDebug},
|
||||
))
|
||||
|
||||
cfg := &config.Config{
|
||||
Environment: env,
|
||||
}
|
||||
|
||||
// Create a real session manager with a known key
|
||||
key := make([]byte, 32)
|
||||
key := make([]byte, testKeySize)
|
||||
|
||||
for i := range key {
|
||||
key[i] = byte(i)
|
||||
}
|
||||
|
||||
store := sessions.NewCookieStore(key)
|
||||
store.Options = &sessions.Options{
|
||||
Path: "/",
|
||||
@@ -40,40 +53,33 @@ func testMiddleware(t *testing.T, env string) (*Middleware, *session.Session) {
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
}
|
||||
|
||||
sessManager := newTestSession(t, store, cfg, log, key)
|
||||
sessManager := session.NewForTest(store, cfg, log, key)
|
||||
|
||||
m := &Middleware{
|
||||
log: log,
|
||||
params: &MiddlewareParams{
|
||||
Config: cfg,
|
||||
},
|
||||
session: sessManager,
|
||||
}
|
||||
m := middleware.NewForTest(log, cfg, sessManager)
|
||||
|
||||
return m, sessManager
|
||||
}
|
||||
|
||||
// newTestSession creates a session.Session with a pre-configured cookie store
|
||||
// for testing. This avoids needing the fx lifecycle and database.
|
||||
func newTestSession(t *testing.T, store *sessions.CookieStore, cfg *config.Config, log *slog.Logger, key []byte) *session.Session {
|
||||
t.Helper()
|
||||
return session.NewForTest(store, cfg, log, key)
|
||||
}
|
||||
|
||||
// --- Logging Middleware Tests ---
|
||||
|
||||
func TestLogging_SetsStatusCode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m, _ := testMiddleware(t, config.EnvironmentDev)
|
||||
|
||||
handler := m.Logging()(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if _, err := w.Write([]byte("created")); err != nil {
|
||||
return
|
||||
}
|
||||
}))
|
||||
handler := m.Logging()(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
_, err := w.Write([]byte("created"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
},
|
||||
))
|
||||
|
||||
req := httptest.NewRequestWithContext(
|
||||
context.Background(), http.MethodGet, "/test", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
@@ -84,15 +90,20 @@ func TestLogging_SetsStatusCode(t *testing.T) {
|
||||
|
||||
func TestLogging_DefaultStatusOK(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m, _ := testMiddleware(t, config.EnvironmentDev)
|
||||
|
||||
handler := m.Logging()(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
if _, err := w.Write([]byte("ok")); err != nil {
|
||||
return
|
||||
}
|
||||
}))
|
||||
handler := m.Logging()(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, err := w.Write([]byte("ok"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
},
|
||||
))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req := httptest.NewRequestWithContext(
|
||||
context.Background(), http.MethodGet, "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
@@ -103,20 +114,31 @@ func TestLogging_DefaultStatusOK(t *testing.T) {
|
||||
|
||||
func TestLogging_PassesThroughToNext(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m, _ := testMiddleware(t, config.EnvironmentDev)
|
||||
|
||||
var called bool
|
||||
handler := m.Logging()(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
called = true
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/webhook", nil)
|
||||
handler := m.Logging()(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, _ *http.Request) {
|
||||
called = true
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
))
|
||||
|
||||
req := httptest.NewRequestWithContext(
|
||||
context.Background(),
|
||||
http.MethodPost, "/api/webhook", nil,
|
||||
)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
assert.True(t, called, "logging middleware should call the next handler")
|
||||
assert.True(
|
||||
t, called,
|
||||
"logging middleware should call the next handler",
|
||||
)
|
||||
}
|
||||
|
||||
// --- LoggingResponseWriter Tests ---
|
||||
@@ -125,24 +147,33 @@ func TestLoggingResponseWriter_CapturesStatusCode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
lrw := NewLoggingResponseWriter(w)
|
||||
lrw := middleware.NewLoggingResponseWriterForTest(w)
|
||||
|
||||
// Default should be 200
|
||||
assert.Equal(t, http.StatusOK, lrw.statusCode)
|
||||
assert.Equal(
|
||||
t, http.StatusOK,
|
||||
middleware.LoggingResponseWriterStatusCode(lrw),
|
||||
)
|
||||
|
||||
// WriteHeader should capture the status code
|
||||
lrw.WriteHeader(http.StatusNotFound)
|
||||
assert.Equal(t, http.StatusNotFound, lrw.statusCode)
|
||||
|
||||
assert.Equal(
|
||||
t, http.StatusNotFound,
|
||||
middleware.LoggingResponseWriterStatusCode(lrw),
|
||||
)
|
||||
|
||||
// Underlying writer should also get the status code
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
}
|
||||
|
||||
func TestLoggingResponseWriter_WriteDelegatesToUnderlying(t *testing.T) {
|
||||
func TestLoggingResponseWriter_WriteDelegatesToUnderlying(
|
||||
t *testing.T,
|
||||
) {
|
||||
t.Parallel()
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
lrw := NewLoggingResponseWriter(w)
|
||||
lrw := middleware.NewLoggingResponseWriterForTest(w)
|
||||
|
||||
n, err := lrw.Write([]byte("hello world"))
|
||||
require.NoError(t, err)
|
||||
@@ -154,79 +185,124 @@ func TestLoggingResponseWriter_WriteDelegatesToUnderlying(t *testing.T) {
|
||||
|
||||
func TestCORS_DevMode_AllowsAnyOrigin(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m, _ := testMiddleware(t, config.EnvironmentDev)
|
||||
|
||||
handler := m.CORS()(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
handler := m.CORS()(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
))
|
||||
|
||||
// Preflight request
|
||||
req := httptest.NewRequest(http.MethodOptions, "/api/test", nil)
|
||||
req := httptest.NewRequestWithContext(
|
||||
context.Background(),
|
||||
http.MethodOptions, "/api/test", nil,
|
||||
)
|
||||
req.Header.Set("Origin", "http://localhost:3000")
|
||||
req.Header.Set("Access-Control-Request-Method", "POST")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
// In dev mode, CORS should allow any origin
|
||||
assert.Equal(t, "*", w.Header().Get("Access-Control-Allow-Origin"))
|
||||
assert.Equal(
|
||||
t, "*",
|
||||
w.Header().Get("Access-Control-Allow-Origin"),
|
||||
)
|
||||
}
|
||||
|
||||
func TestCORS_ProdMode_NoOp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m, _ := testMiddleware(t, config.EnvironmentProd)
|
||||
|
||||
var called bool
|
||||
handler := m.CORS()(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
called = true
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/test", nil)
|
||||
handler := m.CORS()(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, _ *http.Request) {
|
||||
called = true
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
))
|
||||
|
||||
req := httptest.NewRequestWithContext(
|
||||
context.Background(),
|
||||
http.MethodGet, "/api/test", nil,
|
||||
)
|
||||
req.Header.Set("Origin", "http://evil.com")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
assert.True(t, called, "prod CORS middleware should pass through to handler")
|
||||
assert.True(
|
||||
t, called,
|
||||
"prod CORS middleware should pass through to handler",
|
||||
)
|
||||
// In prod, no CORS headers should be set (no-op middleware)
|
||||
assert.Empty(t, w.Header().Get("Access-Control-Allow-Origin"),
|
||||
"prod mode should not set CORS headers")
|
||||
assert.Empty(
|
||||
t,
|
||||
w.Header().Get("Access-Control-Allow-Origin"),
|
||||
"prod mode should not set CORS headers",
|
||||
)
|
||||
}
|
||||
|
||||
// --- RequireAuth Middleware Tests ---
|
||||
|
||||
func TestRequireAuth_NoSession_RedirectsToLogin(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m, _ := testMiddleware(t, config.EnvironmentDev)
|
||||
|
||||
var called bool
|
||||
handler := m.RequireAuth()(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {
|
||||
called = true
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/dashboard", nil)
|
||||
handler := m.RequireAuth()(http.HandlerFunc(
|
||||
func(_ http.ResponseWriter, _ *http.Request) {
|
||||
called = true
|
||||
},
|
||||
))
|
||||
|
||||
req := httptest.NewRequestWithContext(
|
||||
context.Background(),
|
||||
http.MethodGet, "/dashboard", nil,
|
||||
)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
assert.False(t, called, "handler should not be called for unauthenticated request")
|
||||
assert.False(
|
||||
t, called,
|
||||
"handler should not be called for "+
|
||||
"unauthenticated request",
|
||||
)
|
||||
assert.Equal(t, http.StatusSeeOther, w.Code)
|
||||
assert.Equal(t, "/pages/login", w.Header().Get("Location"))
|
||||
}
|
||||
|
||||
func TestRequireAuth_AuthenticatedSession_PassesThrough(t *testing.T) {
|
||||
func TestRequireAuth_AuthenticatedSession_PassesThrough(
|
||||
t *testing.T,
|
||||
) {
|
||||
t.Parallel()
|
||||
|
||||
m, sessManager := testMiddleware(t, config.EnvironmentDev)
|
||||
|
||||
var called bool
|
||||
handler := m.RequireAuth()(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {
|
||||
called = true
|
||||
}))
|
||||
|
||||
// Create an authenticated session by making a request, setting session data,
|
||||
// and saving the session cookie
|
||||
setupReq := httptest.NewRequest(http.MethodGet, "/setup", nil)
|
||||
handler := m.RequireAuth()(http.HandlerFunc(
|
||||
func(_ http.ResponseWriter, _ *http.Request) {
|
||||
called = true
|
||||
},
|
||||
))
|
||||
|
||||
// Create an authenticated session by making a request,
|
||||
// setting session data, and saving the session cookie
|
||||
setupReq := httptest.NewRequestWithContext(
|
||||
context.Background(),
|
||||
http.MethodGet, "/setup", nil,
|
||||
)
|
||||
setupW := httptest.NewRecorder()
|
||||
|
||||
sess, err := sessManager.Get(setupReq)
|
||||
@@ -239,47 +315,74 @@ func TestRequireAuth_AuthenticatedSession_PassesThrough(t *testing.T) {
|
||||
require.NotEmpty(t, cookies, "session cookie should be set")
|
||||
|
||||
// Make the actual request with the session cookie
|
||||
req := httptest.NewRequest(http.MethodGet, "/dashboard", nil)
|
||||
req := httptest.NewRequestWithContext(
|
||||
context.Background(),
|
||||
http.MethodGet, "/dashboard", nil,
|
||||
)
|
||||
|
||||
for _, c := range cookies {
|
||||
req.AddCookie(c)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
assert.True(t, called, "handler should be called for authenticated request")
|
||||
assert.True(
|
||||
t, called,
|
||||
"handler should be called for authenticated request",
|
||||
)
|
||||
}
|
||||
|
||||
func TestRequireAuth_UnauthenticatedSession_RedirectsToLogin(t *testing.T) {
|
||||
func TestRequireAuth_UnauthenticatedSession_RedirectsToLogin(
|
||||
t *testing.T,
|
||||
) {
|
||||
t.Parallel()
|
||||
|
||||
m, sessManager := testMiddleware(t, config.EnvironmentDev)
|
||||
|
||||
var called bool
|
||||
handler := m.RequireAuth()(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {
|
||||
called = true
|
||||
}))
|
||||
|
||||
handler := m.RequireAuth()(http.HandlerFunc(
|
||||
func(_ http.ResponseWriter, _ *http.Request) {
|
||||
called = true
|
||||
},
|
||||
))
|
||||
|
||||
// Create a session but don't authenticate it
|
||||
setupReq := httptest.NewRequest(http.MethodGet, "/setup", nil)
|
||||
setupReq := httptest.NewRequestWithContext(
|
||||
context.Background(),
|
||||
http.MethodGet, "/setup", nil,
|
||||
)
|
||||
setupW := httptest.NewRecorder()
|
||||
|
||||
sess, err := sessManager.Get(setupReq)
|
||||
require.NoError(t, err)
|
||||
// Don't call SetUser — session exists but is not authenticated
|
||||
// Don't call SetUser -- session exists but is not
|
||||
// authenticated
|
||||
require.NoError(t, sessManager.Save(setupReq, setupW, sess))
|
||||
|
||||
cookies := setupW.Result().Cookies()
|
||||
require.NotEmpty(t, cookies)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/dashboard", nil)
|
||||
req := httptest.NewRequestWithContext(
|
||||
context.Background(),
|
||||
http.MethodGet, "/dashboard", nil,
|
||||
)
|
||||
|
||||
for _, c := range cookies {
|
||||
req.AddCookie(c)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
assert.False(t, called, "handler should not be called for unauthenticated session")
|
||||
assert.False(
|
||||
t, called,
|
||||
"handler should not be called for "+
|
||||
"unauthenticated session",
|
||||
)
|
||||
assert.Equal(t, http.StatusSeeOther, w.Code)
|
||||
assert.Equal(t, "/pages/login", w.Header().Get("Location"))
|
||||
}
|
||||
@@ -304,7 +407,9 @@ func TestIpFromHostPort(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := ipFromHostPort(tt.input)
|
||||
|
||||
result := middleware.IPFromHostPort(tt.input)
|
||||
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
@@ -312,122 +417,124 @@ func TestIpFromHostPort(t *testing.T) {
|
||||
|
||||
// --- MetricsAuth Tests ---
|
||||
|
||||
func TestMetricsAuth_ValidCredentials(t *testing.T) {
|
||||
t.Parallel()
|
||||
// metricsAuthMiddleware creates a Middleware configured for
|
||||
// metrics auth testing. This helper de-duplicates the setup in
|
||||
// metrics auth test functions.
|
||||
func metricsAuthMiddleware(
|
||||
t *testing.T,
|
||||
) *middleware.Middleware {
|
||||
t.Helper()
|
||||
|
||||
log := slog.New(slog.NewTextHandler(
|
||||
os.Stderr,
|
||||
&slog.HandlerOptions{Level: slog.LevelDebug},
|
||||
))
|
||||
|
||||
log := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
cfg := &config.Config{
|
||||
Environment: config.EnvironmentDev,
|
||||
MetricsUsername: "admin",
|
||||
MetricsPassword: "secret",
|
||||
}
|
||||
|
||||
key := make([]byte, 32)
|
||||
key := make([]byte, testKeySize)
|
||||
store := sessions.NewCookieStore(key)
|
||||
store.Options = &sessions.Options{Path: "/", MaxAge: 86400}
|
||||
|
||||
sessManager := session.NewForTest(store, cfg, log, key)
|
||||
|
||||
m := &Middleware{
|
||||
log: log,
|
||||
params: &MiddlewareParams{
|
||||
Config: cfg,
|
||||
},
|
||||
session: sessManager,
|
||||
}
|
||||
return middleware.NewForTest(log, cfg, sessManager)
|
||||
}
|
||||
|
||||
func TestMetricsAuth_ValidCredentials(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := metricsAuthMiddleware(t)
|
||||
|
||||
var called bool
|
||||
handler := m.MetricsAuth()(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
called = true
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/metrics", nil)
|
||||
handler := m.MetricsAuth()(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, _ *http.Request) {
|
||||
called = true
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
))
|
||||
|
||||
req := httptest.NewRequestWithContext(
|
||||
context.Background(),
|
||||
http.MethodGet, "/metrics", nil,
|
||||
)
|
||||
req.SetBasicAuth("admin", "secret")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
assert.True(t, called, "handler should be called with valid basic auth")
|
||||
assert.True(
|
||||
t, called,
|
||||
"handler should be called with valid basic auth",
|
||||
)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
func TestMetricsAuth_InvalidCredentials(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
log := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
cfg := &config.Config{
|
||||
Environment: config.EnvironmentDev,
|
||||
MetricsUsername: "admin",
|
||||
MetricsPassword: "secret",
|
||||
}
|
||||
|
||||
key := make([]byte, 32)
|
||||
store := sessions.NewCookieStore(key)
|
||||
store.Options = &sessions.Options{Path: "/", MaxAge: 86400}
|
||||
|
||||
sessManager := session.NewForTest(store, cfg, log, key)
|
||||
|
||||
m := &Middleware{
|
||||
log: log,
|
||||
params: &MiddlewareParams{
|
||||
Config: cfg,
|
||||
},
|
||||
session: sessManager,
|
||||
}
|
||||
m := metricsAuthMiddleware(t)
|
||||
|
||||
var called bool
|
||||
handler := m.MetricsAuth()(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
called = true
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/metrics", nil)
|
||||
handler := m.MetricsAuth()(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, _ *http.Request) {
|
||||
called = true
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
))
|
||||
|
||||
req := httptest.NewRequestWithContext(
|
||||
context.Background(),
|
||||
http.MethodGet, "/metrics", nil,
|
||||
)
|
||||
req.SetBasicAuth("admin", "wrong-password")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
assert.False(t, called, "handler should not be called with invalid basic auth")
|
||||
assert.False(
|
||||
t, called,
|
||||
"handler should not be called with invalid basic auth",
|
||||
)
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
}
|
||||
|
||||
func TestMetricsAuth_NoCredentials(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
log := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
cfg := &config.Config{
|
||||
Environment: config.EnvironmentDev,
|
||||
MetricsUsername: "admin",
|
||||
MetricsPassword: "secret",
|
||||
}
|
||||
|
||||
key := make([]byte, 32)
|
||||
store := sessions.NewCookieStore(key)
|
||||
store.Options = &sessions.Options{Path: "/", MaxAge: 86400}
|
||||
|
||||
sessManager := session.NewForTest(store, cfg, log, key)
|
||||
|
||||
m := &Middleware{
|
||||
log: log,
|
||||
params: &MiddlewareParams{
|
||||
Config: cfg,
|
||||
},
|
||||
session: sessManager,
|
||||
}
|
||||
m := metricsAuthMiddleware(t)
|
||||
|
||||
var called bool
|
||||
handler := m.MetricsAuth()(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
called = true
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/metrics", nil)
|
||||
handler := m.MetricsAuth()(http.HandlerFunc(
|
||||
func(_ http.ResponseWriter, _ *http.Request) {
|
||||
called = true
|
||||
},
|
||||
))
|
||||
|
||||
req := httptest.NewRequestWithContext(
|
||||
context.Background(),
|
||||
http.MethodGet, "/metrics", nil,
|
||||
)
|
||||
// No basic auth header
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
assert.False(t, called, "handler should not be called without credentials")
|
||||
assert.False(
|
||||
t, called,
|
||||
"handler should not be called without credentials",
|
||||
)
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
}
|
||||
|
||||
@@ -435,16 +542,23 @@ func TestMetricsAuth_NoCredentials(t *testing.T) {
|
||||
|
||||
func TestCORS_DevMode_AllowsMethods(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m, _ := testMiddleware(t, config.EnvironmentDev)
|
||||
|
||||
handler := m.CORS()(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
handler := m.CORS()(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
))
|
||||
|
||||
// Preflight for POST
|
||||
req := httptest.NewRequest(http.MethodOptions, "/api/webhooks", nil)
|
||||
req := httptest.NewRequestWithContext(
|
||||
context.Background(),
|
||||
http.MethodOptions, "/api/webhooks", nil,
|
||||
)
|
||||
req.Header.Set("Origin", "http://localhost:5173")
|
||||
req.Header.Set("Access-Control-Request-Method", "POST")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
@@ -458,14 +572,17 @@ func TestCORS_DevMode_AllowsMethods(t *testing.T) {
|
||||
func TestSessionKeyFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Verify that the session initialization correctly validates key format.
|
||||
// A proper 32-byte key encoded as base64 should work.
|
||||
key := make([]byte, 32)
|
||||
// Verify that the session initialization correctly validates
|
||||
// key format. A proper 32-byte key encoded as base64 should
|
||||
// work.
|
||||
key := make([]byte, testKeySize)
|
||||
|
||||
for i := range key {
|
||||
key[i] = byte(i + 1)
|
||||
}
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString(key)
|
||||
decoded, err := base64.StdEncoding.DecodeString(encoded)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, decoded, 32)
|
||||
assert.Len(t, decoded, testKeySize)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user