From 6a17dc6387146a63e6d23a0f081eb842906084e5 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 8 Dec 2025 01:04:26 +0000 Subject: [PATCH 1/7] feat: Add VS Code settings, tasks, and troubleshooting documentation for Go development --- .vscode/settings.json | 37 +- .vscode/tasks.json | 31 ++ Makefile | 10 +- .../api/handlers/certificate_handler_test.go | 12 +- backend/internal/api/handlers/doc.go | 8 + .../internal/api/handlers/perf_assert_test.go | 21 +- backend/internal/api/handlers/testdb.go | 8 +- backend/internal/services/mail_service.go | 32 +- docs/plans/current_spec.md | 390 ++++++++++++++++++ docs/troubleshooting/go-gopls.md | 11 + scripts/check_go_build.sh | 26 ++ scripts/gopls_collect.sh | 23 ++ 12 files changed, 547 insertions(+), 62 deletions(-) create mode 100644 backend/internal/api/handlers/doc.go create mode 100644 docs/troubleshooting/go-gopls.md create mode 100755 scripts/check_go_build.sh create mode 100755 scripts/gopls_collect.sh 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/internal/api/handlers/certificate_handler_test.go b/backend/internal/api/handlers/certificate_handler_test.go index b72a4080..a41fed6f 100644 --- a/backend/internal/api/handlers/certificate_handler_test.go +++ b/backend/internal/api/handlers/certificate_handler_test.go @@ -465,14 +465,4 @@ func generateSelfSignedCertPEM() (string, string, error) { return certPEM.String(), keyPEM.String(), 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/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/perf_assert_test.go b/backend/internal/api/handlers/perf_assert_test.go index 49d5cd9a..120ca31e 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 { @@ -90,11 +77,7 @@ func computePercentiles(samples []float64) (avg, p50, p95, p99, max float64) { 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) diff --git a/backend/internal/api/handlers/testdb.go b/backend/internal/api/handlers/testdb.go index 7e932f42..44f87654 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" @@ -17,8 +18,9 @@ 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/services/mail_service.go b/backend/internal/services/mail_service.go index 89f8884f..7d7b216d 100644 --- a/backend/internal/services/mail_service.go +++ b/backend/internal/services/mail_service.go @@ -18,7 +18,7 @@ import ( // emailHeaderSanitizer removes CR, LF, and other control characters that could // enable header injection attacks (CWE-93: Improper Neutralization of CRLF). -var emailHeaderSanitizer = regexp.MustCompile(`[\r\n\x00-\x1f\x7f]`) +var emailHeaderSanitizer = regexp.MustCompile(`[\x00-\x1f\x7f]`) // SMTPConfig holds the SMTP server configuration. type SMTPConfig struct { @@ -145,14 +145,22 @@ func (s *MailService) TestConnection() error { if err != nil { return fmt.Errorf("SSL connection failed: %w", err) } - defer conn.Close() + defer func() { + if err := conn.Close(); err != nil { + logger.Log().WithError(err).Warn("failed to close tls conn") + } + }() case "starttls", "none", "": client, err := smtp.Dial(addr) if err != nil { return fmt.Errorf("SMTP connection failed: %w", err) } - defer client.Close() + defer func() { + if err := client.Close(); err != nil { + logger.Log().WithError(err).Warn("failed to close smtp client") + } + }() if config.Encryption == "starttls" { tlsConfig := &tls.Config{ @@ -270,13 +278,21 @@ func (s *MailService) sendSSL(addr string, config *SMTPConfig, auth smtp.Auth, t if err != nil { return fmt.Errorf("SSL connection failed: %w", err) } - defer conn.Close() + defer func() { + if err := conn.Close(); err != nil { + logger.Log().WithError(err).Warn("failed to close tls conn") + } + }() client, err := smtp.NewClient(conn, config.Host) if err != nil { return fmt.Errorf("failed to create SMTP client: %w", err) } - defer client.Close() + defer func() { + if err := client.Close(); err != nil { + logger.Log().WithError(err).Warn("failed to close smtp client") + } + }() if auth != nil { if err := client.Auth(auth); err != nil { @@ -314,7 +330,11 @@ func (s *MailService) sendSTARTTLS(addr string, config *SMTPConfig, auth smtp.Au if err != nil { return fmt.Errorf("SMTP connection failed: %w", err) } - defer client.Close() + defer func() { + if err := client.Close(); err != nil { + logger.Log().WithError(err).Warn("failed to close smtp client") + } + }() tlsConfig := &tls.Config{ ServerName: config.Host, diff --git a/docs/plans/current_spec.md b/docs/plans/current_spec.md index 23ada78b..d50a1956 100644 --- a/docs/plans/current_spec.md +++ b/docs/plans/current_spec.md @@ -1,3 +1,393 @@ +# VS Code Go Troubleshooting Guide & Automations — Current Spec + +## Overview + +This document defines a focused implementation plan to add a concise VS Code Go troubleshooting guide and small automations to the Charon repository to address persistent Go compiler errors caused by gopls or workspace misconfiguration. The scope is limited to developer tooling, diagnostics, and safe automation changes that live in `docs/`, `scripts/`, and `.vscode/` (no production code changes). Implementation must respect the repository's architecture rules in .github/copilot-instructions.md (backend in `backend/`, frontend in `frontend/`, no Python). + +## Goals / Acceptance Criteria + +- Provide reproducible steps that make `go build ./...` succeed in the repo for contributors. +- Provide easy-to-run tasks and scripts that surface common misconfigurations (missing modules, GOPATH issues, gopls misbehavior). +- Provide VS Code settings and tasks so the `Go` extension/gopls behaves reliably for this repo layout. +- Provide CI and pre-commit recommendations to prevent regressions. +- Provide QA checklist that verifies build, language-server behavior, and CI integration. + +Acceptance Criteria (testable): +- Running `./scripts/check_go_build.sh` from repo root in a clean dev environment returns exit code 0 and prints "BUILD_OK". +- `cd backend && go build ./...` returns exit code 0 locally on a standard Linux environment with Go installed. +- VS Code: running the `Go: Restart Language Server` command after applying `.vscode/settings.json` and `.vscode/tasks.json` clears gopls editor errors (no stale compiler errors remain in Problems panel for valid code). +- A dedicated GH Actions job runs `cd backend && go test ./...` and `go build ./...` and returns success in CI. + +## Files to Inspect & Modify (exact paths) + +- docs/plans/current_spec.md (this file) +- docs/troubleshooting/go-gopls.md (new — guidance + logs collection) +- .vscode/tasks.json (new) +- .vscode/settings.json (new) +- scripts/check_go_build.sh (new) +- scripts/gopls_collect.sh (new) +- Makefile (suggested additions at root) +- .github/workflows/ci-go.yml (suggested CI job snippet — add or integrate into existing CI) +- .pre-commit-config.yaml (suggested update to add a hook calling `scripts/check_go_build.sh`) +- backend/go.mod, backend/go.work, backend/** (inspect for module path and replace directives) +- backend/cmd/api (inspect build entrypoint) +- backend/internal/server (inspect server mount and attachFrontend logic) +- backend/internal/config (inspect env handling for CHARON_* variables) +- backend/internal/models (inspect for heavy imports that may cause build issues) +- frontend/.vscode (ensure no conflicting workspace settings in frontend) +- go.work (workspace-level module directives) + +If function-level inspection is needed, likely candidates: +- `/projects/Charon/backend/cmd/api/main.go` or `/projects/Charon/backend/cmd/api/*.go` (entrypoint) +- `/projects/Charon/backend/internal/api/routes/routes.go` (AutoMigrate, router mounting) + +Do NOT change production code unless the cause is strictly workspace/config related and low risk. Prefer documenting and instrumenting. + +## Proposed Implementation (step-by-step) + +1. Create `docs/troubleshooting/go-gopls.md` describing how to reproduce, collect logs, and file upstream issues. (Minimal doc — see Templates below.) + +2. Add `.vscode/settings.json` and `.vscode/tasks.json` to the repo root to standardize developer tools. These settings will scope to the workspace and will not affect CI. + +3. Create `scripts/check_go_build.sh` that runs reproducible checks: `go version`, `go env`, `go list -mod=mod`, `go build ./...` in `backend/`, and prints diagnostic info if the build fails. + +4. Create `scripts/gopls_collect.sh` to collect `gopls` logs with `-rpc.trace` and instruct developers how to attach those logs when filing upstream issues. + +5. Add a small Makefile target for dev convenience: `make go-check` -> runs `scripts/check_go_build.sh` and `make gopls-logs` -> runs `scripts/gopls_collect.sh`. + +6. Add a recommended GH Actions job snippet to run `go test` and `go build` for `backend/`. Add this to `.github/workflows/ci-go.yml` or integrate into the existing CI workflow. + +7. Add a sample `pre-commit` hook entry invoking `scripts/check_go_build.sh` (optional, manual-stage hook recommended rather than blocking commit for every contributor). + +8. Add `docs/troubleshooting/go-gopls.md` acceptance checklist and QA test cases. + +9. Communicate rollout plan and document how to revert the automation files (simple revert PR). + +## VS Code Tasks and Settings + +Place these files under `.vscode/` in the repository root. They are workspace recommendations a developer can accept when opening the workspace. + +1) .vscode/tasks.json + +```json +{ + "version": "2.0.0", + "tasks": [ + { + "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" } + } + ] +} +``` + +2) .vscode/settings.json + +```json +{ + "go.useLanguageServer": true, + "gopls": { + "staticcheck": true, + "analyses": { + "unusedparams": true, + "nilness": true + }, + "completeUnimported": true, + "matcher": "Fuzzy", + "verboseOutput": true + }, + "go.toolsEnvVars": { + "GOMODCACHE": "${workspaceFolder}/.cache/go/pkg/mod" + }, + "go.buildOnSave": "workspace", + "go.lintOnSave": "package", + "go.formatTool": "gofmt", + "files.watcherExclude": { + "**/backend/data/**": true, + "**/frontend/dist/**": true + } +} +``` + +Notes on settings: +- `gopls.verboseOutput` will allow VS Code Output panel to show richer logs for triage. +- `GOMODCACHE` is set to workspace-local to prevent unexpected GOPATH/GOMOD cache interference on some dev machines; change if undesired. + +## Scripts to Add + +Create `scripts/check_go_build.sh` and `scripts/gopls_collect.sh` with the contents below. Make both executable (`chmod +x scripts/*.sh`). + +1) scripts/check_go_build.sh + +```sh +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +echo "[charon] repo root: $ROOT_DIR" + +echo "-- go version --" +go version || true + +echo "-- go env --" +go env || true + +echo "-- go list (backend) --" +cd "$ROOT_DIR/backend" +echo "module: $(cat go.mod | sed -n '1p')" +go list -deps ./... | wc -l || true + +echo "-- go build backend ./... --" +if go build ./...; then + echo "BUILD_OK" + exit 0 +else + echo "BUILD_FAIL" + echo "Run 'cd backend && go build -v ./...' for verbose output" + exit 2 +fi +``` + +2) scripts/gopls_collect.sh + +```sh +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +OUT_DIR="/tmp/charon-gopls-logs-$(date +%s)" +mkdir -p "$OUT_DIR" +echo "Collecting gopls debug output to $OUT_DIR" + +if ! command -v gopls >/dev/null 2>&1; then + echo "gopls not found in PATH. Install with: go install golang.org/x/tools/gopls@latest" + exit 2 +fi + +cd "$ROOT_DIR/backend" +echo "Running: gopls -rpc.trace -v check ./... > $OUT_DIR/gopls.log 2>&1" +gopls -rpc.trace -v check ./... > "$OUT_DIR/gopls.log" 2>&1 || true + +echo "Also collecting 'go env' and 'go version'" +go version > "$OUT_DIR/go-version.txt" 2>&1 || true +go env > "$OUT_DIR/go-env.txt" 2>&1 || true + +echo "Logs collected at: $OUT_DIR" +echo "Attach the $OUT_DIR contents when filing issues against golang/vscode-go or gopls." +``` + +Optional: `Makefile` additions (root `Makefile`): + +```makefile +.PHONY: go-check gopls-logs +go-check: + ./scripts/check_go_build.sh + +gopls-logs: + ./scripts/gopls_collect.sh +``` + +## CI or Pre-commit Hooks to Run + +CI: Add or update a GitHub Actions job in `.github/workflows/ci-go.yml` (or combine with existing CI): + +```yaml +name: Go CI (backend) +on: [push, pull_request] +jobs: + go-backend: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.20' + - name: Cache Go modules + uses: actions/cache@v4 + with: + path: ~/.cache/go-build + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + - name: Build backend + run: | + cd backend + go version + go env + go test ./... -v + go build ./... + +``` + +Pre-commit (optional): add this to `.pre-commit-config.yaml` under `repos:` as a local hook or add a simple script that developers can opt into. Example local hook entry: + +```yaml +- repo: local + hooks: + - id: go-check + name: go-check + entry: ./scripts/check_go_build.sh + language: system + stages: [manual] +``` + +Rationale: run as a `manual` hook to avoid blocking every commit but available for maintainers to run pre-merge. + +## Tests & Validation Steps + +Run these commands locally and in CI as part of acceptance testing. + +1) Basic local verification (developer machine): + +```bash +# from repo root +./scripts/check_go_build.sh +# expected output contains: BUILD_OK and exit code 0 + +cd backend +go test ./... -v +# expected: all tests PASS and exit code 0 + +go build ./... +# expected: no errors, exit code 0 + +# In VS Code: open workspace, accept recommended workspace settings, then +# Run Command Palette -> "Go: Restart Language Server" +# Open Problems panel: expect no stale gopls errors for otherwise-valid code +``` + +2) Gather gopls logs (if developer still sees errors): + +```bash +./scripts/gopls_collect.sh +# expected: prints path to logs and files: gopls.log, go-version.txt, go-env.txt +``` + +3) CI validation (after adding `ci-go.yml`): + +Push branch, create PR. GitHub Actions should run `Go CI (backend)` job and show green check on success. Expected steps: `go test` and `go build` both pass. + +4) QA Acceptance Checklist (for QA_Security): +- `./scripts/check_go_build.sh` returns `BUILD_OK` on a clean environment. +- `cd backend && go test ./... -v` yields only PASS/ok lines. +- Running `./scripts/gopls_collect.sh` produces a non-empty `gopls.log` file when gopls invoked. +- VS Code `Go: Restart Language Server` clears stale errors in Problems panel. +- CI job `Go CI (backend)` passes on PR. + +## How to Collect gopls Logs and File an upstream Issue + +1. Reproduce the problem in VS Code. +2. Run `./scripts/gopls_collect.sh` and attach the `$OUT_DIR` results. +3. If `gopls` is not available, install it locally: `go install golang.org/x/tools/gopls@latest`. +4. Include these items in the issue: +- A minimal reproduction steps list. +- The `gopls.log` produced by `gopls -rpc.trace -v check ./...`. +- Output of `go version` and `go env` (from `go-version.txt` and `go-env.txt`). +5. File the issue on the `golang/vscode-go` issue tracker (preferred), include logs and reproduction steps. + +Suggested issue title template: "gopls: persistent compiler errors in Charon workspace — [short symptom]" + +## Rollout & Backout Plan + +Rollout steps: + +1. Implement the changes in a feature branch `docs/gopls-troubleshoot`. +2. Open a PR describing changes and link to this plan. +3. Run CI and verify `Go CI (backend)` passes. +4. Merge after 2 approvals. +5. Notify contributors in README or developer onboarding that workspace settings and scripts are available. + +Backout steps: + +1. Revert the merge commit or open a PR that removes `.vscode/*`, `scripts/*`, and `docs/troubleshooting/*` files. +2. If CI changes were added to `.github/workflows/*`, revert those files as well. + +Notes on safety: these files are developer-tooling only. They do not alter production code or binaries. + +## Estimated Effort (time / complexity) + +- Create docs and scripts: 1–2 hours (low complexity) +- Add .vscode tasks/settings and Makefile snippet: 30–60 minutes +- Add CI job and test in GH Actions: 1 hour +- QA validation and follow-ups: 1–2 hours + +Total: 3–6 hours for a single engineer to implement, test, and land. + +## Notes for Roles + +- Backend_Dev: + - Inspect `/projects/Charon/backend/go.mod`, `/projects/Charon/backend/go.work`, `/projects/Charon/backend/cmd/api` and `/projects/Charon/backend/internal/*` for any non-module-safe imports, cgo usage, or platform-specific build tags that might confuse `gopls` or `go build`. + - If `go build` fails locally but passes in CI, inspect `go env` differences (GOMODCACHE, GOPATH, GOFLAGS, GO111MODULE). Use `./scripts/check_go_build.sh` to capture environment. + +- Frontend_Dev: + - Ensure there are no conflicting workspace `.vscode` files inside `frontend/` that override root workspace settings. If present, move per-project overrides to `frontend/.vscode` and keep common settings in root `.vscode`. + +- QA_Security: + - Use the acceptance checklist above. Validate that `go test` and `go build` run cleanly in CI and locally. + - Confirm that scripts do not leak secrets (they won't — they only run `go` commands). Confirm scripts are shell-only and do not pull remote binaries without explicit developer action. + +- Docs_Writer: + - Create `docs/troubleshooting/go-gopls.md` with background, step-by-step reproduction, and minimal triage guidance. Link to scripts and how to attach logs when filing upstream issues. + +## Example docs/troubleshooting/go-gopls.md (template) + +Create `docs/troubleshooting/go-gopls.md` with this content (starter): + +``` +# Troubleshooting gopls / VS Code Go errors in Charon + +This page documents how to triage and collect logs for persistent Go errors shown by gopls or VS Code in the Charon repository. + +Steps: +1. Open the Charon workspace in VS Code (project root). +2. Accept the workspace settings prompt to apply `.vscode/settings.json`. +3. Run the workspace task: `Go: Build Backend` (or run `./scripts/check_go_build.sh`). +4. If errors persist, run `./scripts/gopls_collect.sh` and attach the output directory to an issue. + +When filing upstream issues, include `gopls.log`, `go-version.txt`, `go-env.txt`, and a short reproduction. + +``` + +## Checklist (QA) + +- [ ] `./scripts/check_go_build.sh` exits 0 and prints `BUILD_OK`. +- [ ] `cd backend && go test ./... -v` returns all `PASS` results. +- [ ] `go build ./...` returns exit code 0. +- [ ] VS Code Problems panel shows no stale gopls errors after `Go: Restart Language Server`. +- [ ] `./scripts/gopls_collect.sh` produces `gopls.log` containing `rpc.trace` sections. +- [ ] CI job `Go CI (backend)` passes on PR. + +## Final Notes + +All proposed files are restricted to developer experience and documentation. Do not modify production source files unless a concrete code-level bug (not tooling) is found and approved by the backend owner. + +If you'd like, I can also open a PR implementing the `.vscode/`, `scripts/`, `docs/troubleshooting/` additions and the CI job snippet. If approved, I will run the repo-level `make go-check` and iterate on any failures. # Plan: Refactor Feature Flags to Optional Features ## Overview diff --git a/docs/troubleshooting/go-gopls.md b/docs/troubleshooting/go-gopls.md new file mode 100644 index 00000000..88bb5f4a --- /dev/null +++ b/docs/troubleshooting/go-gopls.md @@ -0,0 +1,11 @@ +# Troubleshooting gopls / VS Code Go errors in Charon + +This page documents how to triage and collect logs for persistent Go errors shown by gopls or VS Code in the Charon repository. + +Steps: +1. Open the Charon workspace in VS Code (project root). +2. Accept the workspace settings prompt to apply .vscode/settings.json. +3. Run the workspace task: Go: Build Backend (or run ./scripts/check_go_build.sh). +4. If errors persist, run ./scripts/gopls_collect.sh and attach the output directory to an issue. + +When filing upstream issues, include gopls.log, go-version.txt, go-env.txt, and a short reproduction. diff --git a/scripts/check_go_build.sh b/scripts/check_go_build.sh new file mode 100755 index 00000000..4ed4c9fa --- /dev/null +++ b/scripts/check_go_build.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +echo "[charon] repo root: $ROOT_DIR" + +echo "-- go version --" +go version || true + +echo "-- go env --" +go env || true + +echo "-- go list (backend) --" +cd "$ROOT_DIR/backend" +echo "module: $(cat go.mod | sed -n '1p')" +go list -deps ./... | wc -l || true + +echo "-- go build backend ./... --" +if go build ./...; then + echo "BUILD_OK" + exit 0 +else + echo "BUILD_FAIL" + echo "Run 'cd backend && go build -v ./...' for verbose output" + exit 2 +fi diff --git a/scripts/gopls_collect.sh b/scripts/gopls_collect.sh new file mode 100755 index 00000000..2401c806 --- /dev/null +++ b/scripts/gopls_collect.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +OUT_DIR="/tmp/charon-gopls-logs-$(date +%s)" +mkdir -p "$OUT_DIR" +echo "Collecting gopls debug output to $OUT_DIR" + +if ! command -v gopls >/dev/null 2>&1; then + echo "gopls not found in PATH. Install with: go install golang.org/x/tools/gopls@latest" + exit 2 +fi + +cd "$ROOT_DIR/backend" +echo "Running: gopls -rpc.trace -v check ./... > $OUT_DIR/gopls.log 2>&1" +gopls -rpc.trace -v check ./... > "$OUT_DIR/gopls.log" 2>&1 || true + +echo "Also collecting 'go env' and 'go version'" +go version > "$OUT_DIR/go-version.txt" 2>&1 || true +go env > "$OUT_DIR/go-env.txt" 2>&1 || true + +echo "Logs collected at: $OUT_DIR" +echo "Attach the $OUT_DIR contents when filing issues against golang/vscode-go or gopls." From da378e624c320af0daf5d7978ee00baa83dd5426 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 8 Dec 2025 01:19:04 +0000 Subject: [PATCH 2/7] feat: Update indirect dependencies in go.mod and go.sum for improved compatibility --- backend/go.mod | 8 ++------ backend/go.sum | 48 ++++-------------------------------------------- 2 files changed, 6 insertions(+), 50 deletions(-) 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= From 889163936620e6788e8ff49b7dac840f3c8fb596 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 8 Dec 2025 04:39:34 +0000 Subject: [PATCH 3/7] feat: Add .cache to .dockerignore and .gitignore to exclude cache files from Docker build context and version control --- .codecov.yml | 1 + .dockerignore | 1 + .gitignore | 1 + 3 files changed, 3 insertions(+) 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/.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 From e92429f7bbcec90d2503245bb5283f72470a4528 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 8 Dec 2025 04:43:45 +0000 Subject: [PATCH 4/7] feat: Add GolangCI-Lint step to QA workflow for consistent linting in tests --- .github/agents/QA_Security.agent.md | 1 + 1 file changed, 1 insertion(+) 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. From 63cebf07ab0c70e810503094226bc4bdb3482266 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 8 Dec 2025 05:53:51 +0000 Subject: [PATCH 5/7] Refactor services and improve error handling - Updated file permissions in certificate_service_test.go and log_service_test.go to use octal notation. - Added a new doc.go file to document the services package. - Enhanced error handling in docker_service.go, log_service.go, notification_service.go, proxyhost_service.go, remoteserver_service.go, update_service.go, and uptime_service.go by logging errors when closing resources. - Improved log_service.go to simplify log file processing and deduplication. - Introduced CRUD tests for notification templates in notification_service_template_test.go. - Removed the obsolete python_compile_check.sh script. - Updated notification_service.go to improve template management functions. - Added tests for uptime service notifications in uptime_service_notification_test.go. --- .pre-commit-config.yaml | 9 --- backend/.golangci.yml | 3 + backend/cmd/api/main.go | 4 +- .../access_list_handler_coverage_test.go | 10 +-- .../api/handlers/access_list_handler_test.go | 8 +-- .../api/handlers/additional_coverage_test.go | 33 ++++----- .../api/handlers/auth_handler_test.go | 44 ++++++------ .../handlers/backup_handler_sanitize_test.go | 2 +- .../api/handlers/backup_handler_test.go | 68 +++++++++---------- .../internal/api/handlers/benchmark_test.go | 16 ++--- .../api/handlers/certificate_handler.go | 12 +++- .../certificate_handler_coverage_test.go | 12 ++-- .../certificate_handler_security_test.go | 12 ++-- .../api/handlers/certificate_handler_test.go | 24 ++++--- .../api/handlers/coverage_quick_test.go | 14 ++-- .../api/handlers/crowdsec_decisions_test.go | 14 ++-- .../internal/api/handlers/crowdsec_exec.go | 19 ++++-- .../internal/api/handlers/crowdsec_handler.go | 28 +++++--- .../crowdsec_handler_coverage_test.go | 24 +++---- .../api/handlers/crowdsec_handler_test.go | 16 ++--- .../api/handlers/docker_handler_test.go | 8 +-- .../api/handlers/domain_handler_test.go | 10 +-- .../feature_flags_handler_coverage_test.go | 16 ++--- .../handlers/feature_flags_handler_test.go | 4 +- .../internal/api/handlers/handlers_test.go | 20 +++--- .../api/handlers/health_handler_test.go | 2 +- .../internal/api/handlers/import_handler.go | 18 ++--- .../handlers/import_handler_sanitize_test.go | 2 +- .../api/handlers/import_handler_test.go | 50 +++++++------- backend/internal/api/handlers/logs_handler.go | 25 +++++-- .../handlers/logs_handler_coverage_test.go | 13 ++-- .../api/handlers/logs_handler_test.go | 20 +++--- .../api/handlers/misc_coverage_test.go | 7 +- .../handlers/notification_coverage_test.go | 5 +- .../api/handlers/notification_handler_test.go | 12 ++-- .../notification_provider_handler_test.go | 6 +- .../notification_template_handler_test.go | 4 +- .../internal/api/handlers/perf_assert_test.go | 22 +++--- .../api/handlers/proxy_host_handler.go | 16 ++--- .../api/handlers/proxy_host_handler_test.go | 20 +++--- .../api/handlers/remote_server_handler.go | 29 +++++--- .../handlers/remote_server_handler_test.go | 8 +-- .../security_handler_additional_test.go | 4 +- .../handlers/security_handler_audit_test.go | 18 ++--- .../handlers/security_handler_clean_test.go | 18 ++--- .../security_handler_coverage_test.go | 24 +++---- .../security_handler_rules_decisions_test.go | 8 +-- .../security_handler_settings_test.go | 6 +- .../handlers/security_handler_test_fixed.go | 2 +- .../api/handlers/settings_handler_test.go | 10 +-- .../api/handlers/system_handler_test.go | 10 +-- backend/internal/api/handlers/testdb.go | 2 +- .../api/handlers/update_handler_test.go | 6 +- .../api/handlers/uptime_handler_test.go | 20 +++--- .../api/handlers/user_handler_test.go | 50 +++++++------- backend/internal/api/middleware/auth_test.go | 14 ++-- .../internal/api/middleware/recovery_test.go | 6 +- .../api/middleware/request_id_test.go | 2 +- .../api/middleware/request_logger_test.go | 4 +- .../internal/api/middleware/security_test.go | 4 +- .../internal/api/tests/integration_test.go | 6 +- .../api/tests/user_smtp_audit_test.go | 10 +-- backend/internal/caddy/client.go | 4 +- backend/internal/caddy/config.go | 2 +- backend/internal/caddy/config_extra_test.go | 19 +++--- backend/internal/caddy/importer.go | 65 ++++++++++-------- .../caddy/importer_additional_test.go | 4 +- backend/internal/caddy/importer_extra_test.go | 16 ++--- backend/internal/caddy/importer_test.go | 8 +-- backend/internal/caddy/manager.go | 32 ++++----- .../internal/caddy/manager_additional_test.go | 36 +++++----- backend/internal/caddy/manager_test.go | 12 ++-- .../cerberus/cerberus_middleware_test.go | 16 ++--- backend/internal/server/server_test.go | 4 +- .../internal/services/access_list_service.go | 6 +- backend/internal/services/backup_service.go | 24 +++++-- .../services/backup_service_disk_test.go | 35 ++++++++++ .../internal/services/backup_service_test.go | 30 ++++---- .../services/certificate_service_test.go | 57 ++++++++-------- backend/internal/services/doc.go | 6 ++ backend/internal/services/docker_service.go | 7 +- backend/internal/services/log_service.go | 56 ++++++++------- backend/internal/services/log_service_test.go | 14 ++-- .../internal/services/notification_service.go | 20 ++++-- .../notification_service_template_test.go | 50 ++++++++++++++ .../services/notification_service_test.go | 3 +- .../internal/services/proxyhost_service.go | 11 ++- .../internal/services/remoteserver_service.go | 4 +- backend/internal/services/update_service.go | 11 ++- backend/internal/services/uptime_service.go | 14 +++- .../uptime_service_notification_test.go | 35 ++++++++++ go.work.sum | 3 + tools/python_compile_check.sh | 26 ------- 93 files changed, 879 insertions(+), 664 deletions(-) create mode 100644 backend/internal/services/backup_service_disk_test.go create mode 100644 backend/internal/services/doc.go create mode 100644 backend/internal/services/notification_service_template_test.go create mode 100644 backend/internal/services/uptime_service_notification_test.go delete mode 100755 tools/python_compile_check.sh 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/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/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 a41fed6f..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,11 +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 } // 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/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 120ca31e..678f34e5 100644 --- a/backend/internal/api/handlers/perf_assert_test.go +++ b/backend/internal/api/handlers/perf_assert_test.go @@ -53,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 { @@ -73,7 +73,7 @@ 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 } @@ -93,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 != "" { @@ -104,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) } @@ -123,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) @@ -140,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) } @@ -167,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 44f87654..3b5799ac 100644 --- a/backend/internal/api/handlers/testdb.go +++ b/backend/internal/api/handlers/testdb.go @@ -12,7 +12,7 @@ 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() 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=