From 24a602cf362851c612e2204d67d28365847cb783 Mon Sep 17 00:00:00 2001 From: k0d3r1s Date: Tue, 7 Jul 2026 04:25:24 +0300 Subject: [PATCH] Adopt kvelmo make quality flow and align CI versions Port the Go quality workflow from the kvelmo project into this plugin and align CI tooling versions with the already-bumped go 1.26 module. Makefile: - Add a `quality` target (goimports -> gofumpt -> go vet -> import-alias check -> golangci-lint --fix) and make `quality test coverage` the default. - Fail-loud import-alias check: guard on gawk and treat a non-zero exit or any output as failure, so a missing gawk can no longer silently pass. - Drop `|| true` masking on the formatters; add friendly missing-tool guards for gawk/golangci-lint; add a `tools` target and `.NOTPARALLEL`. - Fix coverage-html to read covprofile (was coverage.out). CI: - main.yml: Go 1.26, golangci-lint v2.12.2, checkout@v7, unconditional gawk install, pinned goimports/gofumpt/goveralls, least-privilege permissions, and a post-`make` `git diff --exit-code` so `--fix` cannot launder drift. - go-cross.yml: matrix -> [1.26, 1.x], checkout@v7, read-only permissions. Adopt the broader .golangci.yml (v2, gofumpt formatter) and reconcile the existing code against it: t.Helper() in test helpers, preallocated slice, NewRequestWithContext, and formatter-applied blank lines. Track the shared .github/alias.sh. Document the new dev prerequisites in the README. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Cp1SDaNNs1WahgRvapN1rH --- .github/alias.sh | 134 ++++++++++++++++++++++ .github/workflows/go-cross.yml | 7 +- .github/workflows/main.yml | 25 +++- .golangci.yml | 203 ++++++++++++++++++--------------- Makefile | 24 +++- README.md | 45 ++++++-- conditional_headers.go | 1 + conditional_headers_test.go | 8 +- go.mod | 2 +- handler_test.go | 9 +- integration_test.go | 16 ++- test_utils_test.go | 9 +- 12 files changed, 361 insertions(+), 122 deletions(-) create mode 100755 .github/alias.sh diff --git a/.github/alias.sh b/.github/alias.sh new file mode 100755 index 0000000..3fca974 --- /dev/null +++ b/.github/alias.sh @@ -0,0 +1,134 @@ +#!/bin/bash +set -euo pipefail + +# Check unnecessary aliased imports where no conflict exists +# Flags: 1) alias != basename when basename is not imported, 2) redundant alias == basename +# Exception: when the actual package name equals the current package name, alias is NOT flagged +# (since using the same name would conflict with the current package) + +# Pre-build a cache of import path -> actual package name for common stdlib packages +# This speeds up the check significantly + +find . -type f -name '*.go' \ + -not -name '*.qtpl.go' \ + -not -path '*/vendor/*' \ + -not -path '*/.claude/*' \ + -not -path '*/prototype/*' \ + | while read -r file; do + gawk ' + BEGIN { + inblock = 0 + line_count = 0 + current_package = "" + } + + # Store all lines of the file and detect current package + { + lines[line_count++] = $0 + # Detect package declaration + if (match($0, /^package\s+([a-zA-Z_][a-zA-Z0-9_]*)/, pkg)) { + current_package = pkg[1] + } + } + + # First pass: collect all imports with aliases and their paths + /^\s*import\s*\(/ { inblock = 1; next } + inblock && /^\s*\)/ { inblock = 0; next } + + inblock && match($0, /^\s*"([^"]+)"\s*$/, m) { + split(m[1], parts, "/") + base = parts[length(parts)] + used[base] = 1 + next + } + + inblock && match($0, /^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s+"([^"]+)"/, m) { + alias = m[1] + path = m[2] + imports[path] = alias + used[alias] = 1 + next + } + + match($0, /^\s*import\s+"([^"]+)"\s*$/, m) { + split(m[1], parts, "/") + base = parts[length(parts)] + used[base] = 1 + next + } + + match($0, /^\s*import\s+([a-zA-Z_][a-zA-Z0-9_]*)\s+"([^"]+)"\s*$/, m) { + alias = m[1] + path = m[2] + imports[path] = alias + used[alias] = 1 + next + } + + END { + inblock = 0 + for (i = 0; i < line_count; i++) { + line = lines[i] + + if (line ~ /^\s*import\s*\(/) { inblock = 1; continue } + if (inblock && line ~ /^\s*\)/) { inblock = 0; continue } + + if (inblock && match(line, /^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s+"([^"]+)"/, m)) { + alias = m[1] + path = m[2] + split(path, parts, "/") + base = parts[length(parts)] + + # Determine actual package name + pkg_name = base + # Only check external packages, not stdlib or local paths + if (path !~ /^\./ && index(path, ".") > 0) { + cmd = "go list -f \"{{.Name}}\" \"" path "\" 2>/dev/null" + if ((cmd | getline pname) > 0) { + pkg_name = pname + } + close(cmd) + } + + # Skip if alias is underscore, dot, equals base, or base is already used + # Also skip if the actual package name equals current package (alias is necessary to avoid conflict) + # Also skip if alias equals actual package name (necessary for versioned modules like /v2) + if (alias != "_" && alias != "." && alias != base && alias != pkg_name && !(base in used) && pkg_name != current_package && base != current_package) { + print FILENAME ":" i+1 ":" line + } + # Flag redundant alias (alias == base) - the alias provides no benefit + if (alias != "_" && alias != "." && alias == base) { + print FILENAME ":" i+1 ": redundant alias (remove \"" alias "\"): " line + } + continue + } + + if (!inblock && match(line, /^\s*import\s+([a-zA-Z_][a-zA-Z0-9_]*)\s+"([^"]+)"\s*$/, m)) { + alias = m[1] + path = m[2] + split(path, parts, "/") + base = parts[length(parts)] + + # Determine actual package name + pkg_name = base + if (path !~ /^\./ && index(path, ".") > 0) { + cmd = "go list -f \"{{.Name}}\" \"" path "\" 2>/dev/null" + if ((cmd | getline pname) > 0) { + pkg_name = pname + } + close(cmd) + } + + # Same check for single-line import (including alias != pkg_name for versioned modules) + if (alias != "_" && alias != "." && alias != base && alias != pkg_name && !(base in used) && pkg_name != current_package && base != current_package) { + print FILENAME ":" i+1 ":" line + } + # Flag redundant alias (alias == base) - the alias provides no benefit + if (alias != "_" && alias != "." && alias == base) { + print FILENAME ":" i+1 ": redundant alias (remove \"" alias "\"): " line + } + } + } + } + ' "$file" + done diff --git a/.github/workflows/go-cross.yml b/.github/workflows/go-cross.yml index 5282a3d..6d389e0 100644 --- a/.github/workflows/go-cross.yml +++ b/.github/workflows/go-cross.yml @@ -1,6 +1,9 @@ name: Go Matrix on: [push, pull_request] +permissions: + contents: read + jobs: cross: @@ -11,7 +14,7 @@ jobs: strategy: matrix: - go-version: [ 1.24, 1.25, 1.x ] + go-version: [ 1.26, 1.x ] os: [ubuntu-slim, macos-26, windows-latest] steps: @@ -23,7 +26,7 @@ jobs: # https://github.com/marketplace/actions/checkout - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Test run: make test diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cf53caf..c712f36 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,17 +6,21 @@ on: - master pull_request: +permissions: + contents: read + pull-requests: read + jobs: main: name: Main Process runs-on: ubuntu-slim env: - GO_VERSION: 1.24 - GOLANGCI_LINT_VERSION: v2.7.1 + GO_VERSION: "1.26" + GOLANGCI_LINT_VERSION: v2.12.2 YAEGI_VERSION: v0.16.1 CGO_ENABLED: 1 - + steps: # https://github.com/marketplace/actions/setup-go-environment @@ -27,7 +31,7 @@ jobs: # https://github.com/marketplace/actions/checkout - name: Check out code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -38,6 +42,13 @@ jobs: - name: Install Yaegi ${{ env.YAEGI_VERSION }} run: curl -sfL https://raw.githubusercontent.com/traefik/yaegi/master/install.sh | bash -s -- -b $(go env GOPATH)/bin ${YAEGI_VERSION} + - name: Install quality tooling + run: | + command -v bash >/dev/null || { echo "bash is required for the import-alias check"; exit 1; } + sudo apt-get update && sudo apt-get install -y gawk + go install golang.org/x/tools/cmd/goimports@v0.47.0 + go install mvdan.cc/gofumpt@v0.10.0 + - name: Check and get dependencies run: | go mod tidy @@ -50,8 +61,12 @@ jobs: - name: Lint and Tests run: make + # Fail if `make` (gofumpt/goimports/golangci-lint --fix) rewrote any committed source. + - name: Ensure quality made no changes + run: git diff --exit-code + - name: Install goveralls - run: go install github.com/mattn/goveralls@latest + run: go install github.com/mattn/goveralls@v0.0.12 - name: Send coverage env: COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.golangci.yml b/.golangci.yml index b35c749..f7cebe5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,121 +1,136 @@ version: "2" +run: + timeout: 5m + exclude-dirs: + - .claude + - v1 + +formatters: + enable: + - gofumpt + linters: enable: - asasalint - asciicheck - bidichk - - bodyclose + - bodyclose # HTTP body close + - canonicalheader + - containedctx - contextcheck - copyloopvar - - cyclop - decorder - - dupl - - errcheck + - dogsled + - dupword + - durationcheck + - embeddedstructfieldcheck + - errcheck # Unchecked errors + - errchkjson + - errname - errorlint - exhaustive - - exhaustruct - - gocognit - - gocyclo + - exptostd + - fatcontext + - forcetypeassert + - gocheckcompilerdirectives + - gocritic # Advanced checks - godot - - gosec - - govet + - godox + - goprintffuncname + - gosec # Security issues + - govet # Built-in vet + - grouper + - iface + - inamedparam - ineffassign - - misspell + - interfacebloat + - intrange + - iotamixing + - loggercheck + - makezero + - mirror + - misspell # Typos in comments/strings - musttag - - nestif + - nakedret - nilerr + - nilnesserr + - nilnil + - nlreturn - noctx - - prealloc - - revive + - nolintlint # Enforce nolint comments + - nonamedreturns + - perfsprint + - predeclared + - protogetter + - reassign + - recvcheck - rowserrcheck + - sloglint - sqlclosecheck - - staticcheck + - staticcheck # Go vet++ + - testableexamples + - thelper + - unconvert # Unnecessary conversions - unparam - unused - - varnamelen + - usestdlibvars + - usetesting - wastedassign - whitespace - - wrapcheck + - gochecksumtype + - goconst + - importas + - modernize + - nosprintfhostport + - prealloc + - tparallel + settings: - revive: - rules: - - name: exported - severity: warning - disabled: false - exclude: [ "" ] - arguments: - - disable-checks-on-functions - - disable-checks-on-methods - - disable-checks-on-types - - disable-checks-on-variables - - disable-checks-on-constants - - name: confusing-naming - severity: warning - disabled: false - - name: datarace - severity: warning - disabled: false - - name: deep-exit - severity: warning - disabled: false - - name: early-return - severity: warning - disabled: false - - name: empty-block - severity: warning - disabled: false - - name: empty-lines - severity: warning - disabled: false - - name: error-naming - severity: warning - disabled: false - - name: error-return - severity: warning - disabled: false - - name: get-return - severity: warning - disabled: false - - name: identical-branches - severity: warning - disabled: false - - name: import-shadowing - severity: warning - disabled: false - - name: optimize-operands-order - severity: warning - disabled: false - - name: redefines-builtin-id - severity: warning - disabled: false - - name: redundant-import-alias - severity: warning - disabled: false - - name: string-of-int - severity: warning - disabled: false - - name: superfluous-else - severity: warning - disabled: false - - name: unchecked-type-assertion - severity: warning - disabled: false - - name: unconditional-recursion - severity: warning - disabled: false - - name: unhandled-error - severity: warning - disabled: false - - name: unreachable-code - severity: warning - disabled: false - - name: use-any - severity: warning - disabled: false - - name: use-errors-new - severity: warning - disabled: false + goconst: + min-occurrences: 5 + ignore-tests: true + errchkjson: + # Only report json.Marshal calls that can genuinely fail (unsafe types). + # Safe types like plain structs and map[string]string are ignored. + check-error-free-encoding: false + gofumpt: + extra-rules: true + gocritic: + disabled-checks: + - ifElseChain + - elseif + - appendAssign + - assignOp + - unlambda + gosec: + excludes: + - G101 # Hardcoded credentials - false positive on config key mappings + - G104 + - G115 # Integer overflow - file descriptors are small positive integers + - G118 # Background context in goroutines - lifecycle contexts intentionally outlive requests + - G204 + - G301 + - G302 + - G304 + - G306 + - G702 # Command injection via taint - user-controlled $EDITOR resolved through exec.LookPath + - G703 # Path traversal via taint - paths are validated by ValidatePathWithRoots + - G704 # SSRF via taint - false positives on hardcoded API URLs and Unix sockets + - G706 # Log injection via taint - slog properly escapes structured values + +issues: + exclude-rules: + - path: _test\.go + linters: + - errcheck + - errchkjson + - gosec + - path: testutils/*.go + linters: + - errcheck + - errchkjson + - gosec output: path-mode: "abs" diff --git a/Makefile b/Makefile index 16d57a4..e244da5 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,26 @@ -.PHONY: lint test coverage coverage-html vendor clean +.PHONY: quality tools lint test coverage coverage-html yaegi_test vendor clean +.NOTPARALLEL: # formatters mutate the tree; never race them under `make -j` export GO111MODULE=on -default: lint test coverage +default: quality test coverage + +quality: + @if command -v goimports >/dev/null; then find . -name '*.go' -not -path './.claude/*' -not -path './vendor/*' -exec goimports -w {} +; else echo "goimports not found — run 'make tools' (skipping)"; fi + @if command -v gofumpt >/dev/null; then find . -name '*.go' -not -path './.claude/*' -not -path './vendor/*' -exec gofumpt -l -w {} +; else echo "gofumpt not found — run 'make tools' (skipping)"; fi + go vet ./... + @command -v gawk >/dev/null || { echo "gawk is required for the import-alias check (brew install gawk / apt-get install -y gawk)"; exit 1; } + @alias_out="$$(./.github/alias.sh)"; rc=$$?; \ + if [ $$rc -ne 0 ]; then echo "alias check failed to run (exit $$rc)"; exit 1; fi; \ + if [ -n "$$alias_out" ]; then echo "Unnecessary import alias detected:"; echo "$$alias_out"; exit 1; fi + @command -v golangci-lint >/dev/null || { echo "golangci-lint v2.12.2 required — see README Development Setup"; exit 1; } + golangci-lint run ./... --fix + +# Installs the go-installable tools only. golangci-lint (v2.12.2) + gawk are installed +# separately — see README Development Setup. +tools: + go install golang.org/x/tools/cmd/goimports@v0.47.0 + go install mvdan.cc/gofumpt@v0.10.0 lint: golangci-lint run @@ -14,7 +32,7 @@ coverage: go test -race -covermode atomic -coverprofile=covprofile ./... coverage-html: coverage - go tool cover -html=coverage.out -o coverage.html + go tool cover -html=covprofile -o coverage.html yaegi_test: yaegi test -v . diff --git a/README.md b/README.md index c8889e7..64e62fd 100644 --- a/README.md +++ b/README.md @@ -110,10 +110,17 @@ cd traefik-conditional-headers # Run tests go test -v ./... -# Run linting -golangci-lint run +# Run the full quality gate + tests + coverage (default target) +make + +# Non-mutating lint only +make lint ``` +> Requires `golangci-lint` v2.12.2 and `gawk`; run `make tools` to install `goimports`/`gofumpt`. +> See [Contributing → Development Setup](#development-setup-1) for details. `make`/`make quality` +> reformat files in place. + #### Local Development Use the source directory directly - no building required: @@ -530,9 +537,21 @@ We welcome contributions! Here's how to get started: ### Development Setup 1. **Prerequisites**: - - Go 1.24+ + - Go 1.26+ - Docker - Traefik v3.0+ + - `golangci-lint` **v2.12.2** and `gawk` (required by `make`): + ```bash + # golangci-lint v2.12.2 + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.12.2 + # gawk + brew install gawk # macOS + sudo apt-get install -y gawk # Debian/Ubuntu + ``` + - `goimports` + `gofumpt` (formatters; `make quality` soft-skips them if absent): + ```bash + make tools + ``` 2. **Clone and Build**: ```bash @@ -541,7 +560,7 @@ We welcome contributions! Here's how to get started: go mod tidy ``` -3. **Run Tests**: +3. **Run Tests & Quality**: ```bash # Run unit tests go test -v ./... @@ -552,11 +571,23 @@ We welcome contributions! Here's how to get started: # Run benchmarks go test -bench=. -benchmem ./... - # Check code quality - go vet ./... - go fmt ./... + # Full quality gate + tests + coverage (the default target) + make + + # Just the quality gate (format, vet, import-alias check, golangci-lint --fix) + make quality ``` + > **Note:** `make` / `make quality` **mutate** your working tree — they run `gofumpt -w`, + > `goimports -w`, and `golangci-lint run --fix`. For a non-mutating check use `make lint` + > (bare `golangci-lint run`). CI runs `make` and then `git diff --exit-code`, so any drift + > the auto-fixers would silently correct fails the build instead. + + > **Trip-wire:** `.github/alias.sh`'s `go list` shell-out is inert while this plugin is + > pure stdlib (zero external imports). If you ever add an external dependency, revisit that + > script's handling of import paths before merging — a crafted path could otherwise reach + > shell execution in CI. + 4. **Local Testing**: ```bash # Start test Traefik instance diff --git a/conditional_headers.go b/conditional_headers.go index bb37c7e..f47d1dc 100644 --- a/conditional_headers.go +++ b/conditional_headers.go @@ -144,6 +144,7 @@ func (c *conditionalHeaders) ServeHTTP(responseWriter http.ResponseWriter, reque if c.next != nil { c.next.ServeHTTP(responseWriter, request) } + return // Stop processing rules after first match } } diff --git a/conditional_headers_test.go b/conditional_headers_test.go index 4149d82..0ba1af6 100644 --- a/conditional_headers_test.go +++ b/conditional_headers_test.go @@ -163,20 +163,25 @@ func TestCreateConfig(t *testing.T) { // validateHandlerCreation validates the result of New() function calls. func validateHandlerCreation(t *testing.T, handler http.Handler, err error, expectError bool) { + t.Helper() + if expectError { if err == nil { t.Error("Expected error but got none") } + return } if err != nil { t.Errorf("Unexpected error: %v", err) + return } if handler == nil { t.Error("New() returned nil handler") + return } @@ -238,7 +243,6 @@ func TestConditionalHeadersFields(t *testing.T) { config := &Config{Rules: rules} next := mockNextHandler() handler, err := New(context.Background(), next, config, testPluginNameWithDash) - if err != nil { t.Fatalf("Failed to create handler: %v", err) } @@ -289,7 +293,7 @@ func BenchmarkMatchesHost(b *testing.B) { for _, tc := range testCases { b.Run(tc.name, func(b *testing.B) { - for i := 0; i < b.N; i++ { + for range b.N { matchesHost(tc.incomingHost, tc.ruleHost) } }) diff --git a/go.mod b/go.mod index df24f0c..14bbf0a 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/valksor/traefik-conditional-headers -go 1.24 +go 1.26 diff --git a/handler_test.go b/handler_test.go index 7170fef..9d3739c 100644 --- a/handler_test.go +++ b/handler_test.go @@ -9,6 +9,8 @@ import ( // verifyExpectedHeaders checks that the expected headers were set correctly. func verifyExpectedHeaders(t *testing.T, capturedHeaders map[string][]string, expectedHeaders map[string]string) { + t.Helper() + if len(capturedHeaders) == 0 { t.Error("Expected headers to be set, but none were captured") } @@ -17,6 +19,7 @@ func verifyExpectedHeaders(t *testing.T, capturedHeaders map[string][]string, ex values, exists := capturedHeaders[expectedKey] if !exists { t.Errorf("Expected header %q to be set", expectedKey) + continue } if len(values) != 1 || values[0] != expectedValue { @@ -27,6 +30,8 @@ func verifyExpectedHeaders(t *testing.T, capturedHeaders map[string][]string, ex // verifyNoUnexpectedHeaders checks that no unexpected headers were set. func verifyNoUnexpectedHeaders(t *testing.T, capturedHeaders map[string][]string) { + t.Helper() + for header := range capturedHeaders { // Skip standard headers that might be set by the test infrastructure if header != "User-Agent" && header != "Accept-Encoding" { @@ -37,6 +42,8 @@ func verifyNoUnexpectedHeaders(t *testing.T, capturedHeaders map[string][]string // executeTestRequest creates a handler and executes a test request. func executeTestRequest(t *testing.T, rules []Rule, requestHost string) (map[string][]string, *httptest.ResponseRecorder) { + t.Helper() + var capturedHeaders map[string][]string next := headerCaptureHandler(&capturedHeaders) @@ -317,7 +324,7 @@ func BenchmarkConditionalHeadersServeHTTP(b *testing.B) { responseRecorder := createTestResponse() b.ResetTimer() - for i := 0; i < b.N; i++ { + for range b.N { responseRecorder.Body.Reset() handler.ServeHTTP(responseRecorder, req) } diff --git a/integration_test.go b/integration_test.go index e2d727f..6485490 100644 --- a/integration_test.go +++ b/integration_test.go @@ -8,10 +8,13 @@ import ( // assertExpectedHeaders validates that expected headers are present in actual headers. func assertExpectedHeaders(t *testing.T, host string, expectedHeaders map[string]string, actualHeaders map[string][]string) { + t.Helper() + for expectedKey, expectedValue := range expectedHeaders { actualValue, exists := actualHeaders[expectedKey] if !exists { t.Errorf("Expected header %q to be set for host %q", expectedKey, host) + continue } @@ -25,6 +28,8 @@ func assertExpectedHeaders(t *testing.T, host string, expectedHeaders map[string // validateTestRequest executes a test request and validates expected headers. func validateTestRequest(t *testing.T, config *Config, testReq testRequest) { + t.Helper() + headers := makeTestRequest(t, config, testReq.host) assertExpectedHeaders(t, testReq.host, testReq.expectedHeaders, headers) } @@ -35,7 +40,10 @@ func runTestCase(t *testing.T, testCase struct { config *Config testRequests []testRequest description string -}) { +}, +) { + t.Helper() + t.Logf("Testing scenario: %s", testCase.description) for _, testReq := range testCase.testRequests { @@ -361,9 +369,9 @@ func TestIntegrationEdgeCases(t *testing.T) { // TestIntegrationPerformanceComplexConfig tests performance with complex configurations. func TestIntegrationPerformanceComplexConfig(t *testing.T) { // Create a complex configuration with many rules - var rules []Rule hostSuffixes := []string{".com", ".net", ".org", ".io", ".dev"} subdomains := []string{"api", "admin", "cdn", "static", "auth", "users", "blog", "shop"} + rules := make([]Rule, 0, len(subdomains)*len(hostSuffixes)+1) // Generate many rules for suffixIndex, suffix := range hostSuffixes { @@ -405,7 +413,7 @@ func TestIntegrationPerformanceComplexConfig(t *testing.T) { for _, host := range testHosts { t.Run("Performance test for "+host, func(t *testing.T) { // Run multiple iterations to check performance - for i := 0; i < 100; i++ { + for range 100 { start := testing.AllocsPerRun(1, func() { makeTestRequest(t, config, host) }) @@ -428,6 +436,8 @@ type testRequest struct { // makeTestRequest creates and executes a test request, returning the headers. func makeTestRequest(t *testing.T, config *Config, host string) map[string][]string { + t.Helper() + var capturedHeaders map[string][]string next := headerCaptureHandler(&capturedHeaders) diff --git a/test_utils_test.go b/test_utils_test.go index 6ba6509..3fa0606 100644 --- a/test_utils_test.go +++ b/test_utils_test.go @@ -1,14 +1,17 @@ package traefik_conditional_headers import ( + "context" + "maps" "net/http" "net/http/httptest" ) // createTestRequest creates an HTTP request for testing. func createTestRequest(host string) *http.Request { - req := httptest.NewRequest("GET", "https://"+host+testURLPath, nil) + req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "https://"+host+testURLPath, nil) req.Host = host + return req } @@ -32,9 +35,7 @@ func mockNextHandler() http.Handler { func headerCaptureHandler(capturedHeadersRef *map[string][]string) http.Handler { return http.HandlerFunc(func(responseWriter http.ResponseWriter, request *http.Request) { *capturedHeadersRef = make(map[string][]string) - for key, values := range request.Header { - (*capturedHeadersRef)[key] = values - } + maps.Copy((*capturedHeadersRef), request.Header) responseWriter.WriteHeader(http.StatusOK) _, err := responseWriter.Write([]byte(testResponseOK)) if err != nil {