diff --git a/.codecov.yml b/.codecov.yml index 5e3521ae..a6458e44 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -88,3 +88,4 @@ ignore: # Import/data directories - "import/**" - "data/**" + - ".cache/**" diff --git a/.dockerignore b/.dockerignore index 48f5be27..8de6d2f0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -197,3 +197,4 @@ VERSION.md eslint.config.js go.work go.work.sum +.cache diff --git a/.github/agents/QA_Security.agent.md b/.github/agents/QA_Security.agent.md index 95bc7998..503f762c 100644 --- a/.github/agents/QA_Security.agent.md +++ b/.github/agents/QA_Security.agent.md @@ -27,6 +27,7 @@ Your job is to act as an ADVERSARY. The Developer says "it works"; your job is t - **Path Verification**: Run `list_dir internal/api` to verify where tests should go. - **Creation**: Write a new test file (e.g., `internal/api/tests/audit_test.go`) to test the *flow*. - **Run**: Execute `go test ./internal/api/tests/...` (or specific path). Run local CodeQL and Trivy scans (they are built as VS Code Tasks so they just need to be triggered to run), pre-commit all files, and triage any findings. + - Always run run GolangCI-Lint in docker to ensure consistent linting. - **Cleanup**: If the test was temporary, delete it. If it's valuable, keep it. diff --git a/.github/workflows/codecov-upload.yml b/.github/workflows/codecov-upload.yml index 57bf7b09..d16b2192 100644 --- a/.github/workflows/codecov-upload.yml +++ b/.github/workflows/codecov-upload.yml @@ -26,19 +26,19 @@ jobs: go-version: '1.25.5' cache-dependency-path: backend/go.sum - - name: Run Go tests - working-directory: backend + - name: Run Go tests with coverage + working-directory: ${{ github.workspace }} env: CGO_ENABLED: 1 run: | - go test -race -v -coverprofile=coverage.out ./... 2>&1 | tee test-output.txt + bash scripts/go-test-coverage.sh 2>&1 | tee backend/test-output.txt exit ${PIPESTATUS[0]} - name: Upload backend coverage to Codecov uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5 with: token: ${{ secrets.CODECOV_TOKEN }} - files: ./backend/coverage.out + files: ./backend/coverage.txt flags: backend fail_ci_if_error: true diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index c142ab8d..f1270680 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -22,16 +22,16 @@ jobs: run: | if [ -n "${{ secrets.CHARON_TOKEN }}" ]; then echo "Using CHARON_TOKEN" >&2 - echo "RENOVATE_TOKEN=${{ secrets.CHARON_TOKEN }}" >> $GITHUB_ENV + echo "GITHUB_TOKEN=${{ secrets.CHARON_TOKEN }}" >> $GITHUB_ENV else echo "Using CPMP_TOKEN fallback" >&2 - echo "RENOVATE_TOKEN=${{ secrets.CPMP_TOKEN }}" >> $GITHUB_ENV + echo "GITHUB_TOKEN=${{ secrets.CPMP_TOKEN }}" >> $GITHUB_ENV fi - name: Run Renovate uses: renovatebot/github-action@5712c6a41dea6cdf32c72d92a763bd417e6606aa # v44.0.5 with: configurationFile: .github/renovate.json - token: ${{ env.RENOVATE_TOKEN }} + token: ${{ env.GITHUB_TOKEN }} env: LOG_LEVEL: info diff --git a/.gitignore b/.gitignore index 63785fb8..c69b768d 100644 --- a/.gitignore +++ b/.gitignore @@ -169,3 +169,4 @@ backend/internal/api/handlers/import_handler.go.bak import/ test-results/charon.hatfieldhosted.com.har test-results/local.har +.cache diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 81acf9b7..07e205c3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,13 +1,4 @@ repos: - - repo: local - hooks: - - id: python-compile - name: python compile check - entry: tools/python_compile_check.sh - language: script - files: ".*\\.py$" - pass_filenames: false - always_run: true - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 hooks: diff --git a/.vscode/settings.json b/.vscode/settings.json index 652fc6a4..22194c2e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,34 +1,27 @@ { - "python-envs.pythonProjects": [ - { - "path": "", - "envManager": "ms-python.python:venv", - "packageManager": "ms-python.python:pip" - } - ] - , "gopls": { - "env": { - "GOWORK": "off", - "GOFLAGS": "-mod=mod", - "GOTOOLCHAIN": "auto" + "staticcheck": true, + "analyses": { + "unusedparams": true, + "nilness": true }, - "directoryFilters": [ - "-**/pkg/mod/**", - "-**/go/pkg/mod/**", - "-**/root/go/pkg/mod/**" - ] + "completeUnimported": true, + "matcher": "Fuzzy", + "verboseOutput": true }, - "go.buildFlags": ["-tags=ignore", "-mod=mod"], + "go.useLanguageServer": true, "go.toolsEnvVars": { - "GOWORK": "off", - "GOFLAGS": "-mod=mod", - "GOTOOLCHAIN": "auto" + "GOMODCACHE": "${workspaceFolder}/.cache/go/pkg/mod" }, + "go.buildOnSave": "workspace", + "go.lintOnSave": "package", + "go.formatTool": "gofmt", "files.watcherExclude": { "**/pkg/mod/**": true, "**/go/pkg/mod/**": true, - "**/root/go/pkg/mod/**": true + "**/root/go/pkg/mod/**": true, + "**/backend/data/**": true, + "**/frontend/dist/**": true }, "search.exclude": { "**/pkg/mod/**": true, diff --git a/.vscode/tasks.json b/.vscode/tasks.json index e4001a6b..27aa77c7 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -17,6 +17,37 @@ "group": "test", "problemMatcher": [] }, + { + "label": "Go: Build Backend", + "type": "shell", + "command": "bash", + "args": ["-lc", "cd backend && go build ./..."], + "group": { "kind": "build", "isDefault": true }, + "presentation": { "reveal": "always", "panel": "shared" }, + "problemMatcher": ["$go"] + }, + { + "label": "Go: Test Backend", + "type": "shell", + "command": "bash", + "args": ["-lc", "cd backend && go test ./... -v"], + "group": "test", + "presentation": { "reveal": "always", "panel": "shared" } + }, + { + "label": "Go: Mod Tidy (Backend)", + "type": "shell", + "command": "bash", + "args": ["-lc", "cd backend && go mod tidy"], + "presentation": { "reveal": "silent", "panel": "shared" } + }, + { + "label": "Gather gopls logs", + "type": "shell", + "command": "bash", + "args": ["-lc", "./scripts/gopls_collect.sh"], + "presentation": { "reveal": "always", "panel": "new" } + }, { "label": "Git Remove Cached", "type": "shell", diff --git a/Makefile b/Makefile index d260af2d..7db14981 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help install test build run clean docker-build docker-run release +.PHONY: help install test build run clean docker-build docker-run release go-check gopls-logs # Default target help: @@ -16,6 +16,8 @@ help: @echo " docker-dev - Run Docker in development mode" @echo " release - Create a new semantic version release (interactive)" @echo " dev - Run both backend and frontend in dev mode (requires tmux)" + @echo " go-check - Verify backend build readiness (runs scripts/check_go_build.sh)" + @echo " gopls-logs - Collect gopls diagnostics (runs scripts/gopls_collect.sh)" @echo "" @echo "Security targets:" @echo " security-scan - Quick security scan (govulncheck on Go deps)" @@ -122,6 +124,12 @@ dev: release: @./scripts/release.sh +go-check: + ./scripts/check_go_build.sh + +gopls-logs: + ./scripts/gopls_collect.sh + # Security scanning targets security-scan: @echo "Running security scan (govulncheck)..." diff --git a/backend/.golangci.yml b/backend/.golangci.yml index 27de4874..9f46e9d2 100644 --- a/backend/.golangci.yml +++ b/backend/.golangci.yml @@ -20,6 +20,9 @@ linters: enabled-tags: - diagnostic - performance + - style + - opinionated + - experimental disabled-checks: - whyNoLint - wrapperFunc diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go index 64df9f1a..c12c02d8 100644 --- a/backend/cmd/api/main.go +++ b/backend/cmd/api/main.go @@ -23,10 +23,10 @@ import ( func main() { // Setup logging with rotation logDir := "/app/data/logs" - if err := os.MkdirAll(logDir, 0755); err != nil { + if err := os.MkdirAll(logDir, 0o755); err != nil { // Fallback to local directory if /app/data fails (e.g. local dev) logDir = "data/logs" - _ = os.MkdirAll(logDir, 0755) + _ = os.MkdirAll(logDir, 0o755) } logFile := filepath.Join(logDir, "charon.log") diff --git a/backend/go.mod b/backend/go.mod index cd482b42..ce130056 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -45,7 +45,6 @@ require ( github.com/go-playground/validator/v10 v10.28.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.18.0 // indirect - github.com/hashicorp/go-version v1.8.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -70,8 +69,8 @@ require ( github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect - github.com/quic-go/qpack v0.5.1 // indirect - github.com/quic-go/quic-go v0.55.0 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.57.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect @@ -82,13 +81,10 @@ require ( go.opentelemetry.io/otel/trace v1.38.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/arch v0.22.0 // indirect - golang.org/x/mod v0.29.0 // indirect golang.org/x/net v0.47.0 // indirect - golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.38.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.2 // indirect diff --git a/backend/go.sum b/backend/go.sum index 17e69a8f..b08e2ce1 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,11 +1,7 @@ -cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= @@ -26,10 +22,8 @@ github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151X github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g= github.com/containrrr/shoutrrr v0.8.0 h1:mfG2ATzIS7NR2Ec6XL+xyoHzN97H8WPjir8aYzJUSec= github.com/containrrr/shoutrrr v0.8.0/go.mod h1:ioyQAyu1LJY6sILuNyKaQaw+9Ttik5QePU8atnAdO2o= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 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= @@ -45,8 +39,6 @@ github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI= @@ -74,7 +66,6 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= @@ -88,20 +79,14 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= -github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= -github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= @@ -114,7 +99,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -122,7 +106,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= @@ -140,7 +123,6 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= @@ -163,28 +145,19 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= -github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= -github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10= +github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -192,13 +165,10 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= -github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= @@ -229,27 +199,19 @@ golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= @@ -261,7 +223,6 @@ google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -273,4 +234,3 @@ gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/backend/internal/api/handlers/access_list_handler_coverage_test.go b/backend/internal/api/handlers/access_list_handler_coverage_test.go index c234b7b1..ad50fd9f 100644 --- a/backend/internal/api/handlers/access_list_handler_coverage_test.go +++ b/backend/internal/api/handlers/access_list_handler_coverage_test.go @@ -16,7 +16,7 @@ import ( func TestAccessListHandler_Get_InvalidID(t *testing.T) { router, _ := setupAccessListTestRouter(t) - req := httptest.NewRequest(http.MethodGet, "/access-lists/invalid", nil) + req := httptest.NewRequest(http.MethodGet, "/access-lists/invalid", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) @@ -53,7 +53,7 @@ func TestAccessListHandler_Update_InvalidJSON(t *testing.T) { func TestAccessListHandler_Delete_InvalidID(t *testing.T) { router, _ := setupAccessListTestRouter(t) - req := httptest.NewRequest(http.MethodDelete, "/access-lists/invalid", nil) + req := httptest.NewRequest(http.MethodDelete, "/access-lists/invalid", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) @@ -98,7 +98,7 @@ func TestAccessListHandler_List_DBError(t *testing.T) { handler := NewAccessListHandler(db) router.GET("/access-lists", handler.List) - req := httptest.NewRequest(http.MethodGet, "/access-lists", nil) + req := httptest.NewRequest(http.MethodGet, "/access-lists", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) @@ -115,7 +115,7 @@ func TestAccessListHandler_Get_DBError(t *testing.T) { handler := NewAccessListHandler(db) router.GET("/access-lists/:id", handler.Get) - req := httptest.NewRequest(http.MethodGet, "/access-lists/1", nil) + req := httptest.NewRequest(http.MethodGet, "/access-lists/1", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) @@ -138,7 +138,7 @@ func TestAccessListHandler_Delete_InternalError(t *testing.T) { acl := models.AccessList{UUID: "test-uuid", Name: "Test", Type: "whitelist"} db.Create(&acl) - req := httptest.NewRequest(http.MethodDelete, "/access-lists/1", nil) + req := httptest.NewRequest(http.MethodDelete, "/access-lists/1", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) diff --git a/backend/internal/api/handlers/access_list_handler_test.go b/backend/internal/api/handlers/access_list_handler_test.go index ad795183..51a84ea1 100644 --- a/backend/internal/api/handlers/access_list_handler_test.go +++ b/backend/internal/api/handlers/access_list_handler_test.go @@ -129,7 +129,7 @@ func TestAccessListHandler_List(t *testing.T) { db.Create(&acls[i]) } - req := httptest.NewRequest(http.MethodGet, "/access-lists", nil) + req := httptest.NewRequest(http.MethodGet, "/access-lists", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) @@ -173,7 +173,7 @@ func TestAccessListHandler_Get(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, "/access-lists/"+tt.id, nil) + req := httptest.NewRequest(http.MethodGet, "/access-lists/"+tt.id, http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) @@ -313,7 +313,7 @@ func TestAccessListHandler_Delete(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - req := httptest.NewRequest(http.MethodDelete, "/access-lists/"+tt.id, nil) + req := httptest.NewRequest(http.MethodDelete, "/access-lists/"+tt.id, http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) @@ -393,7 +393,7 @@ func TestAccessListHandler_TestIP(t *testing.T) { func TestAccessListHandler_GetTemplates(t *testing.T) { router, _ := setupAccessListTestRouter(t) - req := httptest.NewRequest(http.MethodGet, "/access-lists/templates", nil) + req := httptest.NewRequest(http.MethodGet, "/access-lists/templates", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) diff --git a/backend/internal/api/handlers/additional_coverage_test.go b/backend/internal/api/handlers/additional_coverage_test.go index 660b59c8..15aa1a5b 100644 --- a/backend/internal/api/handlers/additional_coverage_test.go +++ b/backend/internal/api/handlers/additional_coverage_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "mime/multipart" + "net/http" "net/http/httptest" "os" "path/filepath" @@ -143,7 +144,7 @@ func TestSecurityHandler_GetConfig_InternalError(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest("GET", "/security/config", nil) + c.Request = httptest.NewRequest("GET", "/security/config", http.NoBody) h.GetConfig(c) @@ -186,7 +187,7 @@ func TestSecurityHandler_GenerateBreakGlass_Error(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest("POST", "/security/breakglass", nil) + c.Request = httptest.NewRequest("POST", "/security/breakglass", http.NoBody) h.GenerateBreakGlass(c) @@ -205,7 +206,7 @@ func TestSecurityHandler_ListDecisions_Error(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest("GET", "/security/decisions", nil) + c.Request = httptest.NewRequest("GET", "/security/decisions", http.NoBody) h.ListDecisions(c) @@ -224,7 +225,7 @@ func TestSecurityHandler_ListRuleSets_Error(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest("GET", "/security/rulesets", nil) + c.Request = httptest.NewRequest("GET", "/security/rulesets", http.NoBody) h.ListRuleSets(c) @@ -445,19 +446,19 @@ func TestImportHandler_UploadMulti_PathTraversal(t *testing.T) { // Logs Handler Download error coverage -func setupLogsDownloadTest(t *testing.T) (*LogsHandler, string) { +func setupLogsDownloadTest(t *testing.T) (h *LogsHandler, logsDir string) { t.Helper() tmpDir := t.TempDir() dataDir := filepath.Join(tmpDir, "data") os.MkdirAll(dataDir, 0o755) - logsDir := filepath.Join(dataDir, "logs") + logsDir = filepath.Join(dataDir, "logs") os.MkdirAll(logsDir, 0o755) dbPath := filepath.Join(dataDir, "charon.db") cfg := &config.Config{DatabasePath: dbPath} svc := services.NewLogService(cfg) - h := NewLogsHandler(svc) + h = NewLogsHandler(svc) return h, logsDir } @@ -469,7 +470,7 @@ func TestLogsHandler_Download_PathTraversal(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "filename", Value: "../../../etc/passwd"}} - c.Request = httptest.NewRequest("GET", "/logs/../../../etc/passwd/download", nil) + c.Request = httptest.NewRequest("GET", "/logs/../../../etc/passwd/download", http.NoBody) h.Download(c) @@ -484,7 +485,7 @@ func TestLogsHandler_Download_NotFound(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "filename", Value: "nonexistent.log"}} - c.Request = httptest.NewRequest("GET", "/logs/nonexistent.log/download", nil) + c.Request = httptest.NewRequest("GET", "/logs/nonexistent.log/download", http.NoBody) h.Download(c) @@ -502,7 +503,7 @@ func TestLogsHandler_Download_Success(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "filename", Value: "test.log"}} - c.Request = httptest.NewRequest("GET", "/logs/test.log/download", nil) + c.Request = httptest.NewRequest("GET", "/logs/test.log/download", http.NoBody) h.Download(c) @@ -574,7 +575,7 @@ func TestBackupHandler_List_ServiceError(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest("GET", "/backups", nil) + c.Request = httptest.NewRequest("GET", "/backups", http.NoBody) h.List(c) @@ -602,7 +603,7 @@ func TestBackupHandler_Delete_PathTraversal(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "filename", Value: "../../../etc/passwd"}} - c.Request = httptest.NewRequest("DELETE", "/backups/../../../etc/passwd", nil) + c.Request = httptest.NewRequest("DELETE", "/backups/../../../etc/passwd", http.NoBody) h.Delete(c) @@ -641,7 +642,7 @@ func TestBackupHandler_Delete_InternalError2(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "filename", Value: "test.zip"}} - c.Request = httptest.NewRequest("DELETE", "/backups/test.zip", nil) + c.Request = httptest.NewRequest("DELETE", "/backups/test.zip", http.NoBody) h.Delete(c) @@ -722,7 +723,7 @@ func TestHealthHandler_Basic(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest("GET", "/health", nil) + c.Request = httptest.NewRequest("GET", "/health", http.NoBody) HealthHandler(c) @@ -753,7 +754,7 @@ func TestBackupHandler_Create_Error(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest("POST", "/backups", nil) + c.Request = httptest.NewRequest("POST", "/backups", http.NoBody) h.Create(c) @@ -782,7 +783,7 @@ func TestSettingsHandler_GetSettings_Error(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest("GET", "/settings", nil) + c.Request = httptest.NewRequest("GET", "/settings", http.NoBody) h.GetSettings(c) diff --git a/backend/internal/api/handlers/auth_handler_test.go b/backend/internal/api/handlers/auth_handler_test.go index 878821ba..77340c13 100644 --- a/backend/internal/api/handlers/auth_handler_test.go +++ b/backend/internal/api/handlers/auth_handler_test.go @@ -137,7 +137,7 @@ func TestAuthHandler_Logout(t *testing.T) { r := gin.New() r.POST("/logout", handler.Logout) - req := httptest.NewRequest("POST", "/logout", nil) + req := httptest.NewRequest("POST", "/logout", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -171,7 +171,7 @@ func TestAuthHandler_Me(t *testing.T) { }) r.GET("/me", handler.Me) - req := httptest.NewRequest("GET", "/me", nil) + req := httptest.NewRequest("GET", "/me", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -194,7 +194,7 @@ func TestAuthHandler_Me_NotFound(t *testing.T) { }) r.GET("/me", handler.Me) - req := httptest.NewRequest("GET", "/me", nil) + req := httptest.NewRequest("GET", "/me", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -319,7 +319,7 @@ func TestAuthHandler_Verify_NoCookie(t *testing.T) { r := gin.New() r.GET("/verify", handler.Verify) - req := httptest.NewRequest("GET", "/verify", nil) + req := httptest.NewRequest("GET", "/verify", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -333,7 +333,7 @@ func TestAuthHandler_Verify_InvalidToken(t *testing.T) { r := gin.New() r.GET("/verify", handler.Verify) - req := httptest.NewRequest("GET", "/verify", nil) + req := httptest.NewRequest("GET", "/verify", http.NoBody) req.AddCookie(&http.Cookie{Name: "auth_token", Value: "invalid-token"}) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -362,7 +362,7 @@ func TestAuthHandler_Verify_ValidToken(t *testing.T) { r := gin.New() r.GET("/verify", handler.Verify) - req := httptest.NewRequest("GET", "/verify", nil) + req := httptest.NewRequest("GET", "/verify", http.NoBody) req.AddCookie(&http.Cookie{Name: "auth_token", Value: token}) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -391,7 +391,7 @@ func TestAuthHandler_Verify_BearerToken(t *testing.T) { r := gin.New() r.GET("/verify", handler.Verify) - req := httptest.NewRequest("GET", "/verify", nil) + req := httptest.NewRequest("GET", "/verify", http.NoBody) req.Header.Set("Authorization", "Bearer "+token) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -420,7 +420,7 @@ func TestAuthHandler_Verify_DisabledUser(t *testing.T) { r := gin.New() r.GET("/verify", handler.Verify) - req := httptest.NewRequest("GET", "/verify", nil) + req := httptest.NewRequest("GET", "/verify", http.NoBody) req.AddCookie(&http.Cookie{Name: "auth_token", Value: token}) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -459,7 +459,7 @@ func TestAuthHandler_Verify_ForwardAuthDenied(t *testing.T) { r := gin.New() r.GET("/verify", handler.Verify) - req := httptest.NewRequest("GET", "/verify", nil) + req := httptest.NewRequest("GET", "/verify", http.NoBody) req.AddCookie(&http.Cookie{Name: "auth_token", Value: token}) req.Header.Set("X-Forwarded-Host", "app.example.com") w := httptest.NewRecorder() @@ -474,7 +474,7 @@ func TestAuthHandler_VerifyStatus_NotAuthenticated(t *testing.T) { r := gin.New() r.GET("/status", handler.VerifyStatus) - req := httptest.NewRequest("GET", "/status", nil) + req := httptest.NewRequest("GET", "/status", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -490,7 +490,7 @@ func TestAuthHandler_VerifyStatus_InvalidToken(t *testing.T) { r := gin.New() r.GET("/status", handler.VerifyStatus) - req := httptest.NewRequest("GET", "/status", nil) + req := httptest.NewRequest("GET", "/status", http.NoBody) req.AddCookie(&http.Cookie{Name: "auth_token", Value: "invalid"}) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -520,7 +520,7 @@ func TestAuthHandler_VerifyStatus_Authenticated(t *testing.T) { r := gin.New() r.GET("/status", handler.VerifyStatus) - req := httptest.NewRequest("GET", "/status", nil) + req := httptest.NewRequest("GET", "/status", http.NoBody) req.AddCookie(&http.Cookie{Name: "auth_token", Value: token}) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -553,7 +553,7 @@ func TestAuthHandler_VerifyStatus_DisabledUser(t *testing.T) { r := gin.New() r.GET("/status", handler.VerifyStatus) - req := httptest.NewRequest("GET", "/status", nil) + req := httptest.NewRequest("GET", "/status", http.NoBody) req.AddCookie(&http.Cookie{Name: "auth_token", Value: token}) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -570,7 +570,7 @@ func TestAuthHandler_GetAccessibleHosts_Unauthorized(t *testing.T) { r := gin.New() r.GET("/hosts", handler.GetAccessibleHosts) - req := httptest.NewRequest("GET", "/hosts", nil) + req := httptest.NewRequest("GET", "/hosts", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -604,7 +604,7 @@ func TestAuthHandler_GetAccessibleHosts_AllowAll(t *testing.T) { }) r.GET("/hosts", handler.GetAccessibleHosts) - req := httptest.NewRequest("GET", "/hosts", nil) + req := httptest.NewRequest("GET", "/hosts", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -640,7 +640,7 @@ func TestAuthHandler_GetAccessibleHosts_DenyAll(t *testing.T) { }) r.GET("/hosts", handler.GetAccessibleHosts) - req := httptest.NewRequest("GET", "/hosts", nil) + req := httptest.NewRequest("GET", "/hosts", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -679,7 +679,7 @@ func TestAuthHandler_GetAccessibleHosts_PermittedHosts(t *testing.T) { }) r.GET("/hosts", handler.GetAccessibleHosts) - req := httptest.NewRequest("GET", "/hosts", nil) + req := httptest.NewRequest("GET", "/hosts", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -701,7 +701,7 @@ func TestAuthHandler_GetAccessibleHosts_UserNotFound(t *testing.T) { }) r.GET("/hosts", handler.GetAccessibleHosts) - req := httptest.NewRequest("GET", "/hosts", nil) + req := httptest.NewRequest("GET", "/hosts", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -714,7 +714,7 @@ func TestAuthHandler_CheckHostAccess_Unauthorized(t *testing.T) { r := gin.New() r.GET("/hosts/:hostId/access", handler.CheckHostAccess) - req := httptest.NewRequest("GET", "/hosts/1/access", nil) + req := httptest.NewRequest("GET", "/hosts/1/access", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -735,7 +735,7 @@ func TestAuthHandler_CheckHostAccess_InvalidHostID(t *testing.T) { }) r.GET("/hosts/:hostId/access", handler.CheckHostAccess) - req := httptest.NewRequest("GET", "/hosts/invalid/access", nil) + req := httptest.NewRequest("GET", "/hosts/invalid/access", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -764,7 +764,7 @@ func TestAuthHandler_CheckHostAccess_Allowed(t *testing.T) { }) r.GET("/hosts/:hostId/access", handler.CheckHostAccess) - req := httptest.NewRequest("GET", "/hosts/1/access", nil) + req := httptest.NewRequest("GET", "/hosts/1/access", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -796,7 +796,7 @@ func TestAuthHandler_CheckHostAccess_Denied(t *testing.T) { }) r.GET("/hosts/:hostId/access", handler.CheckHostAccess) - req := httptest.NewRequest("GET", "/hosts/1/access", nil) + req := httptest.NewRequest("GET", "/hosts/1/access", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) diff --git a/backend/internal/api/handlers/backup_handler_sanitize_test.go b/backend/internal/api/handlers/backup_handler_sanitize_test.go index 0e772525..ecfb1fec 100644 --- a/backend/internal/api/handlers/backup_handler_sanitize_test.go +++ b/backend/internal/api/handlers/backup_handler_sanitize_test.go @@ -39,7 +39,7 @@ func TestBackupHandlerSanitizesFilename(t *testing.T) { // Create a malicious filename with newline and path components malicious := "../evil\nname" - c.Request = httptest.NewRequest(http.MethodGet, "/backups/"+strings.ReplaceAll(malicious, "\n", "%0A")+"/restore", nil) + c.Request = httptest.NewRequest(http.MethodGet, "/backups/"+strings.ReplaceAll(malicious, "\n", "%0A")+"/restore", http.NoBody) // Call handler directly with the test context h.Restore(c) diff --git a/backend/internal/api/handlers/backup_handler_test.go b/backend/internal/api/handlers/backup_handler_test.go index a13ba871..5daa4f37 100644 --- a/backend/internal/api/handlers/backup_handler_test.go +++ b/backend/internal/api/handlers/backup_handler_test.go @@ -31,12 +31,12 @@ func setupBackupTest(t *testing.T) (*gin.Engine, *services.BackupService, string // So if DatabasePath is /tmp/data/charon.db, DataDir is /tmp/data, BackupDir is /tmp/data/backups. dataDir := filepath.Join(tmpDir, "data") - err = os.MkdirAll(dataDir, 0755) + err = os.MkdirAll(dataDir, 0o755) require.NoError(t, err) dbPath := filepath.Join(dataDir, "charon.db") // Create a dummy DB file to back up - err = os.WriteFile(dbPath, []byte("dummy db content"), 0644) + err = os.WriteFile(dbPath, []byte("dummy db content"), 0o644) require.NoError(t, err) cfg := &config.Config{ @@ -72,7 +72,7 @@ func TestBackupLifecycle(t *testing.T) { defer os.RemoveAll(tmpDir) // 1. List backups (should be empty) - req := httptest.NewRequest(http.MethodGet, "/api/v1/backups", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/backups", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) @@ -80,7 +80,7 @@ func TestBackupLifecycle(t *testing.T) { // ... // 2. Create backup - req = httptest.NewRequest(http.MethodPost, "/api/v1/backups", nil) + req = httptest.NewRequest(http.MethodPost, "/api/v1/backups", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusCreated, resp.Code) @@ -92,20 +92,20 @@ func TestBackupLifecycle(t *testing.T) { require.NotEmpty(t, filename) // 3. List backups (should have 1) - req = httptest.NewRequest(http.MethodGet, "/api/v1/backups", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/backups", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) // Verify list contains filename // 4. Restore backup - req = httptest.NewRequest(http.MethodPost, "/api/v1/backups/"+filename+"/restore", nil) + req = httptest.NewRequest(http.MethodPost, "/api/v1/backups/"+filename+"/restore", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) // 5. Download backup - req = httptest.NewRequest(http.MethodGet, "/api/v1/backups/"+filename+"/download", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/backups/"+filename+"/download", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) @@ -113,13 +113,13 @@ func TestBackupLifecycle(t *testing.T) { // require.Equal(t, "application/zip", resp.Header().Get("Content-Type")) // 6. Delete backup - req = httptest.NewRequest(http.MethodDelete, "/api/v1/backups/"+filename, nil) + req = httptest.NewRequest(http.MethodDelete, "/api/v1/backups/"+filename, http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) // 7. List backups (should be empty again) - req = httptest.NewRequest(http.MethodGet, "/api/v1/backups", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/backups", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) @@ -128,19 +128,19 @@ func TestBackupLifecycle(t *testing.T) { require.Empty(t, list) // 8. Delete non-existent backup - req = httptest.NewRequest(http.MethodDelete, "/api/v1/backups/missing.zip", nil) + req = httptest.NewRequest(http.MethodDelete, "/api/v1/backups/missing.zip", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusNotFound, resp.Code) // 9. Restore non-existent backup - req = httptest.NewRequest(http.MethodPost, "/api/v1/backups/missing.zip/restore", nil) + req = httptest.NewRequest(http.MethodPost, "/api/v1/backups/missing.zip/restore", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusNotFound, resp.Code) // 10. Download non-existent backup - req = httptest.NewRequest(http.MethodGet, "/api/v1/backups/missing.zip/download", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/backups/missing.zip/download", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusNotFound, resp.Code) @@ -154,7 +154,7 @@ func TestBackupHandler_Errors(t *testing.T) { // Note: Service now handles missing dir gracefully by returning empty list os.RemoveAll(svc.BackupDir) - req := httptest.NewRequest(http.MethodGet, "/api/v1/backups", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/backups", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) @@ -163,7 +163,7 @@ func TestBackupHandler_Errors(t *testing.T) { require.Empty(t, list) // 4. Delete Error (Not Found) - req = httptest.NewRequest(http.MethodDelete, "/api/v1/backups/missing.zip", nil) + req = httptest.NewRequest(http.MethodDelete, "/api/v1/backups/missing.zip", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusNotFound, resp.Code) @@ -174,13 +174,13 @@ func TestBackupHandler_List_Success(t *testing.T) { defer os.RemoveAll(tmpDir) // Create a backup first - req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", nil) + req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusCreated, resp.Code) // Now list should return it - req = httptest.NewRequest(http.MethodGet, "/api/v1/backups", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/backups", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) @@ -196,7 +196,7 @@ func TestBackupHandler_Create_Success(t *testing.T) { router, _, tmpDir := setupBackupTest(t) defer os.RemoveAll(tmpDir) - req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", nil) + req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusCreated, resp.Code) @@ -212,7 +212,7 @@ func TestBackupHandler_Download_Success(t *testing.T) { defer os.RemoveAll(tmpDir) // Create backup - req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", nil) + req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusCreated, resp.Code) @@ -222,7 +222,7 @@ func TestBackupHandler_Download_Success(t *testing.T) { filename := result["filename"] // Download it - req = httptest.NewRequest(http.MethodGet, "/api/v1/backups/"+filename+"/download", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/backups/"+filename+"/download", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) @@ -234,19 +234,19 @@ func TestBackupHandler_PathTraversal(t *testing.T) { defer os.RemoveAll(tmpDir) // Try path traversal in Delete - req := httptest.NewRequest(http.MethodDelete, "/api/v1/backups/../../../etc/passwd", nil) + req := httptest.NewRequest(http.MethodDelete, "/api/v1/backups/../../../etc/passwd", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusNotFound, resp.Code) // Try path traversal in Download - req = httptest.NewRequest(http.MethodGet, "/api/v1/backups/../../../etc/passwd/download", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/backups/../../../etc/passwd/download", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Contains(t, []int{http.StatusBadRequest, http.StatusNotFound}, resp.Code) // Try path traversal in Restore - req = httptest.NewRequest(http.MethodPost, "/api/v1/backups/../../../etc/passwd/restore", nil) + req = httptest.NewRequest(http.MethodPost, "/api/v1/backups/../../../etc/passwd/restore", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusNotFound, resp.Code) @@ -257,7 +257,7 @@ func TestBackupHandler_Download_InvalidPath(t *testing.T) { defer os.RemoveAll(tmpDir) // Request with path traversal attempt - req := httptest.NewRequest(http.MethodGet, "/api/v1/backups/../invalid/download", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/backups/../invalid/download", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) // Should be BadRequest due to path validation failure @@ -269,10 +269,10 @@ func TestBackupHandler_Create_ServiceError(t *testing.T) { defer os.RemoveAll(tmpDir) // Remove write permissions on backup dir to force create error - os.Chmod(svc.BackupDir, 0444) - defer os.Chmod(svc.BackupDir, 0755) + os.Chmod(svc.BackupDir, 0o444) + defer os.Chmod(svc.BackupDir, 0o755) - req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", nil) + req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) // Should fail with 500 due to permission error @@ -284,7 +284,7 @@ func TestBackupHandler_Delete_InternalError(t *testing.T) { defer os.RemoveAll(tmpDir) // Create a backup first - req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", nil) + req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusCreated, resp.Code) @@ -294,10 +294,10 @@ func TestBackupHandler_Delete_InternalError(t *testing.T) { filename := result["filename"] // Make backup dir read-only to cause delete error (not NotExist) - os.Chmod(svc.BackupDir, 0444) - defer os.Chmod(svc.BackupDir, 0755) + os.Chmod(svc.BackupDir, 0o444) + defer os.Chmod(svc.BackupDir, 0o755) - req = httptest.NewRequest(http.MethodDelete, "/api/v1/backups/"+filename, nil) + req = httptest.NewRequest(http.MethodDelete, "/api/v1/backups/"+filename, http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) // Should fail with 500 due to permission error (not 404) @@ -309,7 +309,7 @@ func TestBackupHandler_Restore_InternalError(t *testing.T) { defer os.RemoveAll(tmpDir) // Create a backup first - req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", nil) + req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusCreated, resp.Code) @@ -319,10 +319,10 @@ func TestBackupHandler_Restore_InternalError(t *testing.T) { filename := result["filename"] // Make data dir read-only to cause restore error - os.Chmod(svc.DataDir, 0444) - defer os.Chmod(svc.DataDir, 0755) + os.Chmod(svc.DataDir, 0o444) + defer os.Chmod(svc.DataDir, 0o755) - req = httptest.NewRequest(http.MethodPost, "/api/v1/backups/"+filename+"/restore", nil) + req = httptest.NewRequest(http.MethodPost, "/api/v1/backups/"+filename+"/restore", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) // Should fail with 500 due to permission error diff --git a/backend/internal/api/handlers/benchmark_test.go b/backend/internal/api/handlers/benchmark_test.go index 9b96c0ed..1bee57d8 100644 --- a/backend/internal/api/handlers/benchmark_test.go +++ b/backend/internal/api/handlers/benchmark_test.go @@ -70,7 +70,7 @@ func BenchmarkSecurityHandler_GetStatus(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - req := httptest.NewRequest("GET", "/api/v1/security/status", nil) + req := httptest.NewRequest("GET", "/api/v1/security/status", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { @@ -93,7 +93,7 @@ func BenchmarkSecurityHandler_GetStatus_NoSettings(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - req := httptest.NewRequest("GET", "/api/v1/security/status", nil) + req := httptest.NewRequest("GET", "/api/v1/security/status", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { @@ -126,7 +126,7 @@ func BenchmarkSecurityHandler_ListDecisions(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - req := httptest.NewRequest("GET", "/api/v1/security/decisions?limit=50", nil) + req := httptest.NewRequest("GET", "/api/v1/security/decisions?limit=50", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { @@ -159,7 +159,7 @@ func BenchmarkSecurityHandler_ListRuleSets(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - req := httptest.NewRequest("GET", "/api/v1/security/rulesets", nil) + req := httptest.NewRequest("GET", "/api/v1/security/rulesets", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { @@ -254,7 +254,7 @@ func BenchmarkSecurityHandler_GetConfig(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - req := httptest.NewRequest("GET", "/api/v1/security/config", nil) + req := httptest.NewRequest("GET", "/api/v1/security/config", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { @@ -323,7 +323,7 @@ func BenchmarkSecurityHandler_GetStatus_Parallel(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { - req := httptest.NewRequest("GET", "/api/v1/security/status", nil) + req := httptest.NewRequest("GET", "/api/v1/security/status", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { @@ -366,7 +366,7 @@ func BenchmarkSecurityHandler_ListDecisions_Parallel(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { - req := httptest.NewRequest("GET", "/api/v1/security/decisions?limit=50", nil) + req := httptest.NewRequest("GET", "/api/v1/security/decisions?limit=50", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { @@ -453,7 +453,7 @@ func BenchmarkSecurityHandler_ManySettingsLookups(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - req := httptest.NewRequest("GET", "/api/v1/security/status", nil) + req := httptest.NewRequest("GET", "/api/v1/security/status", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { diff --git a/backend/internal/api/handlers/certificate_handler.go b/backend/internal/api/handlers/certificate_handler.go index 24c3ab67..08cb6bf7 100644 --- a/backend/internal/api/handlers/certificate_handler.go +++ b/backend/internal/api/handlers/certificate_handler.go @@ -86,14 +86,22 @@ func (h *CertificateHandler) Upload(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to open cert file"}) return } - defer func() { _ = certSrc.Close() }() + defer func() { + if err := certSrc.Close(); err != nil { + logger.Log().WithError(err).Warn("failed to close certificate file") + } + }() keySrc, err := keyFile.Open() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to open key file"}) return } - defer func() { _ = keySrc.Close() }() + defer func() { + if err := keySrc.Close(); err != nil { + logger.Log().WithError(err).Warn("failed to close key file") + } + }() // Read to string // Limit size to avoid DoS (e.g. 1MB) diff --git a/backend/internal/api/handlers/certificate_handler_coverage_test.go b/backend/internal/api/handlers/certificate_handler_coverage_test.go index f6a00be7..8151c588 100644 --- a/backend/internal/api/handlers/certificate_handler_coverage_test.go +++ b/backend/internal/api/handlers/certificate_handler_coverage_test.go @@ -26,7 +26,7 @@ func TestCertificateHandler_List_DBError(t *testing.T) { h := NewCertificateHandler(svc, nil, nil) r.GET("/api/certificates", h.List) - req := httptest.NewRequest(http.MethodGet, "/api/certificates", nil) + req := httptest.NewRequest(http.MethodGet, "/api/certificates", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -44,7 +44,7 @@ func TestCertificateHandler_Delete_InvalidID(t *testing.T) { h := NewCertificateHandler(svc, nil, nil) r.DELETE("/api/certificates/:id", h.Delete) - req := httptest.NewRequest(http.MethodDelete, "/api/certificates/invalid", nil) + req := httptest.NewRequest(http.MethodDelete, "/api/certificates/invalid", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -63,7 +63,7 @@ func TestCertificateHandler_Delete_NotFound(t *testing.T) { h := NewCertificateHandler(svc, nil, nil) r.DELETE("/api/certificates/:id", h.Delete) - req := httptest.NewRequest(http.MethodDelete, "/api/certificates/9999", nil) + req := httptest.NewRequest(http.MethodDelete, "/api/certificates/9999", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -99,7 +99,7 @@ func TestCertificateHandler_Delete_NoBackupService(t *testing.T) { h := NewCertificateHandler(svc, nil, nil) r.DELETE("/api/certificates/:id", h.Delete) - req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+toStr(cert.ID), nil) + req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+toStr(cert.ID), http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -124,7 +124,7 @@ func TestCertificateHandler_Delete_CheckUsageDBError(t *testing.T) { h := NewCertificateHandler(svc, nil, nil) r.DELETE("/api/certificates/:id", h.Delete) - req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+toStr(cert.ID), nil) + req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+toStr(cert.ID), http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -147,7 +147,7 @@ func TestCertificateHandler_List_WithCertificates(t *testing.T) { h := NewCertificateHandler(svc, nil, nil) r.GET("/api/certificates", h.List) - req := httptest.NewRequest(http.MethodGet, "/api/certificates", nil) + req := httptest.NewRequest(http.MethodGet, "/api/certificates", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) diff --git a/backend/internal/api/handlers/certificate_handler_security_test.go b/backend/internal/api/handlers/certificate_handler_security_test.go index 351098b8..275a5cfa 100644 --- a/backend/internal/api/handlers/certificate_handler_security_test.go +++ b/backend/internal/api/handlers/certificate_handler_security_test.go @@ -35,7 +35,7 @@ func TestCertificateHandler_Delete_RequiresAuth(t *testing.T) { h := NewCertificateHandler(svc, nil, nil) r.DELETE("/api/certificates/:id", h.Delete) - req := httptest.NewRequest(http.MethodDelete, "/api/certificates/1", nil) + req := httptest.NewRequest(http.MethodDelete, "/api/certificates/1", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -65,7 +65,7 @@ func TestCertificateHandler_List_RequiresAuth(t *testing.T) { h := NewCertificateHandler(svc, nil, nil) r.GET("/api/certificates", h.List) - req := httptest.NewRequest(http.MethodGet, "/api/certificates", nil) + req := httptest.NewRequest(http.MethodGet, "/api/certificates", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -95,7 +95,7 @@ func TestCertificateHandler_Upload_RequiresAuth(t *testing.T) { h := NewCertificateHandler(svc, nil, nil) r.POST("/api/certificates", h.Upload) - req := httptest.NewRequest(http.MethodPost, "/api/certificates", nil) + req := httptest.NewRequest(http.MethodPost, "/api/certificates", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -141,7 +141,7 @@ func TestCertificateHandler_Delete_DiskSpaceCheck(t *testing.T) { h := NewCertificateHandler(svc, mockBackup, nil) r.DELETE("/api/certificates/:id", h.Delete) - req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/certificates/%d", cert.ID), nil) + req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/certificates/%d", cert.ID), http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -186,7 +186,7 @@ func TestCertificateHandler_Delete_NotificationRateLimiting(t *testing.T) { r.DELETE("/api/certificates/:id", h.Delete) // Delete first cert - req1 := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/certificates/%d", cert1.ID), nil) + req1 := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/certificates/%d", cert1.ID), http.NoBody) w1 := httptest.NewRecorder() r.ServeHTTP(w1, req1) @@ -195,7 +195,7 @@ func TestCertificateHandler_Delete_NotificationRateLimiting(t *testing.T) { } // Delete second cert (different ID, should not be rate limited) - req2 := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/certificates/%d", cert2.ID), nil) + req2 := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/certificates/%d", cert2.ID), http.NoBody) w2 := httptest.NewRecorder() r.ServeHTTP(w2, req2) diff --git a/backend/internal/api/handlers/certificate_handler_test.go b/backend/internal/api/handlers/certificate_handler_test.go index b72a4080..2559f5a9 100644 --- a/backend/internal/api/handlers/certificate_handler_test.go +++ b/backend/internal/api/handlers/certificate_handler_test.go @@ -70,7 +70,7 @@ func TestDeleteCertificate_InUse(t *testing.T) { r := setupCertTestRouter(t, db) - req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+toStr(cert.ID), nil) + req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+toStr(cert.ID), http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -117,7 +117,7 @@ func TestDeleteCertificate_CreatesBackup(t *testing.T) { h := NewCertificateHandler(svc, mockBackupService, nil) r.DELETE("/api/certificates/:id", h.Delete) - req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+toStr(cert.ID), nil) + req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+toStr(cert.ID), http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -169,7 +169,7 @@ func TestDeleteCertificate_BackupFailure(t *testing.T) { h := NewCertificateHandler(svc, mockBackupService, nil) r.DELETE("/api/certificates/:id", h.Delete) - req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+toStr(cert.ID), nil) + req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+toStr(cert.ID), http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -225,7 +225,7 @@ func TestDeleteCertificate_InUse_NoBackup(t *testing.T) { h := NewCertificateHandler(svc, mockBackupService, nil) r.DELETE("/api/certificates/:id", h.Delete) - req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+toStr(cert.ID), nil) + req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+toStr(cert.ID), http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -294,7 +294,7 @@ func TestCertificateHandler_List(t *testing.T) { h := NewCertificateHandler(svc, nil, nil) r.GET("/api/certificates", h.List) - req := httptest.NewRequest(http.MethodGet, "/api/certificates", nil) + req := httptest.NewRequest(http.MethodGet, "/api/certificates", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -436,7 +436,7 @@ func TestCertificateHandler_Upload_Success(t *testing.T) { } } -func generateSelfSignedCertPEM() (string, string, error) { +func generateSelfSignedCertPEM() (certPEM, keyPEM string, err error) { // generate RSA key priv, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { @@ -458,21 +458,13 @@ func generateSelfSignedCertPEM() (string, string, error) { if err != nil { return "", "", err } - certPEM := new(bytes.Buffer) - pem.Encode(certPEM, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) - keyPEM := new(bytes.Buffer) - pem.Encode(keyPEM, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) - return certPEM.String(), keyPEM.String(), nil + certBuf := new(bytes.Buffer) + pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + keyBuf := new(bytes.Buffer) + pem.Encode(keyBuf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) + certPEM = certBuf.String() + keyPEM = keyBuf.String() + return certPEM, keyPEM, nil } -// mockCertificateService implements minimal interface for Upload handler tests -type mockCertificateService struct { - uploadFunc func(name, cert, key string) (*models.SSLCertificate, error) -} - -func (m *mockCertificateService) UploadCertificate(name, cert, key string) (*models.SSLCertificate, error) { - if m.uploadFunc != nil { - return m.uploadFunc(name, cert, key) - } - return nil, fmt.Errorf("not implemented") -} +// Note: mockCertificateService removed — helper tests now use real service instances or testify mocks inlined where required. diff --git a/backend/internal/api/handlers/coverage_quick_test.go b/backend/internal/api/handlers/coverage_quick_test.go index d8d5cc35..9e067aa2 100644 --- a/backend/internal/api/handlers/coverage_quick_test.go +++ b/backend/internal/api/handlers/coverage_quick_test.go @@ -36,7 +36,7 @@ func TestBackupHandlerQuick(t *testing.T) { // List w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/backups", nil) + req := httptest.NewRequest(http.MethodGet, "/backups", http.NoBody) r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d", w.Code) @@ -44,7 +44,7 @@ func TestBackupHandlerQuick(t *testing.T) { // Create (backup) w2 := httptest.NewRecorder() - req2 := httptest.NewRequest(http.MethodPost, "/backups", nil) + req2 := httptest.NewRequest(http.MethodPost, "/backups", http.NoBody) r.ServeHTTP(w2, req2) if w2.Code != http.StatusCreated { t.Fatalf("create expected 201 got %d", w2.Code) @@ -59,7 +59,7 @@ func TestBackupHandlerQuick(t *testing.T) { // Delete missing w3 := httptest.NewRecorder() - req3 := httptest.NewRequest(http.MethodDelete, "/backups/missing", nil) + req3 := httptest.NewRequest(http.MethodDelete, "/backups/missing", http.NoBody) r.ServeHTTP(w3, req3) if w3.Code != http.StatusNotFound { t.Fatalf("delete missing expected 404 got %d", w3.Code) @@ -67,7 +67,7 @@ func TestBackupHandlerQuick(t *testing.T) { // Download missing w4 := httptest.NewRecorder() - req4 := httptest.NewRequest(http.MethodGet, "/backups/missing", nil) + req4 := httptest.NewRequest(http.MethodGet, "/backups/missing", http.NoBody) r.ServeHTTP(w4, req4) if w4.Code != http.StatusNotFound { t.Fatalf("download missing expected 404 got %d", w4.Code) @@ -75,7 +75,7 @@ func TestBackupHandlerQuick(t *testing.T) { // Download present (use filename returned from create) w5 := httptest.NewRecorder() - req5 := httptest.NewRequest(http.MethodGet, "/backups/"+createResp.Filename, nil) + req5 := httptest.NewRequest(http.MethodGet, "/backups/"+createResp.Filename, http.NoBody) r.ServeHTTP(w5, req5) if w5.Code != http.StatusOK { t.Fatalf("download expected 200 got %d", w5.Code) @@ -83,7 +83,7 @@ func TestBackupHandlerQuick(t *testing.T) { // Restore missing w6 := httptest.NewRecorder() - req6 := httptest.NewRequest(http.MethodPost, "/backups/missing/restore", nil) + req6 := httptest.NewRequest(http.MethodPost, "/backups/missing/restore", http.NoBody) r.ServeHTTP(w6, req6) if w6.Code != http.StatusNotFound { t.Fatalf("restore missing expected 404 got %d", w6.Code) @@ -91,7 +91,7 @@ func TestBackupHandlerQuick(t *testing.T) { // Restore ok w7 := httptest.NewRecorder() - req7 := httptest.NewRequest(http.MethodPost, "/backups/"+createResp.Filename+"/restore", nil) + req7 := httptest.NewRequest(http.MethodPost, "/backups/"+createResp.Filename+"/restore", http.NoBody) r.ServeHTTP(w7, req7) if w7.Code != http.StatusOK { t.Fatalf("restore expected 200 got %d", w7.Code) diff --git a/backend/internal/api/handlers/crowdsec_decisions_test.go b/backend/internal/api/handlers/crowdsec_decisions_test.go index 3d8b48c7..26ba34bf 100644 --- a/backend/internal/api/handlers/crowdsec_decisions_test.go +++ b/backend/internal/api/handlers/crowdsec_decisions_test.go @@ -44,7 +44,7 @@ func TestListDecisions_Success(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/decisions", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/decisions", http.NoBody) r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -83,7 +83,7 @@ func TestListDecisions_EmptyList(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/decisions", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/decisions", http.NoBody) r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -114,7 +114,7 @@ func TestListDecisions_CscliError(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/decisions", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/decisions", http.NoBody) r.ServeHTTP(w, req) // Should return 200 with empty list and error message @@ -146,7 +146,7 @@ func TestListDecisions_InvalidJSON(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/decisions", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/decisions", http.NoBody) r.ServeHTTP(w, req) assert.Equal(t, http.StatusInternalServerError, w.Code) @@ -339,7 +339,7 @@ func TestUnbanIP_Success(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodDelete, "/api/v1/admin/crowdsec/ban/192.168.1.100", nil) + req := httptest.NewRequest(http.MethodDelete, "/api/v1/admin/crowdsec/ban/192.168.1.100", http.NoBody) r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -373,7 +373,7 @@ func TestUnbanIP_CscliError(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodDelete, "/api/v1/admin/crowdsec/ban/192.168.1.100", nil) + req := httptest.NewRequest(http.MethodDelete, "/api/v1/admin/crowdsec/ban/192.168.1.100", http.NoBody) r.ServeHTTP(w, req) assert.Equal(t, http.StatusInternalServerError, w.Code) @@ -401,7 +401,7 @@ func TestListDecisions_MultipleDecisions(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/decisions", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/decisions", http.NoBody) r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) diff --git a/backend/internal/api/handlers/crowdsec_exec.go b/backend/internal/api/handlers/crowdsec_exec.go index 5852018d..7214f418 100644 --- a/backend/internal/api/handlers/crowdsec_exec.go +++ b/backend/internal/api/handlers/crowdsec_exec.go @@ -2,6 +2,7 @@ package handlers import ( "context" + "errors" "fmt" "os" "os/exec" @@ -61,23 +62,33 @@ func (e *DefaultCrowdsecExecutor) Stop(ctx context.Context, configDir string) er return nil } -func (e *DefaultCrowdsecExecutor) Status(ctx context.Context, configDir string) (bool, int, error) { +func (e *DefaultCrowdsecExecutor) Status(ctx context.Context, configDir string) (running bool, pid int, err error) { b, err := os.ReadFile(e.pidFile(configDir)) if err != nil { + // Missing pid file is treated as not running return false, 0, nil } - pid, err := strconv.Atoi(string(b)) + + pid, err = strconv.Atoi(string(b)) if err != nil { + // Malformed pid file is treated as not running return false, 0, nil } - // Check process exists + proc, err := os.FindProcess(pid) if err != nil { + // Process lookup failures are treated as not running return false, pid, nil } + // Sending signal 0 is not portable on Windows, but OK for Linux containers - if err := proc.Signal(syscall.Signal(0)); err != nil { + if err = proc.Signal(syscall.Signal(0)); err != nil { + if errors.Is(err, os.ErrProcessDone) { + return false, pid, nil + } + // ESRCH or other errors mean process isn't running return false, pid, nil } + return true, pid, nil } diff --git a/backend/internal/api/handlers/crowdsec_handler.go b/backend/internal/api/handlers/crowdsec_handler.go index a3b98c80..7d17623e 100644 --- a/backend/internal/api/handlers/crowdsec_handler.go +++ b/backend/internal/api/handlers/crowdsec_handler.go @@ -20,19 +20,19 @@ import ( "gorm.io/gorm" ) -// Executor abstracts starting/stopping CrowdSec so tests can mock it. +// CrowdsecExecutor abstracts starting/stopping CrowdSec so tests can mock it. type CrowdsecExecutor interface { Start(ctx context.Context, binPath, configDir string) (int, error) Stop(ctx context.Context, configDir string) error Status(ctx context.Context, configDir string) (running bool, pid int, err error) } -// CommandExecutor abstracts command execution for testing +// CommandExecutor abstracts command execution for testing. type CommandExecutor interface { Execute(ctx context.Context, name string, args ...string) ([]byte, error) } -// RealCommandExecutor executes commands using os/exec +// RealCommandExecutor executes commands using os/exec. type RealCommandExecutor struct{} // Execute runs a command and returns its output @@ -50,10 +50,10 @@ type CrowdsecHandler struct { DataDir string } -func NewCrowdsecHandler(db *gorm.DB, exec CrowdsecExecutor, binPath, dataDir string) *CrowdsecHandler { +func NewCrowdsecHandler(db *gorm.DB, executor CrowdsecExecutor, binPath, dataDir string) *CrowdsecHandler { return &CrowdsecHandler{ DB: db, - Executor: exec, + Executor: executor, CmdExec: &RealCommandExecutor{}, BinPath: binPath, DataDir: dataDir, @@ -139,13 +139,21 @@ func (h *CrowdsecHandler) ImportConfig(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to open temp file"}) return } - defer in.Close() + defer func() { + if err := in.Close(); err != nil { + logger.Log().WithError(err).Warn("failed to close temp file") + } + }() out, err := os.Create(target) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create target file"}) return } - defer out.Close() + defer func() { + if err := out.Close(); err != nil { + logger.Log().WithError(err).Warn("failed to close target file") + } + }() if _, err := io.Copy(out, in); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to write config"}) return @@ -197,7 +205,11 @@ func (h *CrowdsecHandler) ExportConfig(c *gin.Context) { if err != nil { return err } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + logger.Log().WithError(err).Warn("failed to close file while archiving", "path", path) + } + }() hdr := &tar.Header{ Name: rel, diff --git a/backend/internal/api/handlers/crowdsec_handler_coverage_test.go b/backend/internal/api/handlers/crowdsec_handler_coverage_test.go index 9b3bacf4..c0235ff3 100644 --- a/backend/internal/api/handlers/crowdsec_handler_coverage_test.go +++ b/backend/internal/api/handlers/crowdsec_handler_coverage_test.go @@ -24,7 +24,7 @@ func (f *errorExec) Start(ctx context.Context, binPath, configDir string) (int, func (f *errorExec) Stop(ctx context.Context, configDir string) error { return errors.New("failed to stop crowdsec") } -func (f *errorExec) Status(ctx context.Context, configDir string) (bool, int, error) { +func (f *errorExec) Status(ctx context.Context, configDir string) (running bool, pid int, err error) { return false, 0, errors.New("failed to get status") } @@ -40,7 +40,7 @@ func TestCrowdsec_Start_Error(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/start", nil) + req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/start", http.NoBody) r.ServeHTTP(w, req) assert.Equal(t, http.StatusInternalServerError, w.Code) @@ -59,7 +59,7 @@ func TestCrowdsec_Stop_Error(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/stop", nil) + req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/stop", http.NoBody) r.ServeHTTP(w, req) assert.Equal(t, http.StatusInternalServerError, w.Code) @@ -78,7 +78,7 @@ func TestCrowdsec_Status_Error(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/status", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/status", http.NoBody) r.ServeHTTP(w, req) assert.Equal(t, http.StatusInternalServerError, w.Code) @@ -98,7 +98,7 @@ func TestCrowdsec_ReadFile_MissingPath(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/file", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/file", http.NoBody) r.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) @@ -118,7 +118,7 @@ func TestCrowdsec_ReadFile_PathTraversal(t *testing.T) { // Attempt path traversal w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/file?path=../../../etc/passwd", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/file?path=../../../etc/passwd", http.NoBody) r.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) @@ -137,7 +137,7 @@ func TestCrowdsec_ReadFile_NotFound(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/file?path=nonexistent.conf", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/file?path=nonexistent.conf", http.NoBody) r.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) @@ -227,7 +227,7 @@ func TestCrowdsec_ExportConfig_NotFound(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/export", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/export", http.NoBody) r.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) @@ -247,7 +247,7 @@ func TestCrowdsec_ListFiles_EmptyDir(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/files", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/files", http.NoBody) r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -273,7 +273,7 @@ func TestCrowdsec_ListFiles_NonExistent(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/files", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/files", http.NoBody) r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -298,7 +298,7 @@ func TestCrowdsec_ImportConfig_NoFile(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/import", nil) + req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/import", http.NoBody) req.Header.Set("Content-Type", "multipart/form-data") r.ServeHTTP(w, req) @@ -323,7 +323,7 @@ func TestCrowdsec_ReadFile_NestedPath(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/file?path=subdir/test.conf", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/file?path=subdir/test.conf", http.NoBody) r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) diff --git a/backend/internal/api/handlers/crowdsec_handler_test.go b/backend/internal/api/handlers/crowdsec_handler_test.go index df43b58d..d4e775e4 100644 --- a/backend/internal/api/handlers/crowdsec_handler_test.go +++ b/backend/internal/api/handlers/crowdsec_handler_test.go @@ -27,7 +27,7 @@ func (f *fakeExec) Stop(ctx context.Context, configDir string) error { f.started = false return nil } -func (f *fakeExec) Status(ctx context.Context, configDir string) (bool, int, error) { +func (f *fakeExec) Status(ctx context.Context, configDir string) (running bool, pid int, err error) { if f.started { return true, 12345, nil } @@ -53,7 +53,7 @@ func TestCrowdsecEndpoints(t *testing.T) { // Status (initially stopped) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/status", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/status", http.NoBody) r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("status expected 200 got %d", w.Code) @@ -61,7 +61,7 @@ func TestCrowdsecEndpoints(t *testing.T) { // Start w2 := httptest.NewRecorder() - req2 := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/start", nil) + req2 := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/start", http.NoBody) r.ServeHTTP(w2, req2) if w2.Code != http.StatusOK { t.Fatalf("start expected 200 got %d", w2.Code) @@ -69,7 +69,7 @@ func TestCrowdsecEndpoints(t *testing.T) { // Stop w3 := httptest.NewRecorder() - req3 := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/stop", nil) + req3 := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/stop", http.NoBody) r.ServeHTTP(w3, req3) if w3.Code != http.StatusOK { t.Fatalf("stop expected 200 got %d", w3.Code) @@ -151,7 +151,7 @@ func TestImportCreatesBackup(t *testing.T) { // fallback: check for any .backup.* in same parent dir entries, _ := os.ReadDir(filepath.Dir(tmpDir)) for _, e := range entries { - if e.IsDir() && filepath.Ext(e.Name()) == "" && (len(e.Name()) > 0) && (filepath.Base(e.Name()) != filepath.Base(tmpDir)) { + if e.IsDir() && filepath.Ext(e.Name()) == "" && e.Name() != "" && (filepath.Base(e.Name()) != filepath.Base(tmpDir)) { // best-effort assume backup present found = true break @@ -181,7 +181,7 @@ func TestExportConfig(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/export", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/export", http.NoBody) r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("export expected 200 got %d body=%s", w.Code, w.Body.String()) @@ -211,14 +211,14 @@ func TestListAndReadFile(t *testing.T) { h.RegisterRoutes(g) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/files", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/files", http.NoBody) r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("files expected 200 got %d body=%s", w.Code, w.Body.String()) } // read a single file w2 := httptest.NewRecorder() - req2 := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/file?path=conf.d/a.conf", nil) + req2 := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/file?path=conf.d/a.conf", http.NoBody) r.ServeHTTP(w2, req2) if w2.Code != http.StatusOK { t.Fatalf("file read expected 200 got %d body=%s", w2.Code, w2.Body.String()) diff --git a/backend/internal/api/handlers/doc.go b/backend/internal/api/handlers/doc.go new file mode 100644 index 00000000..29205a6d --- /dev/null +++ b/backend/internal/api/handlers/doc.go @@ -0,0 +1,8 @@ +// Package handlers provides HTTP handlers used by the Charon backend API. +// +// It exposes Gin-based handler implementations for resources such as +// certificates, proxy hosts, users, notifications, backups, and system +// configuration. This package wires services to HTTP endpoints and +// performs request validation, response formatting, and basic error +// handling. +package handlers diff --git a/backend/internal/api/handlers/docker_handler_test.go b/backend/internal/api/handlers/docker_handler_test.go index bab438db..0ac6c1cd 100644 --- a/backend/internal/api/handlers/docker_handler_test.go +++ b/backend/internal/api/handlers/docker_handler_test.go @@ -50,7 +50,7 @@ func TestDockerHandler_ListContainers(t *testing.T) { h := NewDockerHandler(svc, rsService) h.RegisterRoutes(r.Group("/")) - req, _ := http.NewRequest("GET", "/docker/containers", nil) + req, _ := http.NewRequest("GET", "/docker/containers", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -70,7 +70,7 @@ func TestDockerHandler_ListContainers_NonExistentServerID(t *testing.T) { h.RegisterRoutes(r.Group("/")) // Request with non-existent server_id - req, _ := http.NewRequest("GET", "/docker/containers?server_id=non-existent-uuid", nil) + req, _ := http.NewRequest("GET", "/docker/containers?server_id=non-existent-uuid", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -101,7 +101,7 @@ func TestDockerHandler_ListContainers_WithServerID(t *testing.T) { h.RegisterRoutes(r.Group("/")) // Request with valid server_id (will fail to connect, but shouldn't error on lookup) - req, _ := http.NewRequest("GET", "/docker/containers?server_id="+server.UUID, nil) + req, _ := http.NewRequest("GET", "/docker/containers?server_id="+server.UUID, http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -124,7 +124,7 @@ func TestDockerHandler_ListContainers_WithHostQuery(t *testing.T) { h.RegisterRoutes(r.Group("/")) // Request with custom host parameter - req, _ := http.NewRequest("GET", "/docker/containers?host=tcp://invalid-host:2375", nil) + req, _ := http.NewRequest("GET", "/docker/containers?host=tcp://invalid-host:2375", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) diff --git a/backend/internal/api/handlers/domain_handler_test.go b/backend/internal/api/handlers/domain_handler_test.go index 57a9f519..e4f94f11 100644 --- a/backend/internal/api/handlers/domain_handler_test.go +++ b/backend/internal/api/handlers/domain_handler_test.go @@ -54,7 +54,7 @@ func TestDomainLifecycle(t *testing.T) { require.NotEmpty(t, created.UUID) // 2. List Domains - req = httptest.NewRequest(http.MethodGet, "/api/v1/domains", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/domains", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) @@ -65,13 +65,13 @@ func TestDomainLifecycle(t *testing.T) { require.Equal(t, "example.com", list[0].Name) // 3. Delete Domain - req = httptest.NewRequest(http.MethodDelete, "/api/v1/domains/"+created.UUID, nil) + req = httptest.NewRequest(http.MethodDelete, "/api/v1/domains/"+created.UUID, http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) // 4. Verify Deletion - req = httptest.NewRequest(http.MethodGet, "/api/v1/domains", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/domains", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) @@ -101,7 +101,7 @@ func TestDomainErrors(t *testing.T) { func TestDomainDelete_NotFound(t *testing.T) { router, _ := setupDomainTestRouter(t) - req := httptest.NewRequest(http.MethodDelete, "/api/v1/domains/nonexistent-uuid", nil) + req := httptest.NewRequest(http.MethodDelete, "/api/v1/domains/nonexistent-uuid", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) // Handler may return 200 with deleted=true even if not found (soft delete behavior) @@ -136,7 +136,7 @@ func TestDomainCreate_Duplicate(t *testing.T) { func TestDomainList_Empty(t *testing.T) { router, _ := setupDomainTestRouter(t) - req := httptest.NewRequest(http.MethodGet, "/api/v1/domains", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/domains", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) diff --git a/backend/internal/api/handlers/feature_flags_handler_coverage_test.go b/backend/internal/api/handlers/feature_flags_handler_coverage_test.go index 82468d59..5e84f978 100644 --- a/backend/internal/api/handlers/feature_flags_handler_coverage_test.go +++ b/backend/internal/api/handlers/feature_flags_handler_coverage_test.go @@ -33,7 +33,7 @@ func TestFeatureFlagsHandler_GetFlags_DBPrecedence(t *testing.T) { r := gin.New() r.GET("/api/v1/feature-flags", h.GetFlags) - req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -58,7 +58,7 @@ func TestFeatureFlagsHandler_GetFlags_EnvFallback(t *testing.T) { r := gin.New() r.GET("/api/v1/feature-flags", h.GetFlags) - req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -83,7 +83,7 @@ func TestFeatureFlagsHandler_GetFlags_EnvShortForm(t *testing.T) { r := gin.New() r.GET("/api/v1/feature-flags", h.GetFlags) - req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -108,7 +108,7 @@ func TestFeatureFlagsHandler_GetFlags_EnvNumeric(t *testing.T) { r := gin.New() r.GET("/api/v1/feature-flags", h.GetFlags) - req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -131,7 +131,7 @@ func TestFeatureFlagsHandler_GetFlags_DefaultTrue(t *testing.T) { r := gin.New() r.GET("/api/v1/feature-flags", h.GetFlags) - req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -154,7 +154,7 @@ func TestFeatureFlagsHandler_GetFlags_AllDefaultFlagsPresent(t *testing.T) { r := gin.New() r.GET("/api/v1/feature-flags", h.GetFlags) - req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -353,7 +353,7 @@ func TestFeatureFlagsHandler_GetFlags_DBValueVariants(t *testing.T) { r := gin.New() r.GET("/api/v1/feature-flags", h.GetFlags) - req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -396,7 +396,7 @@ func TestFeatureFlagsHandler_GetFlags_EnvValueVariants(t *testing.T) { r := gin.New() r.GET("/api/v1/feature-flags", h.GetFlags) - req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) diff --git a/backend/internal/api/handlers/feature_flags_handler_test.go b/backend/internal/api/handlers/feature_flags_handler_test.go index 4000a0b6..d994a8de 100644 --- a/backend/internal/api/handlers/feature_flags_handler_test.go +++ b/backend/internal/api/handlers/feature_flags_handler_test.go @@ -32,7 +32,7 @@ func TestFeatureFlags_GetAndUpdate(t *testing.T) { r.PUT("/api/v1/feature-flags", h.UpdateFlags) // 1) GET should return all default flags (as keys) - req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { @@ -83,7 +83,7 @@ func TestFeatureFlags_EnvFallback(t *testing.T) { r := gin.New() r.GET("/api/v1/feature-flags", h.GetFlags) - req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/feature-flags", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { diff --git a/backend/internal/api/handlers/handlers_test.go b/backend/internal/api/handlers/handlers_test.go index 9a568076..a27132ac 100644 --- a/backend/internal/api/handlers/handlers_test.go +++ b/backend/internal/api/handlers/handlers_test.go @@ -56,7 +56,7 @@ func TestRemoteServerHandler_List(t *testing.T) { // Test List w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/api/v1/remote-servers", nil) + req, _ := http.NewRequest("GET", "/api/v1/remote-servers", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -123,7 +123,7 @@ func TestRemoteServerHandler_TestConnection(t *testing.T) { // Test connection w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/api/v1/remote-servers/"+server.UUID+"/test", nil) + req, _ := http.NewRequest("POST", "/api/v1/remote-servers/"+server.UUID+"/test", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -157,7 +157,7 @@ func TestRemoteServerHandler_Get(t *testing.T) { // Test Get w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/api/v1/remote-servers/"+server.UUID, nil) + req, _ := http.NewRequest("GET", "/api/v1/remote-servers/"+server.UUID, http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -235,14 +235,14 @@ func TestRemoteServerHandler_Delete(t *testing.T) { // Test Delete w := httptest.NewRecorder() - req, _ := http.NewRequest("DELETE", "/api/v1/remote-servers/"+server.UUID, nil) + req, _ := http.NewRequest("DELETE", "/api/v1/remote-servers/"+server.UUID, http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNoContent, w.Code) // Verify Delete w2 := httptest.NewRecorder() - req2, _ := http.NewRequest("GET", "/api/v1/remote-servers/"+server.UUID, nil) + req2, _ := http.NewRequest("GET", "/api/v1/remote-servers/"+server.UUID, http.NoBody) router.ServeHTTP(w2, req2) assert.Equal(t, http.StatusNotFound, w2.Code) @@ -271,7 +271,7 @@ func TestProxyHostHandler_List(t *testing.T) { // Test List w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/api/v1/proxy-hosts", nil) + req, _ := http.NewRequest("GET", "/api/v1/proxy-hosts", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -362,7 +362,7 @@ func TestProxyHostHandler_PartialUpdate_DoesNotWipeFields(t *testing.T) { // Fetch via GET to ensure DB persisted state correctly w2 := httptest.NewRecorder() - req2, _ := http.NewRequest("GET", "/api/v1/proxy-hosts/"+original.UUID, nil) + req2, _ := http.NewRequest("GET", "/api/v1/proxy-hosts/"+original.UUID, http.NoBody) router.ServeHTTP(w2, req2) assert.Equal(t, http.StatusOK, w2.Code) @@ -382,7 +382,7 @@ func TestHealthHandler(t *testing.T) { router.GET("/health", handlers.HealthHandler) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/health", nil) + req, _ := http.NewRequest("GET", "/health", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -404,7 +404,7 @@ func TestRemoteServerHandler_Errors(t *testing.T) { // Get non-existent w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/api/v1/remote-servers/non-existent", nil) + req, _ := http.NewRequest("GET", "/api/v1/remote-servers/non-existent", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) @@ -417,7 +417,7 @@ func TestRemoteServerHandler_Errors(t *testing.T) { // Delete non-existent w = httptest.NewRecorder() - req, _ = http.NewRequest("DELETE", "/api/v1/remote-servers/non-existent", nil) + req, _ = http.NewRequest("DELETE", "/api/v1/remote-servers/non-existent", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } diff --git a/backend/internal/api/handlers/health_handler_test.go b/backend/internal/api/handlers/health_handler_test.go index b890cb59..5448a42e 100644 --- a/backend/internal/api/handlers/health_handler_test.go +++ b/backend/internal/api/handlers/health_handler_test.go @@ -15,7 +15,7 @@ func TestHealthHandler(t *testing.T) { r := gin.New() r.GET("/health", HealthHandler) - req, _ := http.NewRequest("GET", "/health", nil) + req, _ := http.NewRequest("GET", "/health", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) diff --git a/backend/internal/api/handlers/import_handler.go b/backend/internal/api/handlers/import_handler.go index 4b8e1203..f8495f12 100644 --- a/backend/internal/api/handlers/import_handler.go +++ b/backend/internal/api/handlers/import_handler.go @@ -267,7 +267,7 @@ func (h *ImportHandler) Upload(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": "invalid import directory"}) return } - if err := os.MkdirAll(uploadsDir, 0755); err != nil { + if err := os.MkdirAll(uploadsDir, 0o755); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create uploads directory"}) return } @@ -276,7 +276,7 @@ func (h *ImportHandler) Upload(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": "invalid temp path"}) return } - if err := os.WriteFile(tempPath, []byte(req.Content), 0644); err != nil { + if err := os.WriteFile(tempPath, []byte(req.Content), 0o644); err != nil { middleware.GetRequestLogger(c).WithField("tempPath", util.SanitizeForLog(filepath.Base(tempPath))).WithError(err).Error("Import Upload: failed to write temp file") c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to write upload"}) return @@ -415,7 +415,7 @@ func (h *ImportHandler) UploadMulti(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": "invalid session directory"}) return } - if err := os.MkdirAll(sessionDir, 0755); err != nil { + if err := os.MkdirAll(sessionDir, 0o755); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create session directory"}) return } @@ -438,13 +438,13 @@ func (h *ImportHandler) UploadMulti(c *gin.Context) { // Create parent directory if file is in a subdirectory if dir := filepath.Dir(targetPath); dir != sessionDir { - if err := os.MkdirAll(dir, 0755); err != nil { + if err := os.MkdirAll(dir, 0o755); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to create directory for %s", f.Filename)}) return } } - if err := os.WriteFile(targetPath, []byte(f.Content), 0644); err != nil { + if err := os.WriteFile(targetPath, []byte(f.Content), 0o644); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to write file %s", f.Filename)}) return } @@ -510,12 +510,12 @@ func detectImportDirectives(content string) []string { for _, line := range lines { trimmed := strings.TrimSpace(line) if strings.HasPrefix(trimmed, "import ") { - path := strings.TrimSpace(strings.TrimPrefix(trimmed, "import")) + importPath := strings.TrimSpace(strings.TrimPrefix(trimmed, "import")) // Remove any trailing comments - if idx := strings.Index(path, "#"); idx != -1 { - path = strings.TrimSpace(path[:idx]) + if idx := strings.Index(importPath, "#"); idx != -1 { + importPath = strings.TrimSpace(importPath[:idx]) } - imports = append(imports, path) + imports = append(imports, importPath) } } return imports diff --git a/backend/internal/api/handlers/import_handler_sanitize_test.go b/backend/internal/api/handlers/import_handler_sanitize_test.go index f4a405b2..2140ca0b 100644 --- a/backend/internal/api/handlers/import_handler_sanitize_test.go +++ b/backend/internal/api/handlers/import_handler_sanitize_test.go @@ -23,7 +23,7 @@ func TestImportUploadSanitizesFilename(t *testing.T) { db := OpenTestDB(t) // Create a fake caddy executable to avoid dependency on system binary fakeCaddy := filepath.Join(tmpDir, "caddy") - os.WriteFile(fakeCaddy, []byte("#!/bin/sh\nexit 0"), 0755) + os.WriteFile(fakeCaddy, []byte("#!/bin/sh\nexit 0"), 0o755) svc := NewImportHandler(db, fakeCaddy, tmpDir, "") router := gin.New() diff --git a/backend/internal/api/handlers/import_handler_test.go b/backend/internal/api/handlers/import_handler_test.go index cac5b128..0ca1c3d6 100644 --- a/backend/internal/api/handlers/import_handler_test.go +++ b/backend/internal/api/handlers/import_handler_test.go @@ -40,7 +40,7 @@ func TestImportHandler_GetStatus(t *testing.T) { router.GET("/import/status", handler.GetStatus) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/import/status", nil) + req, _ := http.NewRequest("GET", "/import/status", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -52,7 +52,7 @@ func TestImportHandler_GetStatus(t *testing.T) { // Case 2: No DB session but has mounted Caddyfile tmpDir := t.TempDir() mountPath := filepath.Join(tmpDir, "mounted.caddyfile") - os.WriteFile(mountPath, []byte("example.com"), 0644) + os.WriteFile(mountPath, []byte("example.com"), 0o644) handler2 := handlers.NewImportHandler(db, "echo", "/tmp", mountPath) router2 := gin.New() @@ -97,7 +97,7 @@ func TestImportHandler_GetPreview(t *testing.T) { // Case 1: No session w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/import/preview", nil) + req, _ := http.NewRequest("GET", "/import/preview", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) @@ -110,7 +110,7 @@ func TestImportHandler_GetPreview(t *testing.T) { db.Create(&session) w = httptest.NewRecorder() - req, _ = http.NewRequest("GET", "/import/preview", nil) + req, _ = http.NewRequest("GET", "/import/preview", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -141,7 +141,7 @@ func TestImportHandler_Cancel(t *testing.T) { db.Create(&session) w := httptest.NewRecorder() - req, _ := http.NewRequest("DELETE", "/import/cancel?session_uuid=test-uuid", nil) + req, _ := http.NewRequest("DELETE", "/import/cancel?session_uuid=test-uuid", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -198,7 +198,7 @@ func TestImportHandler_Upload(t *testing.T) { // Use fake caddy script cwd, _ := os.Getwd() fakeCaddy := filepath.Join(cwd, "testdata", "fake_caddy.sh") - os.Chmod(fakeCaddy, 0755) + os.Chmod(fakeCaddy, 0o755) tmpDir := t.TempDir() handler := handlers.NewImportHandler(db, fakeCaddy, tmpDir, "") @@ -231,7 +231,7 @@ func TestImportHandler_GetPreview_WithContent(t *testing.T) { // Case: Active session with source file content := "example.com {\n reverse_proxy localhost:8080\n}" sourceFile := filepath.Join(tmpDir, "source.caddyfile") - err := os.WriteFile(sourceFile, []byte(content), 0644) + err := os.WriteFile(sourceFile, []byte(content), 0o644) assert.NoError(t, err) // Case: Active session with source file @@ -244,7 +244,7 @@ func TestImportHandler_GetPreview_WithContent(t *testing.T) { db.Create(&session) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/import/preview", nil) + req, _ := http.NewRequest("GET", "/import/preview", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -307,7 +307,7 @@ func TestImportHandler_Cancel_Errors(t *testing.T) { // Case 1: Session not found w := httptest.NewRecorder() - req, _ := http.NewRequest("DELETE", "/import/cancel?session_uuid=non-existent", nil) + req, _ := http.NewRequest("DELETE", "/import/cancel?session_uuid=non-existent", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } @@ -320,14 +320,14 @@ func TestCheckMountedImport(t *testing.T) { // Use fake caddy script cwd, _ := os.Getwd() fakeCaddy := filepath.Join(cwd, "testdata", "fake_caddy.sh") - os.Chmod(fakeCaddy, 0755) + os.Chmod(fakeCaddy, 0o755) // Case 1: File does not exist err := handlers.CheckMountedImport(db, mountPath, fakeCaddy, tmpDir) assert.NoError(t, err) // Case 2: File exists, not processed - err = os.WriteFile(mountPath, []byte("example.com"), 0644) + err = os.WriteFile(mountPath, []byte("example.com"), 0o644) assert.NoError(t, err) err = handlers.CheckMountedImport(db, mountPath, fakeCaddy, tmpDir) @@ -431,10 +431,10 @@ func TestImportHandler_GetPreview_BackupContent(t *testing.T) { // Create backup file backupDir := filepath.Join(tmpDir, "backups") - os.MkdirAll(backupDir, 0755) + os.MkdirAll(backupDir, 0o755) content := "backup content" backupFile := filepath.Join(backupDir, "source.caddyfile") - os.WriteFile(backupFile, []byte(content), 0644) + os.WriteFile(backupFile, []byte(content), 0o644) // Case: Active session with missing source file but existing backup session := models.ImportSession{ @@ -446,7 +446,7 @@ func TestImportHandler_GetPreview_BackupContent(t *testing.T) { db.Create(&session) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/import/preview", nil) + req, _ := http.NewRequest("GET", "/import/preview", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -465,7 +465,7 @@ func TestImportHandler_RegisterRoutes(t *testing.T) { // Verify routes exist by making requests w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/api/v1/import/status", nil) + req, _ := http.NewRequest("GET", "/api/v1/import/status", http.NoBody) router.ServeHTTP(w, req) assert.NotEqual(t, http.StatusNotFound, w.Code) } @@ -478,20 +478,20 @@ func TestImportHandler_GetPreview_TransientMount(t *testing.T) { // Create a mounted Caddyfile content := "example.com" - err := os.WriteFile(mountPath, []byte(content), 0644) + err := os.WriteFile(mountPath, []byte(content), 0o644) assert.NoError(t, err) // Use fake caddy script cwd, _ := os.Getwd() fakeCaddy := filepath.Join(cwd, "testdata", "fake_caddy_hosts.sh") - os.Chmod(fakeCaddy, 0755) + os.Chmod(fakeCaddy, 0o755) handler := handlers.NewImportHandler(db, fakeCaddy, tmpDir, mountPath) router := gin.New() router.GET("/import/preview", handler.GetPreview) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/import/preview", nil) + req, _ := http.NewRequest("GET", "/import/preview", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code, "Response body: %s", w.Body.String()) @@ -522,7 +522,7 @@ func TestImportHandler_Commit_TransientUpload(t *testing.T) { // Use fake caddy script cwd, _ := os.Getwd() fakeCaddy := filepath.Join(cwd, "testdata", "fake_caddy_hosts.sh") - os.Chmod(fakeCaddy, 0755) + os.Chmod(fakeCaddy, 0o755) handler := handlers.NewImportHandler(db, fakeCaddy, tmpDir, "") router := gin.New() @@ -580,13 +580,13 @@ func TestImportHandler_Commit_TransientMount(t *testing.T) { mountPath := filepath.Join(tmpDir, "mounted.caddyfile") // Create a mounted Caddyfile - err := os.WriteFile(mountPath, []byte("mounted.com"), 0644) + err := os.WriteFile(mountPath, []byte("mounted.com"), 0o644) assert.NoError(t, err) // Use fake caddy script cwd, _ := os.Getwd() fakeCaddy := filepath.Join(cwd, "testdata", "fake_caddy_hosts.sh") - os.Chmod(fakeCaddy, 0755) + os.Chmod(fakeCaddy, 0o755) handler := handlers.NewImportHandler(db, fakeCaddy, tmpDir, mountPath) router := gin.New() @@ -627,7 +627,7 @@ func TestImportHandler_Cancel_TransientUpload(t *testing.T) { // Use fake caddy script cwd, _ := os.Getwd() fakeCaddy := filepath.Join(cwd, "testdata", "fake_caddy_hosts.sh") - os.Chmod(fakeCaddy, 0755) + os.Chmod(fakeCaddy, 0o755) handler := handlers.NewImportHandler(db, fakeCaddy, tmpDir, "") router := gin.New() @@ -658,7 +658,7 @@ func TestImportHandler_Cancel_TransientUpload(t *testing.T) { // Cancel should delete the file w = httptest.NewRecorder() - req, _ = http.NewRequest("DELETE", "/import/cancel?session_uuid="+sessionID, nil) + req, _ = http.NewRequest("DELETE", "/import/cancel?session_uuid="+sessionID, http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -704,7 +704,7 @@ func TestImportHandler_Errors(t *testing.T) { // Cancel - Session Not Found w = httptest.NewRecorder() - req, _ = http.NewRequest("DELETE", "/import/cancel?session_uuid=non-existent", nil) + req, _ = http.NewRequest("DELETE", "/import/cancel?session_uuid=non-existent", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } @@ -794,7 +794,7 @@ func TestImportHandler_UploadMulti(t *testing.T) { // Use fake caddy script cwd, _ := os.Getwd() fakeCaddy := filepath.Join(cwd, "testdata", "fake_caddy_hosts.sh") - os.Chmod(fakeCaddy, 0755) + os.Chmod(fakeCaddy, 0o755) handler := handlers.NewImportHandler(db, fakeCaddy, tmpDir, "") router := gin.New() diff --git a/backend/internal/api/handlers/logs_handler.go b/backend/internal/api/handlers/logs_handler.go index 66994212..0e39828a 100644 --- a/backend/internal/api/handlers/logs_handler.go +++ b/backend/internal/api/handlers/logs_handler.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" + "github.com/Wikid82/charon/backend/internal/logger" "github.com/Wikid82/charon/backend/internal/models" "github.com/Wikid82/charon/backend/internal/services" "github.com/gin-gonic/gin" @@ -84,22 +85,36 @@ func (h *LogsHandler) Download(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create temp file"}) return } - defer os.Remove(tmpFile.Name()) + defer func() { + if err := os.Remove(tmpFile.Name()); err != nil { + logger.Log().WithError(err).Warn("failed to remove temp file") + } + }() srcFile, err := os.Open(path) if err != nil { - _ = tmpFile.Close() + if err := tmpFile.Close(); err != nil { + logger.Log().WithError(err).Warn("failed to close temp file") + } c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to open log file"}) return } - defer func() { _ = srcFile.Close() }() + defer func() { + if err := srcFile.Close(); err != nil { + logger.Log().WithError(err).Warn("failed to close source log file") + } + }() if _, err := io.Copy(tmpFile, srcFile); err != nil { - _ = tmpFile.Close() + if err := tmpFile.Close(); err != nil { + logger.Log().WithError(err).Warn("failed to close temp file after copy error") + } c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to copy log file"}) return } - _ = tmpFile.Close() + if err := tmpFile.Close(); err != nil { + logger.Log().WithError(err).Warn("failed to close temp file after copy") + } c.Header("Content-Disposition", "attachment; filename="+filename) c.File(tmpFile.Name()) diff --git a/backend/internal/api/handlers/logs_handler_coverage_test.go b/backend/internal/api/handlers/logs_handler_coverage_test.go index 96bf452f..d8c6b91f 100644 --- a/backend/internal/api/handlers/logs_handler_coverage_test.go +++ b/backend/internal/api/handlers/logs_handler_coverage_test.go @@ -1,6 +1,7 @@ package handlers import ( + "net/http" "net/http/httptest" "os" "path/filepath" @@ -38,7 +39,7 @@ func TestLogsHandler_Read_FilterBySearch(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "filename", Value: "access.log"}} - c.Request = httptest.NewRequest("GET", "/logs/access.log?search=error", nil) + c.Request = httptest.NewRequest("GET", "/logs/access.log?search=error", http.NoBody) h.Read(c) @@ -69,7 +70,7 @@ func TestLogsHandler_Read_FilterByHost(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "filename", Value: "access.log"}} - c.Request = httptest.NewRequest("GET", "/logs/access.log?host=example.com", nil) + c.Request = httptest.NewRequest("GET", "/logs/access.log?host=example.com", http.NoBody) h.Read(c) @@ -99,7 +100,7 @@ func TestLogsHandler_Read_FilterByLevel(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "filename", Value: "access.log"}} - c.Request = httptest.NewRequest("GET", "/logs/access.log?level=error", nil) + c.Request = httptest.NewRequest("GET", "/logs/access.log?level=error", http.NoBody) h.Read(c) @@ -129,7 +130,7 @@ func TestLogsHandler_Read_FilterByStatus(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "filename", Value: "access.log"}} - c.Request = httptest.NewRequest("GET", "/logs/access.log?status=500", nil) + c.Request = httptest.NewRequest("GET", "/logs/access.log?status=500", http.NoBody) h.Read(c) @@ -159,7 +160,7 @@ func TestLogsHandler_Read_SortAsc(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "filename", Value: "access.log"}} - c.Request = httptest.NewRequest("GET", "/logs/access.log?sort=asc", nil) + c.Request = httptest.NewRequest("GET", "/logs/access.log?sort=asc", http.NoBody) h.Read(c) @@ -185,7 +186,7 @@ func TestLogsHandler_List_DirectoryIsFile(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest("GET", "/logs", nil) + c.Request = httptest.NewRequest("GET", "/logs", http.NoBody) h.List(c) diff --git a/backend/internal/api/handlers/logs_handler_test.go b/backend/internal/api/handlers/logs_handler_test.go index b88dea78..8ebf8d53 100644 --- a/backend/internal/api/handlers/logs_handler_test.go +++ b/backend/internal/api/handlers/logs_handler_test.go @@ -26,24 +26,24 @@ func setupLogsTest(t *testing.T) (*gin.Engine, *services.LogService, string) { // It derives it from cfg.DatabasePath dataDir := filepath.Join(tmpDir, "data") - err = os.MkdirAll(dataDir, 0755) + err = os.MkdirAll(dataDir, 0o755) require.NoError(t, err) dbPath := filepath.Join(dataDir, "charon.db") // Create logs dir logsDir := filepath.Join(dataDir, "logs") - err = os.MkdirAll(logsDir, 0755) + err = os.MkdirAll(logsDir, 0o755) require.NoError(t, err) // Create dummy log files with JSON content log1 := `{"level":"info","ts":1600000000,"msg":"request handled","request":{"method":"GET","host":"example.com","uri":"/","remote_ip":"1.2.3.4"},"status":200}` log2 := `{"level":"error","ts":1600000060,"msg":"error handled","request":{"method":"POST","host":"api.example.com","uri":"/submit","remote_ip":"5.6.7.8"},"status":500}` - err = os.WriteFile(filepath.Join(logsDir, "access.log"), []byte(log1+"\n"+log2+"\n"), 0644) + err = os.WriteFile(filepath.Join(logsDir, "access.log"), []byte(log1+"\n"+log2+"\n"), 0o644) require.NoError(t, err) // Write a charon.log and create a cpmp.log symlink to it for backward compatibility (cpmp is legacy) - err = os.WriteFile(filepath.Join(logsDir, "charon.log"), []byte("app log line 1\napp log line 2"), 0644) + err = os.WriteFile(filepath.Join(logsDir, "charon.log"), []byte("app log line 1\napp log line 2"), 0o644) require.NoError(t, err) // Create legacy cpmp log symlink (cpmp is a legacy name for Charon) _ = os.Symlink(filepath.Join(logsDir, "charon.log"), filepath.Join(logsDir, "cpmp.log")) @@ -72,7 +72,7 @@ func TestLogsLifecycle(t *testing.T) { defer os.RemoveAll(tmpDir) // 1. List logs - req := httptest.NewRequest(http.MethodGet, "/api/v1/logs", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/logs", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) @@ -93,7 +93,7 @@ func TestLogsLifecycle(t *testing.T) { require.True(t, found) // 2. Read log - req = httptest.NewRequest(http.MethodGet, "/api/v1/logs/access.log?limit=2", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/logs/access.log?limit=2", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) @@ -108,27 +108,27 @@ func TestLogsLifecycle(t *testing.T) { require.Len(t, content.Logs, 2) // 3. Download log - req = httptest.NewRequest(http.MethodGet, "/api/v1/logs/access.log/download", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/logs/access.log/download", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) require.Contains(t, resp.Body.String(), "request handled") // 4. Read non-existent log - req = httptest.NewRequest(http.MethodGet, "/api/v1/logs/missing.log", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/logs/missing.log", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusNotFound, resp.Code) // 5. Download non-existent log - req = httptest.NewRequest(http.MethodGet, "/api/v1/logs/missing.log/download", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/logs/missing.log/download", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusNotFound, resp.Code) // 6. List logs error (delete directory) os.RemoveAll(filepath.Join(tmpDir, "data", "logs")) - req = httptest.NewRequest(http.MethodGet, "/api/v1/logs", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/logs", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) // ListLogs returns empty list if dir doesn't exist, so it should be 200 OK with empty list diff --git a/backend/internal/api/handlers/misc_coverage_test.go b/backend/internal/api/handlers/misc_coverage_test.go index a515712b..a9684ba8 100644 --- a/backend/internal/api/handlers/misc_coverage_test.go +++ b/backend/internal/api/handlers/misc_coverage_test.go @@ -3,6 +3,7 @@ package handlers import ( "bytes" "encoding/json" + "net/http" "net/http/httptest" "testing" @@ -112,7 +113,7 @@ func TestRemoteServerHandler_List_Error(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest("GET", "/remote-servers", nil) + c.Request = httptest.NewRequest("GET", "/remote-servers", http.NoBody) h.List(c) @@ -131,7 +132,7 @@ func TestRemoteServerHandler_List_EnabledOnly(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest("GET", "/remote-servers?enabled=true", nil) + c.Request = httptest.NewRequest("GET", "/remote-servers?enabled=true", http.NoBody) h.List(c) @@ -267,7 +268,7 @@ func TestUptimeHandler_GetHistory_Error(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: "test-id"}} - c.Request = httptest.NewRequest("GET", "/uptime/test-id/history", nil) + c.Request = httptest.NewRequest("GET", "/uptime/test-id/history", http.NoBody) h.GetHistory(c) diff --git a/backend/internal/api/handlers/notification_coverage_test.go b/backend/internal/api/handlers/notification_coverage_test.go index 2c26b5b8..8c6d2e03 100644 --- a/backend/internal/api/handlers/notification_coverage_test.go +++ b/backend/internal/api/handlers/notification_coverage_test.go @@ -3,6 +3,7 @@ package handlers import ( "bytes" "encoding/json" + "net/http" "net/http/httptest" "testing" @@ -35,7 +36,7 @@ func TestNotificationHandler_List_Error(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest("GET", "/notifications", nil) + c.Request = httptest.NewRequest("GET", "/notifications", http.NoBody) h.List(c) @@ -55,7 +56,7 @@ func TestNotificationHandler_List_UnreadOnly(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest("GET", "/notifications?unread=true", nil) + c.Request = httptest.NewRequest("GET", "/notifications?unread=true", http.NoBody) h.List(c) diff --git a/backend/internal/api/handlers/notification_handler_test.go b/backend/internal/api/handlers/notification_handler_test.go index 3d78996a..0d602d25 100644 --- a/backend/internal/api/handlers/notification_handler_test.go +++ b/backend/internal/api/handlers/notification_handler_test.go @@ -44,7 +44,7 @@ func TestNotificationHandler_List(t *testing.T) { // Test List All w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/notifications", nil) + req, _ := http.NewRequest("GET", "/notifications", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -55,7 +55,7 @@ func TestNotificationHandler_List(t *testing.T) { // Test List Unread w = httptest.NewRecorder() - req, _ = http.NewRequest("GET", "/notifications?unread=true", nil) + req, _ = http.NewRequest("GET", "/notifications?unread=true", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -79,7 +79,7 @@ func TestNotificationHandler_MarkAsRead(t *testing.T) { router.POST("/notifications/:id/read", handler.MarkAsRead) w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/notifications/"+notif.ID+"/read", nil) + req, _ := http.NewRequest("POST", "/notifications/"+notif.ID+"/read", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -103,7 +103,7 @@ func TestNotificationHandler_MarkAllAsRead(t *testing.T) { router.POST("/notifications/read-all", handler.MarkAllAsRead) w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/notifications/read-all", nil) + req, _ := http.NewRequest("POST", "/notifications/read-all", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -126,7 +126,7 @@ func TestNotificationHandler_MarkAllAsRead_Error(t *testing.T) { sqlDB, _ := db.DB() sqlDB.Close() - req, _ := http.NewRequest("POST", "/notifications/read-all", nil) + req, _ := http.NewRequest("POST", "/notifications/read-all", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusInternalServerError, w.Code) @@ -145,7 +145,7 @@ func TestNotificationHandler_DBError(t *testing.T) { sqlDB, _ := db.DB() sqlDB.Close() - req, _ := http.NewRequest("POST", "/notifications/1/read", nil) + req, _ := http.NewRequest("POST", "/notifications/1/read", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusInternalServerError, w.Code) diff --git a/backend/internal/api/handlers/notification_provider_handler_test.go b/backend/internal/api/handlers/notification_provider_handler_test.go index 8961de0d..30a6bcc8 100644 --- a/backend/internal/api/handlers/notification_provider_handler_test.go +++ b/backend/internal/api/handlers/notification_provider_handler_test.go @@ -61,7 +61,7 @@ func TestNotificationProviderHandler_CRUD(t *testing.T) { assert.NotEmpty(t, created.ID) // 2. List - req, _ = http.NewRequest("GET", "/api/v1/notifications/providers", nil) + req, _ = http.NewRequest("GET", "/api/v1/notifications/providers", http.NoBody) w = httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -88,7 +88,7 @@ func TestNotificationProviderHandler_CRUD(t *testing.T) { assert.Equal(t, "Updated Discord", dbProvider.Name) // 4. Delete - req, _ = http.NewRequest("DELETE", "/api/v1/notifications/providers/"+created.ID, nil) + req, _ = http.NewRequest("DELETE", "/api/v1/notifications/providers/"+created.ID, http.NoBody) w = httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -102,7 +102,7 @@ func TestNotificationProviderHandler_CRUD(t *testing.T) { func TestNotificationProviderHandler_Templates(t *testing.T) { r, _ := setupNotificationProviderTest(t) - req, _ := http.NewRequest("GET", "/api/v1/notifications/templates", nil) + req, _ := http.NewRequest("GET", "/api/v1/notifications/templates", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) diff --git a/backend/internal/api/handlers/notification_template_handler_test.go b/backend/internal/api/handlers/notification_template_handler_test.go index 13b51e0e..5a0adfd1 100644 --- a/backend/internal/api/handlers/notification_template_handler_test.go +++ b/backend/internal/api/handlers/notification_template_handler_test.go @@ -45,7 +45,7 @@ func TestNotificationTemplateHandler_CRUDAndPreview(t *testing.T) { require.NotEmpty(t, created.ID) // List - req = httptest.NewRequest(http.MethodGet, "/api/v1/notifications/templates", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/notifications/templates", http.NoBody) w = httptest.NewRecorder() r.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code) @@ -76,7 +76,7 @@ func TestNotificationTemplateHandler_CRUDAndPreview(t *testing.T) { require.NotEmpty(t, previewResp["rendered"]) // Delete - req = httptest.NewRequest(http.MethodDelete, "/api/v1/notifications/templates/"+created.ID, nil) + req = httptest.NewRequest(http.MethodDelete, "/api/v1/notifications/templates/"+created.ID, http.NoBody) w = httptest.NewRecorder() r.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code) diff --git a/backend/internal/api/handlers/perf_assert_test.go b/backend/internal/api/handlers/perf_assert_test.go index 49d5cd9a..678f34e5 100644 --- a/backend/internal/api/handlers/perf_assert_test.go +++ b/backend/internal/api/handlers/perf_assert_test.go @@ -33,20 +33,7 @@ func setupPerfDB(t *testing.T) *gorm.DB { } // thresholdFromEnv loads threshold from environment var as milliseconds -func thresholdFromEnv(envKey string, defaultMs float64) float64 { - if v := os.Getenv(envKey); v != "" { - // try parse as float - if parsed, err := time.ParseDuration(v); err == nil { - return ms(parsed) - } - // fallback try parse as number ms - var f float64 - if _, err := fmt.Sscanf(v, "%f", &f); err == nil { - return f - } - } - return defaultMs -} +// thresholdFromEnv removed — tests use inline environment parsing for clarity. // gatherStats runs the request counts times and returns durations ms func gatherStats(t *testing.T, req *http.Request, router http.Handler, counts int) []float64 { @@ -66,7 +53,7 @@ func gatherStats(t *testing.T, req *http.Request, router http.Handler, counts in } // computePercentiles returns avg, p50, p95, p99, max -func computePercentiles(samples []float64) (avg, p50, p95, p99, max float64) { +func computePercentiles(samples []float64) (avg, p50, p95, p99, maxVal float64) { sort.Float64s(samples) var sum float64 for _, s := range samples { @@ -86,15 +73,11 @@ func computePercentiles(samples []float64) (avg, p50, p95, p99, max float64) { p50 = p(0.50) p95 = p(0.95) p99 = p(0.99) - max = samples[len(samples)-1] + maxVal = samples[len(samples)-1] return } -func perfLogStats(t *testing.T, title string, samples []float64) { - av, p50, p95, p99, max := computePercentiles(samples) - t.Logf("%s - avg=%.3fms p50=%.3fms p95=%.3fms p99=%.3fms max=%.3fms", title, av, p50, p95, p99, max) - // no assert by default, individual tests decide how to fail -} +// perfLogStats removed — tests log stats inline where helpful. func TestPerf_GetStatus_AssertThreshold(t *testing.T) { gin.SetMode(gin.ReleaseMode) @@ -110,9 +93,9 @@ func TestPerf_GetStatus_AssertThreshold(t *testing.T) { router.GET("/api/v1/security/status", h.GetStatus) counts := 500 - req := httptest.NewRequest("GET", "/api/v1/security/status", nil) + req := httptest.NewRequest("GET", "/api/v1/security/status", http.NoBody) samples := gatherStats(t, req, router, counts) - avg, _, p95, _, max := computePercentiles(samples) + avg, _, p95, _, maxVal := computePercentiles(samples) // default thresholds ms thresholdP95 := 2.0 // 2ms per request if env := os.Getenv("PERF_MAX_MS_GETSTATUS_P95"); env != "" { @@ -121,7 +104,7 @@ func TestPerf_GetStatus_AssertThreshold(t *testing.T) { } } // fail if p95 exceeds threshold - t.Logf("GetStatus avg=%.3fms p95=%.3fms max=%.3fms", avg, p95, max) + t.Logf("GetStatus avg=%.3fms p95=%.3fms max=%.3fms", avg, p95, maxVal) if p95 > thresholdP95 { t.Fatalf("GetStatus P95 (%.3fms) exceeds threshold %.3fms", p95, thresholdP95) } @@ -140,7 +123,7 @@ func TestPerf_GetStatus_Parallel_AssertThreshold(t *testing.T) { samples := make(chan float64, n) var worker = func() { for i := 0; i < n; i++ { - req := httptest.NewRequest("GET", "/api/v1/security/status", nil) + req := httptest.NewRequest("GET", "/api/v1/security/status", http.NoBody) w := httptest.NewRecorder() s := time.Now() router.ServeHTTP(w, req) @@ -157,14 +140,14 @@ func TestPerf_GetStatus_Parallel_AssertThreshold(t *testing.T) { for i := 0; i < n*4; i++ { collected = append(collected, <-samples) } - avg, _, p95, _, max := computePercentiles(collected) + avg, _, p95, _, maxVal := computePercentiles(collected) thresholdP95 := 5.0 // 5ms default if env := os.Getenv("PERF_MAX_MS_GETSTATUS_P95_PARALLEL"); env != "" { if parsed, err := time.ParseDuration(env); err == nil { thresholdP95 = ms(parsed) } } - t.Logf("GetStatus Parallel avg=%.3fms p95=%.3fms max=%.3fms", avg, p95, max) + t.Logf("GetStatus Parallel avg=%.3fms p95=%.3fms max=%.3fms", avg, p95, maxVal) if p95 > thresholdP95 { t.Fatalf("GetStatus Parallel P95 (%.3fms) exceeds threshold %.3fms", p95, thresholdP95) } @@ -184,16 +167,16 @@ func TestPerf_ListDecisions_AssertThreshold(t *testing.T) { router.GET("/api/v1/security/decisions", h.ListDecisions) counts := 200 - req := httptest.NewRequest("GET", "/api/v1/security/decisions?limit=50", nil) + req := httptest.NewRequest("GET", "/api/v1/security/decisions?limit=50", http.NoBody) samples := gatherStats(t, req, router, counts) - avg, _, p95, _, max := computePercentiles(samples) + avg, _, p95, _, maxVal := computePercentiles(samples) thresholdP95 := 30.0 // 30ms default if env := os.Getenv("PERF_MAX_MS_LISTDECISIONS_P95"); env != "" { if parsed, err := time.ParseDuration(env); err == nil { thresholdP95 = ms(parsed) } } - t.Logf("ListDecisions avg=%.3fms p95=%.3fms max=%.3fms", avg, p95, max) + t.Logf("ListDecisions avg=%.3fms p95=%.3fms max=%.3fms", avg, p95, maxVal) if p95 > thresholdP95 { t.Fatalf("ListDecisions P95 (%.3fms) exceeds threshold %.3fms", p95, thresholdP95) } diff --git a/backend/internal/api/handlers/proxy_host_handler.go b/backend/internal/api/handlers/proxy_host_handler.go index 9807c820..10c949ef 100644 --- a/backend/internal/api/handlers/proxy_host_handler.go +++ b/backend/internal/api/handlers/proxy_host_handler.go @@ -125,9 +125,9 @@ func (h *ProxyHostHandler) Create(c *gin.Context) { // Get retrieves a proxy host by UUID. func (h *ProxyHostHandler) Get(c *gin.Context) { - uuid := c.Param("uuid") + uuidStr := c.Param("uuid") - host, err := h.service.GetByUUID(uuid) + host, err := h.service.GetByUUID(uuidStr) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "proxy host not found"}) return @@ -310,9 +310,9 @@ func (h *ProxyHostHandler) Update(c *gin.Context) { // Delete removes a proxy host. func (h *ProxyHostHandler) Delete(c *gin.Context) { - uuid := c.Param("uuid") + uuidStr := c.Param("uuid") - host, err := h.service.GetByUUID(uuid) + host, err := h.service.GetByUUID(uuidStr) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "proxy host not found"}) return @@ -399,11 +399,11 @@ func (h *ProxyHostHandler) BulkUpdateACL(c *gin.Context) { updated := 0 errors := []map[string]string{} - for _, uuid := range req.HostUUIDs { - host, err := h.service.GetByUUID(uuid) + for _, hostUUID := range req.HostUUIDs { + host, err := h.service.GetByUUID(hostUUID) if err != nil { errors = append(errors, map[string]string{ - "uuid": uuid, + "uuid": hostUUID, "error": "proxy host not found", }) continue @@ -412,7 +412,7 @@ func (h *ProxyHostHandler) BulkUpdateACL(c *gin.Context) { host.AccessListID = req.AccessListID if err := h.service.Update(host); err != nil { errors = append(errors, map[string]string{ - "uuid": uuid, + "uuid": hostUUID, "error": err.Error(), }) continue diff --git a/backend/internal/api/handlers/proxy_host_handler_test.go b/backend/internal/api/handlers/proxy_host_handler_test.go index a5510d52..dc0ddc97 100644 --- a/backend/internal/api/handlers/proxy_host_handler_test.go +++ b/backend/internal/api/handlers/proxy_host_handler_test.go @@ -59,7 +59,7 @@ func TestProxyHostLifecycle(t *testing.T) { require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &created)) require.Equal(t, "media.example.com", created.DomainNames) - listReq := httptest.NewRequest(http.MethodGet, "/api/v1/proxy-hosts", nil) + listReq := httptest.NewRequest(http.MethodGet, "/api/v1/proxy-hosts", http.NoBody) listResp := httptest.NewRecorder() router.ServeHTTP(listResp, listReq) require.Equal(t, http.StatusOK, listResp.Code) @@ -69,7 +69,7 @@ func TestProxyHostLifecycle(t *testing.T) { require.Len(t, hosts, 1) // Get by ID - getReq := httptest.NewRequest(http.MethodGet, "/api/v1/proxy-hosts/"+created.UUID, nil) + getReq := httptest.NewRequest(http.MethodGet, "/api/v1/proxy-hosts/"+created.UUID, http.NoBody) getResp := httptest.NewRecorder() router.ServeHTTP(getResp, getReq) require.Equal(t, http.StatusOK, getResp.Code) @@ -92,13 +92,13 @@ func TestProxyHostLifecycle(t *testing.T) { require.False(t, updated.Enabled) // Delete - delReq := httptest.NewRequest(http.MethodDelete, "/api/v1/proxy-hosts/"+created.UUID, nil) + delReq := httptest.NewRequest(http.MethodDelete, "/api/v1/proxy-hosts/"+created.UUID, http.NoBody) delResp := httptest.NewRecorder() router.ServeHTTP(delResp, delReq) require.Equal(t, http.StatusOK, delResp.Code) // Verify Delete - getReq2 := httptest.NewRequest(http.MethodGet, "/api/v1/proxy-hosts/"+created.UUID, nil) + getReq2 := httptest.NewRequest(http.MethodGet, "/api/v1/proxy-hosts/"+created.UUID, http.NoBody) getResp2 := httptest.NewRecorder() router.ServeHTTP(getResp2, getReq2) require.Equal(t, http.StatusNotFound, getResp2.Code) @@ -131,7 +131,7 @@ func TestProxyHostDelete_WithUptimeCleanup(t *testing.T) { require.Equal(t, int64(1), count) // Delete host with delete_uptime=true - req := httptest.NewRequest(http.MethodDelete, "/api/v1/proxy-hosts/"+host.UUID+"?delete_uptime=true", nil) + req := httptest.NewRequest(http.MethodDelete, "/api/v1/proxy-hosts/"+host.UUID+"?delete_uptime=true", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code) @@ -198,7 +198,7 @@ func TestProxyHostErrors(t *testing.T) { db.Create(&host) // Test Get - Not Found - req = httptest.NewRequest(http.MethodGet, "/api/v1/proxy-hosts/non-existent-uuid", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/proxy-hosts/non-existent-uuid", http.NoBody) resp = httptest.NewRecorder() r.ServeHTTP(resp, req) require.Equal(t, http.StatusNotFound, resp.Code) @@ -226,13 +226,13 @@ func TestProxyHostErrors(t *testing.T) { require.Equal(t, http.StatusInternalServerError, resp.Code) // Test Delete - Not Found - req = httptest.NewRequest(http.MethodDelete, "/api/v1/proxy-hosts/non-existent-uuid", nil) + req = httptest.NewRequest(http.MethodDelete, "/api/v1/proxy-hosts/non-existent-uuid", http.NoBody) resp = httptest.NewRecorder() r.ServeHTTP(resp, req) require.Equal(t, http.StatusNotFound, resp.Code) // Test Delete - Apply Config Error - req = httptest.NewRequest(http.MethodDelete, "/api/v1/proxy-hosts/"+host.UUID, nil) + req = httptest.NewRequest(http.MethodDelete, "/api/v1/proxy-hosts/"+host.UUID, http.NoBody) resp = httptest.NewRecorder() r.ServeHTTP(resp, req) require.Equal(t, http.StatusInternalServerError, resp.Code) @@ -408,7 +408,7 @@ func TestProxyHostHandler_List_Error(t *testing.T) { sqlDB, _ := db.DB() sqlDB.Close() - req := httptest.NewRequest(http.MethodGet, "/api/v1/proxy-hosts", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/proxy-hosts", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusInternalServerError, resp.Code) @@ -465,7 +465,7 @@ func TestProxyHostWithCaddyIntegration(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) // Test Delete with Caddy Sync - req = httptest.NewRequest(http.MethodDelete, "/api/v1/proxy-hosts/"+createdHost.UUID, nil) + req = httptest.NewRequest(http.MethodDelete, "/api/v1/proxy-hosts/"+createdHost.UUID, http.NoBody) resp = httptest.NewRecorder() r.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) diff --git a/backend/internal/api/handlers/remote_server_handler.go b/backend/internal/api/handlers/remote_server_handler.go index 2518504f..b1831500 100644 --- a/backend/internal/api/handlers/remote_server_handler.go +++ b/backend/internal/api/handlers/remote_server_handler.go @@ -9,6 +9,7 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" + "github.com/Wikid82/charon/backend/internal/logger" "github.com/Wikid82/charon/backend/internal/models" "github.com/Wikid82/charon/backend/internal/services" "github.com/Wikid82/charon/backend/internal/util" @@ -87,9 +88,9 @@ func (h *RemoteServerHandler) Create(c *gin.Context) { // Get retrieves a remote server by UUID. func (h *RemoteServerHandler) Get(c *gin.Context) { - uuid := c.Param("uuid") + uuidStr := c.Param("uuid") - server, err := h.service.GetByUUID(uuid) + server, err := h.service.GetByUUID(uuidStr) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "server not found"}) return @@ -100,9 +101,9 @@ func (h *RemoteServerHandler) Get(c *gin.Context) { // Update updates an existing remote server. func (h *RemoteServerHandler) Update(c *gin.Context) { - uuid := c.Param("uuid") + uuidStr := c.Param("uuid") - server, err := h.service.GetByUUID(uuid) + server, err := h.service.GetByUUID(uuidStr) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "server not found"}) return @@ -123,9 +124,9 @@ func (h *RemoteServerHandler) Update(c *gin.Context) { // Delete removes a remote server. func (h *RemoteServerHandler) Delete(c *gin.Context) { - uuid := c.Param("uuid") + uuidStr := c.Param("uuid") - server, err := h.service.GetByUUID(uuid) + server, err := h.service.GetByUUID(uuidStr) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "server not found"}) return @@ -154,9 +155,9 @@ func (h *RemoteServerHandler) Delete(c *gin.Context) { // TestConnection tests the TCP connection to a remote server. func (h *RemoteServerHandler) TestConnection(c *gin.Context) { - uuid := c.Param("uuid") + uuidStr := c.Param("uuid") - server, err := h.service.GetByUUID(uuid) + server, err := h.service.GetByUUID(uuidStr) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "server not found"}) return @@ -185,7 +186,11 @@ func (h *RemoteServerHandler) TestConnection(c *gin.Context) { c.JSON(http.StatusOK, result) return } - defer func() { _ = conn.Close() }() + defer func() { + if err := conn.Close(); err != nil { + logger.Log().WithError(err).Warn("failed to close tcp connection") + } + }() // Connection successful result["reachable"] = true @@ -228,7 +233,11 @@ func (h *RemoteServerHandler) TestConnectionCustom(c *gin.Context) { c.JSON(http.StatusOK, result) return } - defer func() { _ = conn.Close() }() + defer func() { + if err := conn.Close(); err != nil { + logger.Log().WithError(err).Warn("failed to close tcp connection") + } + }() // Connection successful result["reachable"] = true diff --git a/backend/internal/api/handlers/remote_server_handler_test.go b/backend/internal/api/handlers/remote_server_handler_test.go index e2250987..5cf501dc 100644 --- a/backend/internal/api/handlers/remote_server_handler_test.go +++ b/backend/internal/api/handlers/remote_server_handler_test.go @@ -84,13 +84,13 @@ func TestRemoteServerHandler_FullCRUD(t *testing.T) { assert.NotEmpty(t, created.UUID) // List - req, _ = http.NewRequest("GET", "/api/v1/remote-servers", nil) + req, _ = http.NewRequest("GET", "/api/v1/remote-servers", http.NoBody) w = httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) // Get - req, _ = http.NewRequest("GET", "/api/v1/remote-servers/"+created.UUID, nil) + req, _ = http.NewRequest("GET", "/api/v1/remote-servers/"+created.UUID, http.NoBody) w = httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -104,7 +104,7 @@ func TestRemoteServerHandler_FullCRUD(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) // Delete - req, _ = http.NewRequest("DELETE", "/api/v1/remote-servers/"+created.UUID, nil) + req, _ = http.NewRequest("DELETE", "/api/v1/remote-servers/"+created.UUID, http.NoBody) w = httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusNoContent, w.Code) @@ -122,7 +122,7 @@ func TestRemoteServerHandler_FullCRUD(t *testing.T) { assert.Equal(t, http.StatusNotFound, w.Code) // Delete - Not Found - req, _ = http.NewRequest("DELETE", "/api/v1/remote-servers/non-existent-uuid", nil) + req, _ = http.NewRequest("DELETE", "/api/v1/remote-servers/non-existent-uuid", http.NoBody) w = httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) diff --git a/backend/internal/api/handlers/security_handler_additional_test.go b/backend/internal/api/handlers/security_handler_additional_test.go index a9fae5c6..92d195f2 100644 --- a/backend/internal/api/handlers/security_handler_additional_test.go +++ b/backend/internal/api/handlers/security_handler_additional_test.go @@ -29,7 +29,7 @@ func TestSecurityHandler_GetConfigAndUpdateConfig(t *testing.T) { // Create a gin test context for GetConfig when no config exists w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - req := httptest.NewRequest("GET", "/security/config", nil) + req := httptest.NewRequest("GET", "/security/config", http.NoBody) c.Request = req h.GetConfig(c) require.Equal(t, http.StatusOK, w.Code) @@ -53,7 +53,7 @@ func TestSecurityHandler_GetConfigAndUpdateConfig(t *testing.T) { // Now call GetConfig again and ensure config is returned w = httptest.NewRecorder() c, _ = gin.CreateTestContext(w) - req = httptest.NewRequest("GET", "/security/config", nil) + req = httptest.NewRequest("GET", "/security/config", http.NoBody) c.Request = req h.GetConfig(c) require.Equal(t, http.StatusOK, w.Code) diff --git a/backend/internal/api/handlers/security_handler_audit_test.go b/backend/internal/api/handlers/security_handler_audit_test.go index cd0896d7..b969cc86 100644 --- a/backend/internal/api/handlers/security_handler_audit_test.go +++ b/backend/internal/api/handlers/security_handler_audit_test.go @@ -65,7 +65,7 @@ func TestSecurityHandler_GetStatus_SQLInjection(t *testing.T) { router := gin.New() router.GET("/api/v1/security/status", h.GetStatus) - req := httptest.NewRequest("GET", "/api/v1/security/status", nil) + req := httptest.NewRequest("GET", "/api/v1/security/status", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) @@ -248,7 +248,7 @@ func TestSecurityHandler_GetStatus_SettingsOverride(t *testing.T) { router := gin.New() router.GET("/api/v1/security/status", h.GetStatus) - req := httptest.NewRequest("GET", "/api/v1/security/status", nil) + req := httptest.NewRequest("GET", "/api/v1/security/status", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) @@ -293,7 +293,7 @@ func TestSecurityHandler_GetStatus_DisabledViaSettings(t *testing.T) { router := gin.New() router.GET("/api/v1/security/status", h.GetStatus) - req := httptest.NewRequest("GET", "/api/v1/security/status", nil) + req := httptest.NewRequest("GET", "/api/v1/security/status", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) @@ -342,7 +342,7 @@ func TestSecurityAudit_DeleteRuleSet_InvalidID(t *testing.T) { if tc.id == "" { url = "/api/v1/security/rulesets/" } - req := httptest.NewRequest("DELETE", url, nil) + req := httptest.NewRequest("DELETE", url, http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) @@ -383,7 +383,7 @@ func TestSecurityHandler_UpsertRuleSet_XSSInContent(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) // Verify it's stored and returned as JSON (not rendered as HTML) - req2 := httptest.NewRequest("GET", "/api/v1/security/rulesets", nil) + req2 := httptest.NewRequest("GET", "/api/v1/security/rulesets", http.NoBody) w2 := httptest.NewRecorder() router.ServeHTTP(w2, req2) @@ -468,7 +468,7 @@ func TestSecurityHandler_GetStatus_NilDB(t *testing.T) { router := gin.New() router.GET("/api/v1/security/status", h.GetStatus) - req := httptest.NewRequest("GET", "/api/v1/security/status", nil) + req := httptest.NewRequest("GET", "/api/v1/security/status", http.NoBody) w := httptest.NewRecorder() // Should not panic @@ -498,7 +498,7 @@ func TestSecurityHandler_Enable_WithoutWhitelist(t *testing.T) { router.POST("/api/v1/security/enable", h.Enable) // Try to enable without token or whitelist - req := httptest.NewRequest("POST", "/api/v1/security/enable", nil) + req := httptest.NewRequest("POST", "/api/v1/security/enable", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) @@ -525,7 +525,7 @@ func TestSecurityHandler_Disable_RequiresToken(t *testing.T) { router.POST("/api/v1/security/disable", h.Disable) // Try to disable from non-localhost without token - req := httptest.NewRequest("POST", "/api/v1/security/disable", nil) + req := httptest.NewRequest("POST", "/api/v1/security/disable", http.NoBody) req.RemoteAddr = "10.0.0.5:12345" w := httptest.NewRecorder() router.ServeHTTP(w, req) @@ -560,7 +560,7 @@ func TestSecurityHandler_GetStatus_CrowdSecModeValidation(t *testing.T) { router := gin.New() router.GET("/api/v1/security/status", h.GetStatus) - req := httptest.NewRequest("GET", "/api/v1/security/status", nil) + req := httptest.NewRequest("GET", "/api/v1/security/status", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) diff --git a/backend/internal/api/handlers/security_handler_clean_test.go b/backend/internal/api/handlers/security_handler_clean_test.go index 760bb800..e494884a 100644 --- a/backend/internal/api/handlers/security_handler_clean_test.go +++ b/backend/internal/api/handlers/security_handler_clean_test.go @@ -46,7 +46,7 @@ func TestSecurityHandler_GetStatus_Clean(t *testing.T) { router.GET("/security/status", handler.GetStatus) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/security/status", nil) + req, _ := http.NewRequest("GET", "/security/status", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -72,7 +72,7 @@ func TestSecurityHandler_Cerberus_DBOverride(t *testing.T) { router.GET("/security/status", handler.GetStatus) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/security/status", nil) + req, _ := http.NewRequest("GET", "/security/status", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -108,7 +108,7 @@ func TestSecurityHandler_ACL_DBOverride(t *testing.T) { router.GET("/security/status", handler.GetStatus) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/security/status", nil) + req, _ := http.NewRequest("GET", "/security/status", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -127,7 +127,7 @@ func TestSecurityHandler_GenerateBreakGlass_ReturnsToken(t *testing.T) { router.POST("/security/breakglass/generate", handler.GenerateBreakGlass) w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/security/breakglass/generate", nil) + req, _ := http.NewRequest("POST", "/security/breakglass/generate", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} @@ -156,7 +156,7 @@ func TestSecurityHandler_ACL_DisabledWhenCerberusOff(t *testing.T) { router.GET("/security/status", handler.GetStatus) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/security/status", nil) + req, _ := http.NewRequest("GET", "/security/status", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -185,7 +185,7 @@ func TestSecurityHandler_CrowdSec_Mode_DBOverride(t *testing.T) { router.GET("/security/status", handler.GetStatus) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/security/status", nil) + req, _ := http.NewRequest("GET", "/security/status", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -209,7 +209,7 @@ func TestSecurityHandler_CrowdSec_ExternalMappedToDisabled_DBOverride(t *testing router.GET("/security/status", handler.GetStatus) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/security/status", nil) + req, _ := http.NewRequest("GET", "/security/status", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} @@ -233,7 +233,7 @@ func TestSecurityHandler_ExternalModeMappedToDisabled(t *testing.T) { router.GET("/security/status", handler.GetStatus) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/security/status", nil) + req, _ := http.NewRequest("GET", "/security/status", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} @@ -279,7 +279,7 @@ func TestSecurityHandler_Enable_Disable_WithAdminWhitelistAndToken(t *testing.T) assert.Equal(t, http.StatusOK, resp.Code) // Generate break-glass token - req = httptest.NewRequest("POST", "/api/v1/security/breakglass/generate", nil) + req = httptest.NewRequest("POST", "/api/v1/security/breakglass/generate", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) assert.Equal(t, http.StatusOK, resp.Code) diff --git a/backend/internal/api/handlers/security_handler_coverage_test.go b/backend/internal/api/handlers/security_handler_coverage_test.go index 613c07be..7959599a 100644 --- a/backend/internal/api/handlers/security_handler_coverage_test.go +++ b/backend/internal/api/handlers/security_handler_coverage_test.go @@ -102,7 +102,7 @@ func TestSecurityHandler_GetConfig_Success(t *testing.T) { router.GET("/security/config", handler.GetConfig) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/security/config", nil) + req, _ := http.NewRequest("GET", "/security/config", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -122,7 +122,7 @@ func TestSecurityHandler_GetConfig_NotFound(t *testing.T) { router.GET("/security/config", handler.GetConfig) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/security/config", nil) + req, _ := http.NewRequest("GET", "/security/config", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -147,7 +147,7 @@ func TestSecurityHandler_ListDecisions_Success(t *testing.T) { router.GET("/security/decisions", handler.ListDecisions) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/security/decisions", nil) + req, _ := http.NewRequest("GET", "/security/decisions", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -173,7 +173,7 @@ func TestSecurityHandler_ListDecisions_WithLimit(t *testing.T) { router.GET("/security/decisions", handler.ListDecisions) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/security/decisions?limit=2", nil) + req, _ := http.NewRequest("GET", "/security/decisions?limit=2", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -286,7 +286,7 @@ func TestSecurityHandler_ListRuleSets_Success(t *testing.T) { router.GET("/security/rulesets", handler.ListRuleSets) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/security/rulesets", nil) + req, _ := http.NewRequest("GET", "/security/rulesets", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -377,7 +377,7 @@ func TestSecurityHandler_DeleteRuleSet_Success(t *testing.T) { router.DELETE("/security/rulesets/:id", handler.DeleteRuleSet) w := httptest.NewRecorder() - req, _ := http.NewRequest("DELETE", "/security/rulesets/1", nil) + req, _ := http.NewRequest("DELETE", "/security/rulesets/1", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -397,7 +397,7 @@ func TestSecurityHandler_DeleteRuleSet_NotFound(t *testing.T) { router.DELETE("/security/rulesets/:id", handler.DeleteRuleSet) w := httptest.NewRecorder() - req, _ := http.NewRequest("DELETE", "/security/rulesets/999", nil) + req, _ := http.NewRequest("DELETE", "/security/rulesets/999", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) @@ -413,7 +413,7 @@ func TestSecurityHandler_DeleteRuleSet_InvalidID(t *testing.T) { router.DELETE("/security/rulesets/:id", handler.DeleteRuleSet) w := httptest.NewRecorder() - req, _ := http.NewRequest("DELETE", "/security/rulesets/invalid", nil) + req, _ := http.NewRequest("DELETE", "/security/rulesets/invalid", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) @@ -431,7 +431,7 @@ func TestSecurityHandler_DeleteRuleSet_EmptyID(t *testing.T) { // This should hit the "id is required" check if we bypass routing w := httptest.NewRecorder() - req, _ := http.NewRequest("DELETE", "/security/rulesets/", nil) + req, _ := http.NewRequest("DELETE", "/security/rulesets/", http.NoBody) router.ServeHTTP(w, req) // Router won't match this path, so 404 @@ -517,7 +517,7 @@ func TestSecurityHandler_Enable_WithValidBreakGlassToken(t *testing.T) { // Generate a break-glass token w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/security/breakglass/generate", nil) + req, _ := http.NewRequest("POST", "/security/breakglass/generate", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -609,7 +609,7 @@ func TestSecurityHandler_Disable_FromRemoteWithToken(t *testing.T) { // Generate token w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/security/breakglass/generate", nil) + req, _ := http.NewRequest("POST", "/security/breakglass/generate", http.NoBody) router.ServeHTTP(w, req) var tokenResp map[string]string json.Unmarshal(w.Body.Bytes(), &tokenResp) @@ -689,7 +689,7 @@ func TestSecurityHandler_GenerateBreakGlass_NoConfig(t *testing.T) { router.POST("/security/breakglass/generate", handler.GenerateBreakGlass) w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/security/breakglass/generate", nil) + req, _ := http.NewRequest("POST", "/security/breakglass/generate", http.NoBody) router.ServeHTTP(w, req) // Should succeed and create a new config with the token diff --git a/backend/internal/api/handlers/security_handler_rules_decisions_test.go b/backend/internal/api/handlers/security_handler_rules_decisions_test.go index 3953891c..0c46954d 100644 --- a/backend/internal/api/handlers/security_handler_rules_decisions_test.go +++ b/backend/internal/api/handlers/security_handler_rules_decisions_test.go @@ -57,7 +57,7 @@ func TestSecurityHandler_CreateAndListDecisionAndRulesets(t *testing.T) { require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &decisionResp)) require.NotNil(t, decisionResp["decision"]) - req = httptest.NewRequest(http.MethodGet, "/api/v1/security/decisions?limit=10", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/security/decisions?limit=10", http.NoBody) resp = httptest.NewRecorder() r.ServeHTTP(resp, req) if resp.Code != http.StatusOK { @@ -80,7 +80,7 @@ func TestSecurityHandler_CreateAndListDecisionAndRulesets(t *testing.T) { require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &rsResp)) require.NotNil(t, rsResp["ruleset"]) - req = httptest.NewRequest(http.MethodGet, "/api/v1/security/rulesets", nil) + req = httptest.NewRequest(http.MethodGet, "/api/v1/security/rulesets", http.NoBody) resp = httptest.NewRecorder() r.ServeHTTP(resp, req) if resp.Code != http.StatusOK { @@ -94,7 +94,7 @@ func TestSecurityHandler_CreateAndListDecisionAndRulesets(t *testing.T) { idFloat, ok := listRsResp["rulesets"][0]["id"].(float64) require.True(t, ok) id := int(idFloat) - req = httptest.NewRequest(http.MethodDelete, "/api/v1/security/rulesets/"+strconv.Itoa(id), nil) + req = httptest.NewRequest(http.MethodDelete, "/api/v1/security/rulesets/"+strconv.Itoa(id), http.NoBody) resp = httptest.NewRecorder() r.ServeHTTP(resp, req) assert.Equal(t, http.StatusOK, resp.Code) @@ -159,7 +159,7 @@ func TestSecurityHandler_UpsertDeleteTriggersApplyConfig(t *testing.T) { // Read ID from DB var rs models.SecurityRuleSet assert.NoError(t, db.First(&rs).Error) - req = httptest.NewRequest(http.MethodDelete, "/api/v1/security/rulesets/"+strconv.Itoa(int(rs.ID)), nil) + req = httptest.NewRequest(http.MethodDelete, "/api/v1/security/rulesets/"+strconv.Itoa(int(rs.ID)), http.NoBody) resp = httptest.NewRecorder() r.ServeHTTP(resp, req) assert.Equal(t, http.StatusOK, resp.Code) diff --git a/backend/internal/api/handlers/security_handler_settings_test.go b/backend/internal/api/handlers/security_handler_settings_test.go index 013f5670..e48f7ff2 100644 --- a/backend/internal/api/handlers/security_handler_settings_test.go +++ b/backend/internal/api/handlers/security_handler_settings_test.go @@ -131,7 +131,7 @@ func TestSecurityHandler_GetStatus_RespectsSettingsTable(t *testing.T) { router.GET("/security/status", handler.GetStatus) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/security/status", nil) + req, _ := http.NewRequest("GET", "/security/status", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -173,7 +173,7 @@ func TestSecurityHandler_GetStatus_WAFModeFromSettings(t *testing.T) { router.GET("/security/status", handler.GetStatus) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/security/status", nil) + req, _ := http.NewRequest("GET", "/security/status", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -205,7 +205,7 @@ func TestSecurityHandler_GetStatus_RateLimitModeFromSettings(t *testing.T) { router.GET("/security/status", handler.GetStatus) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/security/status", nil) + req, _ := http.NewRequest("GET", "/security/status", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) diff --git a/backend/internal/api/handlers/security_handler_test_fixed.go b/backend/internal/api/handlers/security_handler_test_fixed.go index aaf1694a..23bf1efb 100644 --- a/backend/internal/api/handlers/security_handler_test_fixed.go +++ b/backend/internal/api/handlers/security_handler_test_fixed.go @@ -90,7 +90,7 @@ func TestSecurityHandler_GetStatus_Fixed(t *testing.T) { router.GET("/security/status", handler.GetStatus) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/security/status", nil) + req, _ := http.NewRequest("GET", "/security/status", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, tt.expectedStatus, w.Code) diff --git a/backend/internal/api/handlers/settings_handler_test.go b/backend/internal/api/handlers/settings_handler_test.go index ba55faf7..e2bf788d 100644 --- a/backend/internal/api/handlers/settings_handler_test.go +++ b/backend/internal/api/handlers/settings_handler_test.go @@ -38,7 +38,7 @@ func TestSettingsHandler_GetSettings(t *testing.T) { router.GET("/settings", handler.GetSettings) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/settings", nil) + req, _ := http.NewRequest("GET", "/settings", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -148,7 +148,7 @@ func TestSettingsHandler_GetSMTPConfig(t *testing.T) { router.GET("/settings/smtp", handler.GetSMTPConfig) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/settings/smtp", nil) + req, _ := http.NewRequest("GET", "/settings/smtp", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -169,7 +169,7 @@ func TestSettingsHandler_GetSMTPConfig_Empty(t *testing.T) { router.GET("/settings/smtp", handler.GetSMTPConfig) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/settings/smtp", nil) + req, _ := http.NewRequest("GET", "/settings/smtp", http.NoBody) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -303,7 +303,7 @@ func TestSettingsHandler_TestSMTPConfig_NonAdmin(t *testing.T) { }) router.POST("/settings/smtp/test", handler.TestSMTPConfig) - req, _ := http.NewRequest("POST", "/settings/smtp/test", nil) + req, _ := http.NewRequest("POST", "/settings/smtp/test", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) @@ -321,7 +321,7 @@ func TestSettingsHandler_TestSMTPConfig_NotConfigured(t *testing.T) { }) router.POST("/settings/smtp/test", handler.TestSMTPConfig) - req, _ := http.NewRequest("POST", "/settings/smtp/test", nil) + req, _ := http.NewRequest("POST", "/settings/smtp/test", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) diff --git a/backend/internal/api/handlers/system_handler_test.go b/backend/internal/api/handlers/system_handler_test.go index 10647bdb..a7a69f3e 100644 --- a/backend/internal/api/handlers/system_handler_test.go +++ b/backend/internal/api/handlers/system_handler_test.go @@ -10,7 +10,7 @@ import ( func TestGetClientIPHeadersAndRemoteAddr(t *testing.T) { // Cloudflare header should win - req := httptest.NewRequest(http.MethodGet, "/", nil) + req := httptest.NewRequest(http.MethodGet, "/", http.NoBody) req.Header.Set("CF-Connecting-IP", "5.6.7.8") ip := getClientIP(req) if ip != "5.6.7.8" { @@ -18,7 +18,7 @@ func TestGetClientIPHeadersAndRemoteAddr(t *testing.T) { } // X-Real-IP should be preferred over RemoteAddr - req2 := httptest.NewRequest(http.MethodGet, "/", nil) + req2 := httptest.NewRequest(http.MethodGet, "/", http.NoBody) req2.Header.Set("X-Real-IP", "10.0.0.4") req2.RemoteAddr = "1.2.3.4:5678" ip2 := getClientIP(req2) @@ -27,7 +27,7 @@ func TestGetClientIPHeadersAndRemoteAddr(t *testing.T) { } // X-Forwarded-For returns first in list - req3 := httptest.NewRequest(http.MethodGet, "/", nil) + req3 := httptest.NewRequest(http.MethodGet, "/", http.NoBody) req3.Header.Set("X-Forwarded-For", "192.168.0.1, 192.168.0.2") ip3 := getClientIP(req3) if ip3 != "192.168.0.1" { @@ -35,7 +35,7 @@ func TestGetClientIPHeadersAndRemoteAddr(t *testing.T) { } // Fallback to remote addr port trimmed - req4 := httptest.NewRequest(http.MethodGet, "/", nil) + req4 := httptest.NewRequest(http.MethodGet, "/", http.NoBody) req4.RemoteAddr = "7.7.7.7:8888" ip4 := getClientIP(req4) if ip4 != "7.7.7.7" { @@ -51,7 +51,7 @@ func TestGetMyIPHandler(t *testing.T) { // With CF header w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/myip", nil) + req := httptest.NewRequest(http.MethodGet, "/myip", http.NoBody) req.Header.Set("CF-Connecting-IP", "5.6.7.8") r.ServeHTTP(w, req) if w.Code != http.StatusOK { diff --git a/backend/internal/api/handlers/testdb.go b/backend/internal/api/handlers/testdb.go index 7e932f42..3b5799ac 100644 --- a/backend/internal/api/handlers/testdb.go +++ b/backend/internal/api/handlers/testdb.go @@ -1,8 +1,9 @@ package handlers import ( + crand "crypto/rand" "fmt" - "math/rand" + "math/big" "strings" "testing" "time" @@ -11,14 +12,15 @@ import ( "gorm.io/gorm" ) -// openTestDB creates a SQLite in-memory DB unique per test and applies +// OpenTestDB creates a SQLite in-memory DB unique per test and applies // a busy timeout and WAL journal mode to reduce SQLITE locking during parallel tests. func OpenTestDB(t *testing.T) *gorm.DB { t.Helper() // Append a timestamp/random suffix to ensure uniqueness even across parallel runs dsnName := strings.ReplaceAll(t.Name(), "/", "_") - rand.Seed(time.Now().UnixNano()) - uniqueSuffix := fmt.Sprintf("%d%d", time.Now().UnixNano(), rand.Intn(10000)) + // Use crypto/rand for suffix generation in tests to avoid static analysis warnings + n, _ := crand.Int(crand.Reader, big.NewInt(10000)) + uniqueSuffix := fmt.Sprintf("%d%d", time.Now().UnixNano(), n.Int64()) dsn := fmt.Sprintf("file:%s_%s?mode=memory&cache=shared&_journal_mode=WAL&_busy_timeout=5000", dsnName, uniqueSuffix) db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{}) if err != nil { diff --git a/backend/internal/api/handlers/update_handler_test.go b/backend/internal/api/handlers/update_handler_test.go index 1405a231..5c50f730 100644 --- a/backend/internal/api/handlers/update_handler_test.go +++ b/backend/internal/api/handlers/update_handler_test.go @@ -37,7 +37,7 @@ func TestUpdateHandler_Check(t *testing.T) { r.GET("/api/v1/update", h.Check) // Test Request - req := httptest.NewRequest(http.MethodGet, "/api/v1/update", nil) + req := httptest.NewRequest(http.MethodGet, "/api/v1/update", http.NoBody) resp := httptest.NewRecorder() r.ServeHTTP(resp, req) @@ -62,7 +62,7 @@ func TestUpdateHandler_Check(t *testing.T) { rError := gin.New() rError.GET("/api/v1/update", hError.Check) - reqError := httptest.NewRequest(http.MethodGet, "/api/v1/update", nil) + reqError := httptest.NewRequest(http.MethodGet, "/api/v1/update", http.NoBody) respError := httptest.NewRecorder() rError.ServeHTTP(respError, reqError) @@ -80,7 +80,7 @@ func TestUpdateHandler_Check(t *testing.T) { rClientError := gin.New() rClientError.GET("/api/v1/update", hClientError.Check) - reqClientError := httptest.NewRequest(http.MethodGet, "/api/v1/update", nil) + reqClientError := httptest.NewRequest(http.MethodGet, "/api/v1/update", http.NoBody) respClientError := httptest.NewRecorder() rClientError.ServeHTTP(respClientError, reqClientError) diff --git a/backend/internal/api/handlers/uptime_handler_test.go b/backend/internal/api/handlers/uptime_handler_test.go index c840e3c3..11bb8c2d 100644 --- a/backend/internal/api/handlers/uptime_handler_test.go +++ b/backend/internal/api/handlers/uptime_handler_test.go @@ -52,7 +52,7 @@ func TestUptimeHandler_List(t *testing.T) { } db.Create(&monitor) - req, _ := http.NewRequest("GET", "/api/v1/uptime", nil) + req, _ := http.NewRequest("GET", "/api/v1/uptime", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -88,7 +88,7 @@ func TestUptimeHandler_GetHistory(t *testing.T) { CreatedAt: time.Now(), }) - req, _ := http.NewRequest("GET", "/api/v1/uptime/"+monitorID+"/history", nil) + req, _ := http.NewRequest("GET", "/api/v1/uptime/"+monitorID+"/history", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -108,7 +108,7 @@ func TestUptimeHandler_CheckMonitor(t *testing.T) { monitor := models.UptimeMonitor{ID: "check-mon-1", Name: "Check Monitor", Type: "http", URL: "http://example.com"} db.Create(&monitor) - req, _ := http.NewRequest("POST", "/api/v1/uptime/check-mon-1/check", nil) + req, _ := http.NewRequest("POST", "/api/v1/uptime/check-mon-1/check", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -118,7 +118,7 @@ func TestUptimeHandler_CheckMonitor(t *testing.T) { func TestUptimeHandler_CheckMonitor_NotFound(t *testing.T) { r, _ := setupUptimeHandlerTest(t) - req, _ := http.NewRequest("POST", "/api/v1/uptime/nonexistent/check", nil) + req, _ := http.NewRequest("POST", "/api/v1/uptime/nonexistent/check", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -193,7 +193,7 @@ func TestUptimeHandler_DeleteAndSync(t *testing.T) { monitor := models.UptimeMonitor{ID: "mon-delete", Name: "ToDelete", Type: "http", URL: "http://example.com"} db.Create(&monitor) - req, _ := http.NewRequest("DELETE", "/api/v1/uptime/mon-delete", nil) + req, _ := http.NewRequest("DELETE", "/api/v1/uptime/mon-delete", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -209,7 +209,7 @@ func TestUptimeHandler_DeleteAndSync(t *testing.T) { host := models.ProxyHost{UUID: "ph-up-1", Name: "Test Host", DomainNames: "sync.example.com", ForwardHost: "127.0.0.1", ForwardPort: 80, Enabled: true} db.Create(&host) - req, _ := http.NewRequest("POST", "/api/v1/uptime/sync", nil) + req, _ := http.NewRequest("POST", "/api/v1/uptime/sync", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -244,7 +244,7 @@ func TestUptimeHandler_DeleteAndSync(t *testing.T) { func TestUptimeHandler_Sync_Success(t *testing.T) { r, _ := setupUptimeHandlerTest(t) - req, _ := http.NewRequest("POST", "/api/v1/uptime/sync", nil) + req, _ := http.NewRequest("POST", "/api/v1/uptime/sync", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -259,7 +259,7 @@ func TestUptimeHandler_Delete_Error(t *testing.T) { r, db := setupUptimeHandlerTest(t) db.Exec("DROP TABLE IF EXISTS uptime_monitors") - req, _ := http.NewRequest("DELETE", "/api/v1/uptime/nonexistent", nil) + req, _ := http.NewRequest("DELETE", "/api/v1/uptime/nonexistent", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -270,7 +270,7 @@ func TestUptimeHandler_List_Error(t *testing.T) { r, db := setupUptimeHandlerTest(t) db.Exec("DROP TABLE IF EXISTS uptime_monitors") - req, _ := http.NewRequest("GET", "/api/v1/uptime", nil) + req, _ := http.NewRequest("GET", "/api/v1/uptime", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -281,7 +281,7 @@ func TestUptimeHandler_GetHistory_Error(t *testing.T) { r, db := setupUptimeHandlerTest(t) db.Exec("DROP TABLE IF EXISTS uptime_heartbeats") - req, _ := http.NewRequest("GET", "/api/v1/uptime/monitor-1/history", nil) + req, _ := http.NewRequest("GET", "/api/v1/uptime/monitor-1/history", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) diff --git a/backend/internal/api/handlers/user_handler_test.go b/backend/internal/api/handlers/user_handler_test.go index 52e2a404..0c870feb 100644 --- a/backend/internal/api/handlers/user_handler_test.go +++ b/backend/internal/api/handlers/user_handler_test.go @@ -34,7 +34,7 @@ func TestUserHandler_GetSetupStatus(t *testing.T) { r.GET("/setup", handler.GetSetupStatus) // No users -> setup required - req, _ := http.NewRequest("GET", "/setup", nil) + req, _ := http.NewRequest("GET", "/setup", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -110,7 +110,7 @@ func TestUserHandler_RegenerateAPIKey(t *testing.T) { }) r.POST("/api-key", handler.RegenerateAPIKey) - req, _ := http.NewRequest("POST", "/api-key", nil) + req, _ := http.NewRequest("POST", "/api-key", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -143,7 +143,7 @@ func TestUserHandler_GetProfile(t *testing.T) { }) r.GET("/profile", handler.GetProfile) - req, _ := http.NewRequest("GET", "/profile", nil) + req, _ := http.NewRequest("GET", "/profile", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -206,18 +206,18 @@ func TestUserHandler_Errors(t *testing.T) { }) // Test Unauthorized - req, _ := http.NewRequest("GET", "/profile-no-auth", nil) + req, _ := http.NewRequest("GET", "/profile-no-auth", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) - req, _ = http.NewRequest("POST", "/api-key-no-auth", nil) + req, _ = http.NewRequest("POST", "/api-key-no-auth", http.NoBody) w = httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) // Test Not Found (GetProfile) - req, _ = http.NewRequest("GET", "/profile-not-found", nil) + req, _ = http.NewRequest("GET", "/profile-not-found", http.NoBody) w = httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) @@ -229,7 +229,7 @@ func TestUserHandler_Errors(t *testing.T) { // However, let's see if we can force an error by closing DB? No, shared DB. // We can drop the table? db.Migrator().DropTable(&models.User{}) - req, _ = http.NewRequest("POST", "/api-key-not-found", nil) + req, _ = http.NewRequest("POST", "/api-key-not-found", http.NoBody) w = httptest.NewRecorder() r.ServeHTTP(w, req) // If table missing, Update should fail @@ -360,7 +360,7 @@ func TestUserHandler_UpdateProfile_Errors(t *testing.T) { // 1. Unauthorized (no userID) r.PUT("/profile-no-auth", handler.UpdateProfile) - req, _ := http.NewRequest("PUT", "/profile-no-auth", nil) + req, _ := http.NewRequest("PUT", "/profile-no-auth", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) @@ -409,7 +409,7 @@ func TestUserHandler_ListUsers_NonAdmin(t *testing.T) { }) r.GET("/users", handler.ListUsers) - req := httptest.NewRequest("GET", "/users", nil) + req := httptest.NewRequest("GET", "/users", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -433,7 +433,7 @@ func TestUserHandler_ListUsers_Admin(t *testing.T) { }) r.GET("/users", handler.ListUsers) - req := httptest.NewRequest("GET", "/users", nil) + req := httptest.NewRequest("GET", "/users", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -577,7 +577,7 @@ func TestUserHandler_GetUser_NonAdmin(t *testing.T) { }) r.GET("/users/:id", handler.GetUser) - req := httptest.NewRequest("GET", "/users/1", nil) + req := httptest.NewRequest("GET", "/users/1", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -594,7 +594,7 @@ func TestUserHandler_GetUser_InvalidID(t *testing.T) { }) r.GET("/users/:id", handler.GetUser) - req := httptest.NewRequest("GET", "/users/invalid", nil) + req := httptest.NewRequest("GET", "/users/invalid", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -611,7 +611,7 @@ func TestUserHandler_GetUser_NotFound(t *testing.T) { }) r.GET("/users/:id", handler.GetUser) - req := httptest.NewRequest("GET", "/users/999", nil) + req := httptest.NewRequest("GET", "/users/999", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -632,7 +632,7 @@ func TestUserHandler_GetUser_Success(t *testing.T) { }) r.GET("/users/:id", handler.GetUser) - req := httptest.NewRequest("GET", "/users/1", nil) + req := httptest.NewRequest("GET", "/users/1", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -759,7 +759,7 @@ func TestUserHandler_DeleteUser_NonAdmin(t *testing.T) { }) r.DELETE("/users/:id", handler.DeleteUser) - req := httptest.NewRequest("DELETE", "/users/1", nil) + req := httptest.NewRequest("DELETE", "/users/1", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -776,7 +776,7 @@ func TestUserHandler_DeleteUser_InvalidID(t *testing.T) { }) r.DELETE("/users/:id", handler.DeleteUser) - req := httptest.NewRequest("DELETE", "/users/invalid", nil) + req := httptest.NewRequest("DELETE", "/users/invalid", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -794,7 +794,7 @@ func TestUserHandler_DeleteUser_NotFound(t *testing.T) { }) r.DELETE("/users/:id", handler.DeleteUser) - req := httptest.NewRequest("DELETE", "/users/999", nil) + req := httptest.NewRequest("DELETE", "/users/999", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -816,7 +816,7 @@ func TestUserHandler_DeleteUser_Success(t *testing.T) { }) r.DELETE("/users/:id", handler.DeleteUser) - req := httptest.NewRequest("DELETE", "/users/1", nil) + req := httptest.NewRequest("DELETE", "/users/1", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -838,7 +838,7 @@ func TestUserHandler_DeleteUser_CannotDeleteSelf(t *testing.T) { }) r.DELETE("/users/:id", handler.DeleteUser) - req := httptest.NewRequest("DELETE", "/users/1", nil) + req := httptest.NewRequest("DELETE", "/users/1", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -976,7 +976,7 @@ func TestUserHandler_ValidateInvite_MissingToken(t *testing.T) { r := gin.New() r.GET("/invite/validate", handler.ValidateInvite) - req := httptest.NewRequest("GET", "/invite/validate", nil) + req := httptest.NewRequest("GET", "/invite/validate", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -989,7 +989,7 @@ func TestUserHandler_ValidateInvite_InvalidToken(t *testing.T) { r := gin.New() r.GET("/invite/validate", handler.ValidateInvite) - req := httptest.NewRequest("GET", "/invite/validate?token=invalidtoken", nil) + req := httptest.NewRequest("GET", "/invite/validate?token=invalidtoken", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -1014,7 +1014,7 @@ func TestUserHandler_ValidateInvite_ExpiredToken(t *testing.T) { r := gin.New() r.GET("/invite/validate", handler.ValidateInvite) - req := httptest.NewRequest("GET", "/invite/validate?token=expiredtoken123", nil) + req := httptest.NewRequest("GET", "/invite/validate?token=expiredtoken123", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -1039,7 +1039,7 @@ func TestUserHandler_ValidateInvite_AlreadyAccepted(t *testing.T) { r := gin.New() r.GET("/invite/validate", handler.ValidateInvite) - req := httptest.NewRequest("GET", "/invite/validate?token=acceptedtoken123", nil) + req := httptest.NewRequest("GET", "/invite/validate?token=acceptedtoken123", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -1064,7 +1064,7 @@ func TestUserHandler_ValidateInvite_Success(t *testing.T) { r := gin.New() r.GET("/invite/validate", handler.ValidateInvite) - req := httptest.NewRequest("GET", "/invite/validate?token=validtoken123", nil) + req := httptest.NewRequest("GET", "/invite/validate?token=validtoken123", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -1333,7 +1333,7 @@ func TestGetBaseURL(t *testing.T) { c.String(200, url) }) - req := httptest.NewRequest("GET", "/test", nil) + req := httptest.NewRequest("GET", "/test", http.NoBody) req.Host = "example.com" req.Header.Set("X-Forwarded-Proto", "https") w := httptest.NewRecorder() diff --git a/backend/internal/api/middleware/auth_test.go b/backend/internal/api/middleware/auth_test.go index 7dc3edcb..7fb4e077 100644 --- a/backend/internal/api/middleware/auth_test.go +++ b/backend/internal/api/middleware/auth_test.go @@ -33,7 +33,7 @@ func TestAuthMiddleware_MissingHeader(t *testing.T) { c.Status(http.StatusOK) }) - req, _ := http.NewRequest("GET", "/test", nil) + req, _ := http.NewRequest("GET", "/test", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -53,7 +53,7 @@ func TestRequireRole_Success(t *testing.T) { c.Status(http.StatusOK) }) - req, _ := http.NewRequest("GET", "/test", nil) + req, _ := http.NewRequest("GET", "/test", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -72,7 +72,7 @@ func TestRequireRole_Forbidden(t *testing.T) { c.Status(http.StatusOK) }) - req, _ := http.NewRequest("GET", "/test", nil) + req, _ := http.NewRequest("GET", "/test", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -95,7 +95,7 @@ func TestAuthMiddleware_Cookie(t *testing.T) { c.Status(http.StatusOK) }) - req, _ := http.NewRequest("GET", "/test", nil) + req, _ := http.NewRequest("GET", "/test", http.NoBody) req.AddCookie(&http.Cookie{Name: "auth_token", Value: token}) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -119,7 +119,7 @@ func TestAuthMiddleware_ValidToken(t *testing.T) { c.Status(http.StatusOK) }) - req, _ := http.NewRequest("GET", "/test", nil) + req, _ := http.NewRequest("GET", "/test", http.NoBody) req.Header.Set("Authorization", "Bearer "+token) w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -137,7 +137,7 @@ func TestAuthMiddleware_InvalidToken(t *testing.T) { c.Status(http.StatusOK) }) - req, _ := http.NewRequest("GET", "/test", nil) + req, _ := http.NewRequest("GET", "/test", http.NoBody) req.Header.Set("Authorization", "Bearer invalid-token") w := httptest.NewRecorder() r.ServeHTTP(w, req) @@ -155,7 +155,7 @@ func TestRequireRole_MissingRoleInContext(t *testing.T) { c.Status(http.StatusOK) }) - req, _ := http.NewRequest("GET", "/test", nil) + req, _ := http.NewRequest("GET", "/test", http.NoBody) w := httptest.NewRecorder() r.ServeHTTP(w, req) diff --git a/backend/internal/api/middleware/recovery_test.go b/backend/internal/api/middleware/recovery_test.go index fbe12240..64675fdd 100644 --- a/backend/internal/api/middleware/recovery_test.go +++ b/backend/internal/api/middleware/recovery_test.go @@ -27,7 +27,7 @@ func TestRecoveryLogsStacktraceVerbose(t *testing.T) { panic("test panic") }) - req := httptest.NewRequest(http.MethodGet, "/panic", nil) + req := httptest.NewRequest(http.MethodGet, "/panic", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) @@ -62,7 +62,7 @@ func TestRecoveryLogsBriefWhenNotVerbose(t *testing.T) { panic("brief panic") }) - req := httptest.NewRequest(http.MethodGet, "/panic", nil) + req := httptest.NewRequest(http.MethodGet, "/panic", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) @@ -95,7 +95,7 @@ func TestRecoverySanitizesHeadersAndPath(t *testing.T) { panic("sensitive panic") }) - req := httptest.NewRequest(http.MethodGet, "/panic", nil) + req := httptest.NewRequest(http.MethodGet, "/panic", http.NoBody) // Add sensitive header that should be redacted req.Header.Set("Authorization", "Bearer secret-token") w := httptest.NewRecorder() diff --git a/backend/internal/api/middleware/request_id_test.go b/backend/internal/api/middleware/request_id_test.go index 69598b13..816c4f09 100644 --- a/backend/internal/api/middleware/request_id_test.go +++ b/backend/internal/api/middleware/request_id_test.go @@ -24,7 +24,7 @@ func TestRequestIDAddsHeaderAndLogger(t *testing.T) { c.String(200, "ok") }) - req := httptest.NewRequest(http.MethodGet, "/test", nil) + req := httptest.NewRequest(http.MethodGet, "/test", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) diff --git a/backend/internal/api/middleware/request_logger_test.go b/backend/internal/api/middleware/request_logger_test.go index 8282c81e..8ff8a494 100644 --- a/backend/internal/api/middleware/request_logger_test.go +++ b/backend/internal/api/middleware/request_logger_test.go @@ -23,7 +23,7 @@ func TestRequestLoggerSanitizesPath(t *testing.T) { router.Use(RequestLogger()) router.GET(longPath, func(c *gin.Context) { c.Status(http.StatusOK) }) - req := httptest.NewRequest(http.MethodGet, longPath, nil) + req := httptest.NewRequest(http.MethodGet, longPath, http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) @@ -56,7 +56,7 @@ func TestRequestLoggerIncludesRequestID(t *testing.T) { router.Use(RequestLogger()) router.GET("/ok", func(c *gin.Context) { c.String(200, "ok") }) - req := httptest.NewRequest(http.MethodGet, "/ok", nil) + req := httptest.NewRequest(http.MethodGet, "/ok", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { diff --git a/backend/internal/api/middleware/security_test.go b/backend/internal/api/middleware/security_test.go index d83cf7bf..99d5f6de 100644 --- a/backend/internal/api/middleware/security_test.go +++ b/backend/internal/api/middleware/security_test.go @@ -117,7 +117,7 @@ func TestSecurityHeaders(t *testing.T) { c.String(http.StatusOK, "OK") }) - req := httptest.NewRequest(http.MethodGet, "/test", nil) + req := httptest.NewRequest(http.MethodGet, "/test", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) @@ -141,7 +141,7 @@ func TestSecurityHeadersCustomCSP(t *testing.T) { c.String(http.StatusOK, "OK") }) - req := httptest.NewRequest(http.MethodGet, "/test", nil) + req := httptest.NewRequest(http.MethodGet, "/test", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) diff --git a/backend/internal/api/tests/integration_test.go b/backend/internal/api/tests/integration_test.go index 71574633..5bb0224f 100644 --- a/backend/internal/api/tests/integration_test.go +++ b/backend/internal/api/tests/integration_test.go @@ -38,7 +38,7 @@ func TestIntegration_WAF_BlockAndMonitor(t *testing.T) { // Block mode should reject suspicious payload on an API route covered by middleware rBlock, _ := newServer("block") - req := httptest.NewRequest(http.MethodGet, "/api/v1/remote-servers?test=