chore: implement instruction compliance remediation

- Replace Go interface{} with any (Go 1.18+ standard)
- Add database indexes to frequently queried model fields
- Add JSDoc documentation to frontend API client methods
- Remove deprecated docker-compose version keys
- Add concurrency groups to all 25 GitHub Actions workflows
- Add YAML front matter and fix H1→H2 headings in docs

Coverage: Backend 85.5%, Frontend 87.73%
Security: No vulnerabilities detected

Refs: docs/plans/instruction_compliance_spec.md
This commit is contained in:
GitHub Actions
2025-12-21 04:08:42 +00:00
parent a45600e7c4
commit af8384046c
180 changed files with 2101 additions and 880 deletions

View File

@@ -6,6 +6,10 @@ on:
pull_request:
types: [opened, reopened]
concurrency:
group: ${{ github.workflow }}-${{ github.event.issue.number || github.event.pull_request.number }}
cancel-in-progress: false
jobs:
add-to-project:
runs-on: ubuntu-latest

View File

@@ -6,6 +6,10 @@ on:
release:
types: [published]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
update-draft:
runs-on: ubuntu-latest

View File

@@ -4,6 +4,10 @@ on:
issues:
types: [opened, edited]
concurrency:
group: ${{ github.workflow }}-${{ github.event.issue.number }}
cancel-in-progress: true
jobs:
auto-label:
runs-on: ubuntu-latest

View File

@@ -4,6 +4,10 @@ on:
push:
branches: [ main ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: write
pull-requests: write

View File

@@ -15,6 +15,13 @@ on:
- 'backend/**'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
GO_VERSION: '1.25.5'
permissions:
contents: write
deployments: write
@@ -29,7 +36,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version: '1.25.5'
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: backend/go.sum
- name: Run Benchmark
@@ -40,7 +47,8 @@ jobs:
# Only store results on pushes to main - PRs just run benchmarks without storage
# This avoids gh-pages branch errors and permission issues on fork PRs
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: benchmark-action/github-action-benchmark@v1
# Security: Pinned to full SHA for supply chain security
uses: benchmark-action/github-action-benchmark@4e0b38bc48375986542b13c0d8976b7b80c60c00 # v1
with:
name: Go Benchmark
tool: 'go'

View File

@@ -5,6 +5,10 @@ on:
- cron: '17 7 * * 1' # Mondays at 07:17 UTC
workflow_dispatch: {}
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
permissions:
contents: read
issues: write

View File

@@ -7,6 +7,14 @@ on:
- development
- 'feature/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
GO_VERSION: '1.25.5'
NODE_VERSION: '24.12.0'
permissions:
contents: read
@@ -23,7 +31,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version: '1.25.5'
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: backend/go.sum
- name: Run Go tests with coverage
@@ -54,7 +62,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
with:
node-version: '24.12.0'
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
cache-dependency-path: frontend/package-lock.json

View File

@@ -8,6 +8,13 @@ on:
schedule:
- cron: '0 3 * * 1'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
GO_VERSION: '1.25.5'
permissions:
contents: read
security-events: write
@@ -42,7 +49,7 @@ jobs:
if: matrix.language == 'go'
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version: '1.25.5'
go-version: ${{ env.GO_VERSION }}
- name: Autobuild
uses: github/codeql-action/autobuild@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4

View File

@@ -4,6 +4,10 @@ name: Create Project Labels
on:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
jobs:
create-labels:
runs-on: ubuntu-latest

View File

@@ -15,6 +15,10 @@ on:
workflow_dispatch:
workflow_call:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/charon

View File

@@ -10,6 +10,10 @@ on:
paths:
- 'Dockerfile'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
hadolint:
runs-on: ubuntu-latest

View File

@@ -15,6 +15,10 @@ on:
workflow_dispatch:
workflow_call:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/charon

View File

@@ -24,6 +24,13 @@ on:
required: false
type: string
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
env:
NODE_VERSION: '24.12.0'
permissions:
contents: write
issues: write
@@ -44,7 +51,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
with:
node-version: '24.12.0'
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: npm install gray-matter

View File

@@ -21,6 +21,9 @@ concurrency:
group: "pages"
cancel-in-progress: false
env:
NODE_VERSION: '24.12.0'
jobs:
build:
name: Build Documentation
@@ -35,7 +38,7 @@ jobs:
- name: 🔧 Set up Node.js
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
with:
node-version: '24.12.0'
node-version: ${{ env.NODE_VERSION }}
# Step 3: Create a beautiful docs site structure
- name: 📝 Build documentation site

View File

@@ -7,6 +7,10 @@ on:
- cron: '0 2 * * *' # daily at 02:00 UTC
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read

View File

@@ -9,6 +9,10 @@ on:
paths:
- 'scripts/history-rewrite/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest

View File

@@ -4,6 +4,10 @@ on:
pull_request:
types: [opened, edited, synchronize]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
validate:
name: Validate history-rewrite checklist (conditional)

View File

@@ -6,6 +6,13 @@ on:
- main
- development
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
env:
NODE_VERSION: '24.12.0'
permissions:
contents: write
pull-requests: write
@@ -20,7 +27,7 @@ jobs:
- name: Set up Node (for github-script)
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
with:
node-version: '24.12.0'
node-version: ${{ env.NODE_VERSION }}
- name: Propagate Changes
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8

View File

@@ -6,6 +6,14 @@ on:
pull_request:
branches: [ main, development ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
GO_VERSION: '1.25.5'
NODE_VERSION: '24.12.0'
jobs:
backend-quality:
name: Backend (Go)
@@ -16,7 +24,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
with:
go-version: '1.25.5'
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: backend/go.sum
- name: Repo health check
@@ -89,7 +97,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: '24.12.0'
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
cache-dependency-path: frontend/package-lock.json

View File

@@ -5,6 +5,14 @@ on:
tags:
- 'v*'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
env:
GO_VERSION: '1.25.5'
NODE_VERSION: '24.12.0'
permissions:
contents: write
packages: write
@@ -26,12 +34,12 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version: '1.25.5'
go-version: ${{ env.GO_VERSION }}
- name: Set up Node.js
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
with:
node-version: '24.12.0'
node-version: ${{ env.NODE_VERSION }}
- name: Build Frontend
working-directory: frontend
@@ -43,7 +51,8 @@ jobs:
npm run build
- name: Install Cross-Compilation Tools (Zig)
uses: goto-bus-stop/setup-zig@v2
# Security: Pinned to full SHA for supply chain security
uses: goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2
with:
version: 0.13.0

View File

@@ -5,6 +5,10 @@ on:
- cron: '0 5 * * *' # daily 05:00 UTC
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
permissions:
contents: write
pull-requests: write

View File

@@ -7,6 +7,10 @@ on:
types: [opened, synchronize, reopened]
workflow_dispatch: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
repo_health:
name: Repo health

View File

@@ -11,6 +11,10 @@ on:
type: boolean
default: true
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/charon

View File

@@ -20,6 +20,10 @@ on:
# Allow manual trigger
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
waf-integration:
name: Coraza WAF Integration

View File

@@ -253,6 +253,11 @@ RUN apk --no-cache add bash ca-certificates sqlite-libs sqlite tzdata curl gette
&& apk --no-cache upgrade \
&& apk --no-cache upgrade c-ares
# Security: Create non-root user and group for running the application
# This follows the principle of least privilege (CIS Docker Benchmark 4.1)
RUN addgroup -g 1000 charon && \
adduser -D -u 1000 -G charon -h /app -s /sbin/nologin charon
# Download MaxMind GeoLite2 Country database
# Note: In production, users should provide their own MaxMind license key
# This uses the publicly available GeoLite2 database
@@ -279,9 +284,11 @@ RUN chmod +x /usr/local/bin/crowdsec /usr/local/bin/cscli 2>/dev/null || true; \
fi
# Create required CrowdSec directories in runtime image
# Also prepare persistent config directory structure for volume mounts
RUN mkdir -p /etc/crowdsec /etc/crowdsec/acquis.d /etc/crowdsec/bouncers \
/etc/crowdsec/hub /etc/crowdsec/notifications \
/var/lib/crowdsec/data /var/log/crowdsec /var/log/caddy
/var/lib/crowdsec/data /var/log/crowdsec /var/log/caddy \
/app/data/crowdsec/config /app/data/crowdsec/data
# Copy CrowdSec configuration templates from source
COPY configs/crowdsec/acquis.yaml /etc/crowdsec.dist/acquis.yaml
@@ -320,6 +327,14 @@ ENV CHARON_ENV=production \
# Create necessary directories
RUN mkdir -p /app/data /app/data/caddy /config /app/data/crowdsec
# Security: Set ownership of all application directories to non-root charon user
# Note: /app/data and /config are typically mounted as volumes; permissions
# will be handled at runtime in docker-entrypoint.sh if needed
RUN chown -R charon:charon /app /config /var/log/crowdsec /var/log/caddy && \
chown -R charon:charon /etc/crowdsec 2>/dev/null || true && \
chown -R charon:charon /etc/crowdsec.dist 2>/dev/null || true && \
chown -R charon:charon /var/lib/crowdsec 2>/dev/null || true
# Re-declare build args for LABEL usage
ARG VERSION=dev
ARG BUILD_DATE
@@ -339,5 +354,14 @@ LABEL org.opencontainers.image.title="Charon (CPMP legacy)" \
# Expose ports
EXPOSE 80 443 443/udp 2019 8080
# Security: Add healthcheck to monitor container health
# Verifies the Charon API is responding correctly
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD curl -f http://localhost:8080/api/v1/health || exit 1
# Security: Run as non-root user (CIS Docker Benchmark 4.1)
# The entrypoint script handles any required permission fixes for volumes
USER charon
# Use custom entrypoint to start both Caddy and Charon
ENTRYPOINT ["/docker-entrypoint.sh"]

View File

@@ -134,7 +134,7 @@ func main() {
// Verify critical security tables exist before starting server
// This prevents silent failures in CrowdSec reconciliation
securityModels := []interface{}{
securityModels := []any{
&models.SecurityConfig{},
&models.SecurityDecision{},
&models.SecurityAudit{},

View File

@@ -107,7 +107,7 @@ func TestMigrateCommand_Succeeds(t *testing.T) {
t.Fatalf("reconnect db: %v", err)
}
securityModels := []interface{}{
securityModels := []any{
&models.SecurityConfig{},
&models.SecurityDecision{},
&models.SecurityAudit{},
@@ -155,7 +155,7 @@ func TestStartupVerification_MissingTables(t *testing.T) {
}
// Simulate startup verification logic from main.go
securityModels := []interface{}{
securityModels := []any{
&models.SecurityConfig{},
&models.SecurityDecision{},
&models.SecurityAudit{},

View File

@@ -41,12 +41,12 @@ func TestAccessListHandler_Create(t *testing.T) {
tests := []struct {
name string
payload map[string]interface{}
payload map[string]any
wantStatus int
}{
{
name: "create whitelist successfully",
payload: map[string]interface{}{
payload: map[string]any{
"name": "Office Whitelist",
"description": "Allow office IPs only",
"type": "whitelist",
@@ -57,7 +57,7 @@ func TestAccessListHandler_Create(t *testing.T) {
},
{
name: "create geo whitelist successfully",
payload: map[string]interface{}{
payload: map[string]any{
"name": "US Only",
"type": "geo_whitelist",
"country_codes": "US,CA",
@@ -67,7 +67,7 @@ func TestAccessListHandler_Create(t *testing.T) {
},
{
name: "create local network only",
payload: map[string]interface{}{
payload: map[string]any{
"name": "Local Network",
"type": "whitelist",
"local_network_only": true,
@@ -77,7 +77,7 @@ func TestAccessListHandler_Create(t *testing.T) {
},
{
name: "fail with invalid type",
payload: map[string]interface{}{
payload: map[string]any{
"name": "Invalid",
"type": "invalid_type",
"enabled": true,
@@ -86,7 +86,7 @@ func TestAccessListHandler_Create(t *testing.T) {
},
{
name: "fail with missing name",
payload: map[string]interface{}{
payload: map[string]any{
"type": "whitelist",
"enabled": true,
},
@@ -205,13 +205,13 @@ func TestAccessListHandler_Update(t *testing.T) {
tests := []struct {
name string
id string
payload map[string]interface{}
payload map[string]any
wantStatus int
}{
{
name: "update successfully",
id: "1",
payload: map[string]interface{}{
payload: map[string]any{
"name": "Updated Name",
"description": "New description",
"enabled": false,
@@ -223,7 +223,7 @@ func TestAccessListHandler_Update(t *testing.T) {
{
name: "update non-existent ACL",
id: "9999",
payload: map[string]interface{}{
payload: map[string]any{
"name": "Test",
"type": "whitelist",
"ip_rules": `[]`,
@@ -380,7 +380,7 @@ func TestAccessListHandler_TestIP(t *testing.T) {
assert.Equal(t, tt.wantStatus, w.Code)
if w.Code == http.StatusOK {
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Contains(t, response, "allowed")
@@ -400,7 +400,7 @@ func TestAccessListHandler_GetTemplates(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var response []map[string]interface{}
var response []map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.NotEmpty(t, response)

View File

@@ -48,7 +48,7 @@ func TestImportHandler_Commit_InvalidSessionUUID(t *testing.T) {
h := NewImportHandler(db, "", t.TempDir(), "")
body, _ := json.Marshal(map[string]interface{}{
body, _ := json.Marshal(map[string]any{
"session_uuid": "../../../etc/passwd",
})
@@ -70,7 +70,7 @@ func TestImportHandler_Commit_SessionNotFound(t *testing.T) {
h := NewImportHandler(db, "", t.TempDir(), "")
body, _ := json.Marshal(map[string]interface{}{
body, _ := json.Marshal(map[string]any{
"session_uuid": "nonexistent-session",
})
@@ -160,7 +160,7 @@ func TestSecurityHandler_UpdateConfig_ApplyCaddyError(t *testing.T) {
// Create handler with nil caddy manager (ApplyConfig will be called but is nil)
h := NewSecurityHandler(config.SecurityConfig{}, db, nil)
body, _ := json.Marshal(map[string]interface{}{
body, _ := json.Marshal(map[string]any{
"name": "test",
"waf_mode": "block",
})
@@ -242,7 +242,7 @@ func TestSecurityHandler_UpsertRuleSet_Error(t *testing.T) {
// Drop table to cause upsert to fail
db.Migrator().DropTable(&models.SecurityRuleSet{})
body, _ := json.Marshal(map[string]interface{}{
body, _ := json.Marshal(map[string]any{
"name": "test-ruleset",
"enabled": true,
})
@@ -267,7 +267,7 @@ func TestSecurityHandler_CreateDecision_LogError(t *testing.T) {
// Drop decisions table to cause log to fail
db.Migrator().DropTable(&models.SecurityDecision{})
body, _ := json.Marshal(map[string]interface{}{
body, _ := json.Marshal(map[string]any{
"ip": "192.168.1.1",
"action": "ban",
})
@@ -381,7 +381,7 @@ func TestImportHandler_UploadMulti_MissingCaddyfile(t *testing.T) {
h := NewImportHandler(db, "", t.TempDir(), "")
body, _ := json.Marshal(map[string]interface{}{
body, _ := json.Marshal(map[string]any{
"files": []map[string]string{
{"filename": "sites/example.com", "content": "example.com {}"},
},
@@ -404,7 +404,7 @@ func TestImportHandler_UploadMulti_EmptyContent(t *testing.T) {
h := NewImportHandler(db, "", t.TempDir(), "")
body, _ := json.Marshal(map[string]interface{}{
body, _ := json.Marshal(map[string]any{
"files": []map[string]string{
{"filename": "Caddyfile", "content": ""},
},
@@ -427,7 +427,7 @@ func TestImportHandler_UploadMulti_PathTraversal(t *testing.T) {
h := NewImportHandler(db, "", t.TempDir(), "")
body, _ := json.Marshal(map[string]interface{}{
body, _ := json.Marshal(map[string]any{
"files": []map[string]string{
{"filename": "Caddyfile", "content": "example.com {}"},
{"filename": "../../../etc/passwd", "content": "bad content"},
@@ -676,7 +676,7 @@ func TestRemoteServerHandler_TestConnectionCustom_Unreachable2(t *testing.T) {
svc := services.NewRemoteServerService(db)
h := NewRemoteServerHandler(svc, nil)
body, _ := json.Marshal(map[string]interface{}{
body, _ := json.Marshal(map[string]any{
"host": "192.0.2.1", // TEST-NET - not routable
"port": 65535,
})
@@ -870,7 +870,7 @@ func TestImportHandler_UploadMulti_ValidCaddyfile(t *testing.T) {
h := NewImportHandler(db, "", t.TempDir(), "")
body, _ := json.Marshal(map[string]interface{}{
body, _ := json.Marshal(map[string]any{
"files": []map[string]string{
{"filename": "Caddyfile", "content": "example.com { reverse_proxy localhost:8080 }"},
},
@@ -894,7 +894,7 @@ func TestImportHandler_UploadMulti_SubdirFile(t *testing.T) {
h := NewImportHandler(db, "", t.TempDir(), "")
body, _ := json.Marshal(map[string]interface{}{
body, _ := json.Marshal(map[string]any{
"files": []map[string]string{
{"filename": "Caddyfile", "content": "import sites/*"},
{"filename": "sites/example.com", "content": "example.com {}"},

View File

@@ -210,7 +210,7 @@ func TestAuthHandler_Me(t *testing.T) {
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Equal(t, float64(user.ID), resp["user_id"])
assert.Equal(t, "admin", resp["role"])
@@ -513,7 +513,7 @@ func TestAuthHandler_VerifyStatus_NotAuthenticated(t *testing.T) {
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Equal(t, false, resp["authenticated"])
}
@@ -530,7 +530,7 @@ func TestAuthHandler_VerifyStatus_InvalidToken(t *testing.T) {
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Equal(t, false, resp["authenticated"])
}
@@ -560,10 +560,10 @@ func TestAuthHandler_VerifyStatus_Authenticated(t *testing.T) {
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Equal(t, true, resp["authenticated"])
userObj := resp["user"].(map[string]interface{})
userObj := resp["user"].(map[string]any)
assert.Equal(t, "status@example.com", userObj["email"])
}
@@ -593,7 +593,7 @@ func TestAuthHandler_VerifyStatus_DisabledUser(t *testing.T) {
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Equal(t, false, resp["authenticated"])
}
@@ -643,9 +643,9 @@ func TestAuthHandler_GetAccessibleHosts_AllowAll(t *testing.T) {
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
hosts := resp["hosts"].([]interface{})
hosts := resp["hosts"].([]any)
assert.Len(t, hosts, 2)
}
@@ -679,9 +679,9 @@ func TestAuthHandler_GetAccessibleHosts_DenyAll(t *testing.T) {
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
hosts := resp["hosts"].([]interface{})
hosts := resp["hosts"].([]any)
assert.Len(t, hosts, 0)
}
@@ -718,9 +718,9 @@ func TestAuthHandler_GetAccessibleHosts_PermittedHosts(t *testing.T) {
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
hosts := resp["hosts"].([]interface{})
hosts := resp["hosts"].([]any)
assert.Len(t, hosts, 1)
}
@@ -803,7 +803,7 @@ func TestAuthHandler_CheckHostAccess_Allowed(t *testing.T) {
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Equal(t, true, resp["can_access"])
}
@@ -835,7 +835,7 @@ func TestAuthHandler_CheckHostAccess_Denied(t *testing.T) {
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Equal(t, false, resp["can_access"])
}

View File

@@ -31,7 +31,7 @@ func TestBackupHandlerSanitizesFilename(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
// Ensure request-scoped logger is present and writes to our buffer
c.Set("logger", logger.WithFields(map[string]interface{}{"test": "1"}))
c.Set("logger", logger.WithFields(map[string]any{"test": "1"}))
// initialize logger to buffer
buf := &bytes.Buffer{}

View File

@@ -123,7 +123,7 @@ func TestBackupLifecycle(t *testing.T) {
resp = httptest.NewRecorder()
router.ServeHTTP(resp, req)
require.Equal(t, http.StatusOK, resp.Code)
var list []interface{}
var list []any
json.Unmarshal(resp.Body.Bytes(), &list)
require.Empty(t, list)
@@ -158,7 +158,7 @@ func TestBackupHandler_Errors(t *testing.T) {
resp := httptest.NewRecorder()
router.ServeHTTP(resp, req)
require.Equal(t, http.StatusOK, resp.Code)
var list []interface{}
var list []any
json.Unmarshal(resp.Body.Bytes(), &list)
require.Empty(t, list)

View File

@@ -178,7 +178,7 @@ func BenchmarkSecurityHandler_UpsertRuleSet(b *testing.B) {
router := gin.New()
router.POST("/api/v1/security/rulesets", h.UpsertRuleSet)
payload := map[string]interface{}{
payload := map[string]any{
"name": "bench-ruleset",
"content": "SecRule REQUEST_URI \"@contains /admin\" \"id:1000,phase:1,deny\"",
"mode": "blocking",
@@ -209,7 +209,7 @@ func BenchmarkSecurityHandler_CreateDecision(b *testing.B) {
router := gin.New()
router.POST("/api/v1/security/decisions", h.CreateDecision)
payload := map[string]interface{}{
payload := map[string]any{
"ip": "192.168.1.100",
"action": "block",
"details": "benchmark test",
@@ -273,7 +273,7 @@ func BenchmarkSecurityHandler_UpdateConfig(b *testing.B) {
router := gin.New()
router.PUT("/api/v1/security/config", h.UpdateConfig)
payload := map[string]interface{}{
payload := map[string]any{
"name": "default",
"enabled": true,
"rate_limit_enable": true,
@@ -396,7 +396,7 @@ func BenchmarkSecurityHandler_LargeRuleSetContent(b *testing.B) {
largeContent += "SecRule REQUEST_URI \"@contains /path" + string(rune(i)) + "\" \"id:" + string(rune(1000+i)) + ",phase:1,deny\"\n"
}
payload := map[string]interface{}{
payload := map[string]any{
"name": "large-ruleset",
"content": largeContent,
"mode": "blocking",

View File

@@ -126,7 +126,7 @@ func (h *CertificateHandler) Upload(c *gin.Context) {
"cert",
"Certificate Uploaded",
fmt.Sprintf("Certificate %s uploaded", util.SanitizeForLog(cert.Name)),
map[string]interface{}{
map[string]any{
"Name": util.SanitizeForLog(cert.Name),
"Domains": util.SanitizeForLog(cert.Domains),
"Action": "uploaded",
@@ -203,7 +203,7 @@ func (h *CertificateHandler) Delete(c *gin.Context) {
"cert",
"Certificate Deleted",
fmt.Sprintf("Certificate ID %d deleted", id),
map[string]interface{}{
map[string]any{
"ID": id,
"Action": "deleted",
},

View File

@@ -27,7 +27,7 @@ import (
// mockAuthMiddleware adds a mock user to the context for testing
func mockAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("user", map[string]interface{}{"id": 1, "username": "testuser"})
c.Set("user", map[string]any{"id": 1, "username": "testuser"})
c.Next()
}
}

View File

@@ -47,17 +47,17 @@ func TestListPresetsShowsCachedStatus(t *testing.T) {
require.Equal(t, http.StatusOK, resp.Code)
var result map[string]interface{}
var result map[string]any
err = json.Unmarshal(resp.Body.Bytes(), &result)
require.NoError(t, err)
presets := result["presets"].([]interface{})
presets := result["presets"].([]any)
require.NotEmpty(t, presets, "Should have at least one preset")
// Find our cached preset
found := false
for _, p := range presets {
preset := p.(map[string]interface{})
preset := p.(map[string]any)
if preset["slug"] == "test/cached" {
found = true
require.True(t, preset["cached"].(bool), "Preset should be marked as cached")

View File

@@ -49,14 +49,14 @@ func TestListDecisions_Success(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
decisions := resp["decisions"].([]interface{})
decisions := resp["decisions"].([]any)
assert.Len(t, decisions, 1)
decision := decisions[0].(map[string]interface{})
decision := decisions[0].(map[string]any)
assert.Equal(t, "192.168.1.100", decision["value"])
assert.Equal(t, "ban", decision["type"])
assert.Equal(t, "ip", decision["scope"])
@@ -88,11 +88,11 @@ func TestListDecisions_EmptyList(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
decisions := resp["decisions"].([]interface{})
decisions := resp["decisions"].([]any)
assert.Len(t, decisions, 0)
assert.Equal(t, float64(0), resp["total"])
}
@@ -120,11 +120,11 @@ func TestListDecisions_CscliError(t *testing.T) {
// Should return 200 with empty list and error message
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
decisions := resp["decisions"].([]interface{})
decisions := resp["decisions"].([]any)
assert.Len(t, decisions, 0)
assert.Contains(t, resp["error"], "cscli not available")
}
@@ -183,7 +183,7 @@ func TestBanIP_Success(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
@@ -232,7 +232,7 @@ func TestBanIP_DefaultDuration(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
@@ -344,7 +344,7 @@ func TestUnbanIP_Success(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
@@ -406,25 +406,25 @@ func TestListDecisions_MultipleDecisions(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
decisions := resp["decisions"].([]interface{})
decisions := resp["decisions"].([]any)
assert.Len(t, decisions, 3)
assert.Equal(t, float64(3), resp["total"])
// Verify each decision
d1 := decisions[0].(map[string]interface{})
d1 := decisions[0].(map[string]any)
assert.Equal(t, "192.168.1.100", d1["value"])
assert.Equal(t, "cscli", d1["origin"])
d2 := decisions[1].(map[string]interface{})
d2 := decisions[1].(map[string]any)
assert.Equal(t, "10.0.0.50", d2["value"])
assert.Equal(t, "crowdsec", d2["origin"])
assert.Equal(t, "ssh-bf", d2["scenario"])
d3 := decisions[2].(map[string]interface{})
d3 := decisions[2].(map[string]any)
assert.Equal(t, "172.16.0.0/24", d3["value"])
assert.Equal(t, "range", d3["scope"])
}

View File

@@ -253,12 +253,12 @@ func TestCrowdsec_ListFiles_EmptyDir(t *testing.T) {
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
// Files may be nil or empty array when dir is empty
files := resp["files"]
if files != nil {
assert.Len(t, files.([]interface{}), 0)
assert.Len(t, files.([]any), 0)
}
}
@@ -279,7 +279,7 @@ func TestCrowdsec_ListFiles_NonExistent(t *testing.T) {
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
// Should return empty array (nil) for non-existent dir
// The files key should exist
@@ -329,7 +329,7 @@ func TestCrowdsec_ReadFile_NestedPath(t *testing.T) {
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Equal(t, "nested content", resp["content"])
}
@@ -398,9 +398,9 @@ func TestCrowdsec_ListPresets_Success(t *testing.T) {
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
assert.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
presets, ok := resp["presets"].([]interface{})
presets, ok := resp["presets"].([]any)
assert.True(t, ok)
assert.Greater(t, len(presets), 0)
}

View File

@@ -648,7 +648,7 @@ func TestConsoleEnrollSuccess(t *testing.T) {
require.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
// Enrollment request sent, but user must accept on crowdsec.net
require.Equal(t, "pending_acceptance", resp["status"])
@@ -725,7 +725,7 @@ func TestConsoleStatusSuccess(t *testing.T) {
require.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
require.Equal(t, "not_enrolled", resp["status"])
}
@@ -754,7 +754,7 @@ func TestConsoleStatusAfterEnroll(t *testing.T) {
require.Equal(t, http.StatusOK, w2.Code)
var resp map[string]interface{}
var resp map[string]any
require.NoError(t, json.Unmarshal(w2.Body.Bytes(), &resp))
// Enrollment request sent, but user must accept on crowdsec.net
require.Equal(t, "pending_acceptance", resp["status"])
@@ -969,7 +969,7 @@ func TestGetAcquisitionConfigNotFound(t *testing.T) {
if w.Code == http.StatusNotFound {
require.Contains(t, w.Body.String(), "not found")
} else {
var resp map[string]interface{}
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
require.Contains(t, resp, "content")
require.Equal(t, "/etc/crowdsec/acquis.yaml", resp["path"])
@@ -1134,7 +1134,7 @@ func TestDeleteConsoleEnrollmentThenReenroll(t *testing.T) {
req2 := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/console/status", http.NoBody)
r.ServeHTTP(w2, req2)
require.Equal(t, http.StatusOK, w2.Code)
var resp map[string]interface{}
var resp map[string]any
require.NoError(t, json.Unmarshal(w2.Body.Bytes(), &resp))
require.Equal(t, "pending_acceptance", resp["status"])
require.Equal(t, "test-agent-1", resp["agent_name"])
@@ -1150,7 +1150,7 @@ func TestDeleteConsoleEnrollmentThenReenroll(t *testing.T) {
req4 := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/console/status", http.NoBody)
r.ServeHTTP(w4, req4)
require.Equal(t, http.StatusOK, w4.Code)
var resp2 map[string]interface{}
var resp2 map[string]any
require.NoError(t, json.Unmarshal(w4.Body.Bytes(), &resp2))
require.Equal(t, "not_enrolled", resp2["status"])
@@ -1167,7 +1167,7 @@ func TestDeleteConsoleEnrollmentThenReenroll(t *testing.T) {
req6 := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/console/status", http.NoBody)
r.ServeHTTP(w6, req6)
require.Equal(t, http.StatusOK, w6.Code)
var resp3 map[string]interface{}
var resp3 map[string]any
require.NoError(t, json.Unmarshal(w6.Body.Bytes(), &resp3))
require.Equal(t, "pending_acceptance", resp3["status"])
require.Equal(t, "test-agent-2", resp3["agent_name"])
@@ -1200,7 +1200,7 @@ func TestCrowdsecStart_LAPINotReadyTimeout(t *testing.T) {
r.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
require.Equal(t, "started", resp["status"])
require.False(t, resp["lapi_ready"].(bool))

View File

@@ -31,7 +31,7 @@ func TestGetLAPIDecisions_FallbackToCscli(t *testing.T) {
// Should return success (from cscli fallback)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
// Should have decisions array (empty from mock)
@@ -58,7 +58,7 @@ func TestGetLAPIDecisions_EmptyResponse(t *testing.T) {
// Will fallback to cscli which returns empty
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
// Should have decisions array (may be empty)
@@ -83,7 +83,7 @@ func TestCheckLAPIHealth_Handler(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)

View File

@@ -303,7 +303,7 @@ func TestApplyPresetHandlerBackupFailure(t *testing.T) {
require.Equal(t, http.StatusInternalServerError, w.Code)
// Verify response includes backup path for traceability
var response map[string]interface{}
var response map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &response))
_, hasBackup := response["backup"]
require.True(t, hasBackup, "Response should include 'backup' field for diagnostics")
@@ -479,7 +479,7 @@ r.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
require.Equal(t, "pulled", resp["status"])
@@ -520,7 +520,7 @@ r.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
require.Equal(t, "applied", resp["status"])

View File

@@ -73,7 +73,7 @@ func TestPullThenApplyIntegration(t *testing.T) {
require.Equal(t, http.StatusOK, pullResp.Code, "Pull should succeed")
var pullResult map[string]interface{}
var pullResult map[string]any
err = json.Unmarshal(pullResp.Body.Bytes(), &pullResult)
require.NoError(t, err)
require.Equal(t, "pulled", pullResult["status"])
@@ -100,7 +100,7 @@ func TestPullThenApplyIntegration(t *testing.T) {
// This should NOT return "preset not cached" error
require.Equal(t, http.StatusOK, applyResp.Code, "Apply should succeed after pull. Response: %s", applyResp.Body.String())
var applyResult map[string]interface{}
var applyResult map[string]any
err = json.Unmarshal(applyResp.Body.Bytes(), &applyResult)
require.NoError(t, err)
require.Equal(t, "applied", applyResult["status"], "Apply status should be 'applied'")
@@ -144,7 +144,7 @@ func TestApplyWithoutPullReturnsProperError(t *testing.T) {
require.Equal(t, http.StatusInternalServerError, applyResp.Code, "Apply should fail without cache")
var errorResult map[string]interface{}
var errorResult map[string]any
err = json.Unmarshal(applyResp.Body.Bytes(), &errorResult)
require.NoError(t, err)

View File

@@ -265,7 +265,7 @@ func TestStatusResponseFormat(t *testing.T) {
r.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)

View File

@@ -57,7 +57,7 @@ func (h *DomainHandler) Create(c *gin.Context) {
"domain",
"Domain Added",
fmt.Sprintf("Domain %s added", util.SanitizeForLog(domain.Name)),
map[string]interface{}{
map[string]any{
"Name": util.SanitizeForLog(domain.Name),
"Action": "created",
},
@@ -77,7 +77,7 @@ func (h *DomainHandler) Delete(c *gin.Context) {
"domain",
"Domain Deleted",
fmt.Sprintf("Domain %s deleted", util.SanitizeForLog(domain.Name)),
map[string]interface{}{
map[string]any{
"Name": util.SanitizeForLog(domain.Name),
"Action": "deleted",
},

View File

@@ -78,7 +78,7 @@ func TestRemoteServerHandler_Create(t *testing.T) {
handler.RegisterRoutes(router.Group("/api/v1"))
// Test Create
serverData := map[string]interface{}{
serverData := map[string]any{
"name": "New Server",
"provider": "generic",
"host": "192.168.1.100",
@@ -128,7 +128,7 @@ func TestRemoteServerHandler_TestConnection(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var result map[string]interface{}
var result map[string]any
err := json.Unmarshal(w.Body.Bytes(), &result)
assert.NoError(t, err)
assert.False(t, result["reachable"].(bool))
@@ -189,7 +189,7 @@ func TestRemoteServerHandler_Update(t *testing.T) {
handler.RegisterRoutes(router.Group("/api/v1"))
// Test Update
updateData := map[string]interface{}{
updateData := map[string]any{
"name": "Updated Server",
"provider": "generic",
"host": "10.0.0.1",
@@ -293,7 +293,7 @@ func TestProxyHostHandler_Create(t *testing.T) {
handler.RegisterRoutes(router.Group("/api/v1"))
// Test Create
hostData := map[string]interface{}{
hostData := map[string]any{
"name": "New Host",
"domain_names": "new.local",
"forward_scheme": "http",

View File

@@ -773,7 +773,7 @@ func CheckMountedImport(db *gorm.DB, mountPath, caddyBinary, importDir string) e
return nil
}
func mustMarshal(v interface{}) []byte {
func mustMarshal(v any) []byte {
b, _ := json.Marshal(v)
return b
}

View File

@@ -34,7 +34,7 @@ func TestImportUploadSanitizesFilename(t *testing.T) {
logger.Init(true, buf)
maliciousFilename := "../evil\nfile.caddy"
payload := map[string]interface{}{"filename": maliciousFilename, "content": "site { respond \"ok\" }"}
payload := map[string]any{"filename": maliciousFilename, "content": "site { respond \"ok\" }"}
bodyBytes, _ := json.Marshal(payload)
req := httptest.NewRequest(http.MethodPost, "/import/upload", bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")

View File

@@ -44,7 +44,7 @@ func TestImportHandler_GetStatus(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
assert.NoError(t, err)
assert.Equal(t, false, resp["has_pending"])
@@ -65,7 +65,7 @@ func TestImportHandler_GetStatus(t *testing.T) {
err = json.Unmarshal(w.Body.Bytes(), &resp)
assert.NoError(t, err)
assert.Equal(t, true, resp["has_pending"])
session := resp["session"].(map[string]interface{})
session := resp["session"].(map[string]any)
assert.Equal(t, "transient", session["state"])
assert.Equal(t, mountPath, session["source_file"])
@@ -84,7 +84,7 @@ func TestImportHandler_GetStatus(t *testing.T) {
err = json.Unmarshal(w.Body.Bytes(), &resp)
assert.NoError(t, err)
assert.Equal(t, true, resp["has_pending"])
session = resp["session"].(map[string]interface{})
session = resp["session"].(map[string]any)
assert.Equal(t, "pending", session["state"]) // DB session, not transient
}
@@ -114,11 +114,11 @@ func TestImportHandler_GetPreview(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var result map[string]interface{}
var result map[string]any
json.Unmarshal(w.Body.Bytes(), &result)
preview := result["preview"].(map[string]interface{})
hosts := preview["hosts"].([]interface{})
preview := result["preview"].(map[string]any)
hosts := preview["hosts"].([]any)
assert.Len(t, hosts, 1)
// Verify status changed to reviewing
@@ -165,7 +165,7 @@ func TestImportHandler_Commit(t *testing.T) {
}
db.Create(&session)
payload := map[string]interface{}{
payload := map[string]any{
"session_uuid": "test-uuid",
"resolutions": map[string]string{
"example.com": "import",
@@ -248,7 +248,7 @@ func TestImportHandler_GetPreview_WithContent(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var result map[string]interface{}
var result map[string]any
err = json.Unmarshal(w.Body.Bytes(), &result)
assert.NoError(t, err)
@@ -269,7 +269,7 @@ func TestImportHandler_Commit_Errors(t *testing.T) {
assert.Equal(t, http.StatusBadRequest, w.Code)
// Case 2: Session not found
payload := map[string]interface{}{
payload := map[string]any{
"session_uuid": "non-existent",
"resolutions": map[string]string{},
}
@@ -287,7 +287,7 @@ func TestImportHandler_Commit_Errors(t *testing.T) {
}
db.Create(&session)
payload = map[string]interface{}{
payload = map[string]any{
"session_uuid": "invalid-data-uuid",
"resolutions": map[string]string{},
}
@@ -367,7 +367,7 @@ func TestImportHandler_Upload_Failure(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
// The error message comes from Upload -> ImportFile -> "import failed: ..."
assert.Contains(t, resp["error"], "import failed")
@@ -406,11 +406,11 @@ func TestImportHandler_Upload_Conflict(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
// Verify response contains conflict in preview (upload is transient)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
assert.NoError(t, err)
preview := resp["preview"].(map[string]interface{})
conflicts := preview["conflicts"].([]interface{})
preview := resp["preview"].(map[string]any)
conflicts := preview["conflicts"].([]any)
found := false
for _, c := range conflicts {
if c.(string) == "example.com" || strings.Contains(c.(string), "example.com") {
@@ -450,7 +450,7 @@ func TestImportHandler_GetPreview_BackupContent(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var result map[string]interface{}
var result map[string]any
json.Unmarshal(w.Body.Bytes(), &result)
assert.Equal(t, content, result["caddyfile_content"])
@@ -495,18 +495,18 @@ func TestImportHandler_GetPreview_TransientMount(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code, "Response body: %s", w.Body.String())
var result map[string]interface{}
var result map[string]any
err = json.Unmarshal(w.Body.Bytes(), &result)
assert.NoError(t, err)
// Verify transient session
session, ok := result["session"].(map[string]interface{})
session, ok := result["session"].(map[string]any)
assert.True(t, ok, "session should be present in response")
assert.Equal(t, "transient", session["state"])
assert.Equal(t, mountPath, session["source_file"])
// Verify preview contains hosts
preview, ok := result["preview"].(map[string]interface{})
preview, ok := result["preview"].(map[string]any)
assert.True(t, ok, "preview should be present in response")
assert.NotNil(t, preview["hosts"])
@@ -541,13 +541,13 @@ func TestImportHandler_Commit_TransientUpload(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
// Extract session ID
var uploadResp map[string]interface{}
var uploadResp map[string]any
json.Unmarshal(w.Body.Bytes(), &uploadResp)
session := uploadResp["session"].(map[string]interface{})
session := uploadResp["session"].(map[string]any)
sessionID := session["id"].(string)
// Now commit the transient upload
commitPayload := map[string]interface{}{
commitPayload := map[string]any{
"session_uuid": sessionID,
"resolutions": map[string]string{
"uploaded.com": "import",
@@ -594,7 +594,7 @@ func TestImportHandler_Commit_TransientMount(t *testing.T) {
// Commit the mount with a random session ID (transient)
sessionID := uuid.NewString()
commitPayload := map[string]interface{}{
commitPayload := map[string]any{
"session_uuid": sessionID,
"resolutions": map[string]string{
"mounted.com": "import",
@@ -646,9 +646,9 @@ func TestImportHandler_Cancel_TransientUpload(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
// Extract session ID and file path
var uploadResp map[string]interface{}
var uploadResp map[string]any
json.Unmarshal(w.Body.Bytes(), &uploadResp)
session := uploadResp["session"].(map[string]interface{})
session := uploadResp["session"].(map[string]any)
sessionID := session["id"].(string)
sourceFile := session["source_file"].(string)
@@ -691,7 +691,7 @@ func TestImportHandler_Errors(t *testing.T) {
assert.Equal(t, http.StatusBadRequest, w.Code)
// Commit - Session Not Found
body := map[string]interface{}{
body := map[string]any{
"session_uuid": "non-existent",
"resolutions": map[string]string{},
}
@@ -760,12 +760,12 @@ func TestImportHandler_DetectImports(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
assert.NoError(t, err)
assert.Equal(t, tt.hasImport, resp["has_imports"])
imports := resp["imports"].([]interface{})
imports := resp["imports"].([]any)
assert.Len(t, imports, len(tt.imports))
})
}
@@ -801,7 +801,7 @@ func TestImportHandler_UploadMulti(t *testing.T) {
router.POST("/import/upload-multi", handler.UploadMulti)
t.Run("single Caddyfile", func(t *testing.T) {
payload := map[string]interface{}{
payload := map[string]any{
"files": []map[string]string{
{"filename": "Caddyfile", "content": "example.com"},
},
@@ -815,14 +815,14 @@ func TestImportHandler_UploadMulti(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.NotNil(t, resp["session"])
assert.NotNil(t, resp["preview"])
})
t.Run("Caddyfile with site files", func(t *testing.T) {
payload := map[string]interface{}{
payload := map[string]any{
"files": []map[string]string{
{"filename": "Caddyfile", "content": "import sites/*\n"},
{"filename": "sites/site1", "content": "site1.com"},
@@ -838,14 +838,14 @@ func TestImportHandler_UploadMulti(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
session := resp["session"].(map[string]interface{})
session := resp["session"].(map[string]any)
assert.Equal(t, "transient", session["state"])
})
t.Run("missing Caddyfile", func(t *testing.T) {
payload := map[string]interface{}{
payload := map[string]any{
"files": []map[string]string{
{"filename": "sites/site1", "content": "site1.com"},
},
@@ -861,7 +861,7 @@ func TestImportHandler_UploadMulti(t *testing.T) {
})
t.Run("path traversal in filename", func(t *testing.T) {
payload := map[string]interface{}{
payload := map[string]any{
"files": []map[string]string{
{"filename": "Caddyfile", "content": "import sites/*\n"},
{"filename": "../etc/passwd", "content": "sensitive"},
@@ -878,7 +878,7 @@ func TestImportHandler_UploadMulti(t *testing.T) {
})
t.Run("empty file content", func(t *testing.T) {
payload := map[string]interface{}{
payload := map[string]any{
"files": []map[string]string{
{"filename": "Caddyfile", "content": "example.com"},
{"filename": "sites/site1", "content": " "},
@@ -892,7 +892,7 @@ func TestImportHandler_UploadMulti(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Contains(t, resp["error"], "empty")
})

View File

@@ -100,7 +100,7 @@ func TestLogsLifecycle(t *testing.T) {
var content struct {
Filename string `json:"filename"`
Logs []interface{} `json:"logs"`
Logs []any `json:"logs"`
Total int `json:"total"`
}
err = json.Unmarshal(resp.Body.Bytes(), &content)

View File

@@ -25,11 +25,11 @@ var upgrader = websocket.Upgrader{
// LogEntry represents a structured log entry sent over WebSocket.
type LogEntry struct {
Level string `json:"level"`
Message string `json:"message"`
Timestamp string `json:"timestamp"`
Source string `json:"source"`
Fields map[string]interface{} `json:"fields"`
Level string `json:"level"`
Message string `json:"message"`
Timestamp string `json:"timestamp"`
Source string `json:"source"`
Fields map[string]any `json:"fields"`
}
// LogsWSHandler handles WebSocket connections for live log streaming.

View File

@@ -212,7 +212,7 @@ func TestRemoteServerHandler_TestConnectionCustom_Unreachable(t *testing.T) {
svc := services.NewRemoteServerService(db)
h := NewRemoteServerHandler(svc, nil)
body, _ := json.Marshal(map[string]interface{}{
body, _ := json.Marshal(map[string]any{
"host": "192.0.2.1", // TEST-NET - should be unreachable
"port": 65535,
})

View File

@@ -338,9 +338,9 @@ func TestNotificationProviderHandler_Preview_WithData(t *testing.T) {
svc := services.NewNotificationService(db)
h := NewNotificationProviderHandler(svc)
payload := map[string]interface{}{
payload := map[string]any{
"template": "minimal",
"data": map[string]interface{}{
"data": map[string]any{
"Title": "Custom Title",
"Message": "Custom Message",
},
@@ -363,7 +363,7 @@ func TestNotificationProviderHandler_Preview_InvalidTemplate(t *testing.T) {
svc := services.NewNotificationService(db)
h := NewNotificationProviderHandler(svc)
payload := map[string]interface{}{
payload := map[string]any{
"template": "custom",
"config": "{{.Invalid",
}
@@ -524,7 +524,7 @@ func TestNotificationTemplateHandler_Preview_TemplateNotFound(t *testing.T) {
svc := services.NewNotificationService(db)
h := NewNotificationTemplateHandler(svc)
payload := map[string]interface{}{
payload := map[string]any{
"template_id": "nonexistent",
}
body, _ := json.Marshal(payload)
@@ -553,9 +553,9 @@ func TestNotificationTemplateHandler_Preview_WithStoredTemplate(t *testing.T) {
}
require.NoError(t, svc.CreateTemplate(tmpl))
payload := map[string]interface{}{
payload := map[string]any{
"template_id": tmpl.ID,
"data": map[string]interface{}{
"data": map[string]any{
"Title": "Test Title",
},
}
@@ -577,7 +577,7 @@ func TestNotificationTemplateHandler_Preview_InvalidTemplate(t *testing.T) {
svc := services.NewNotificationService(db)
h := NewNotificationTemplateHandler(svc)
payload := map[string]interface{}{
payload := map[string]any{
"template": "{{.Invalid",
}
body, _ := json.Marshal(payload)

View File

@@ -104,7 +104,7 @@ func (h *NotificationProviderHandler) Templates(c *gin.Context) {
// Preview renders the template for a provider and returns the resulting JSON object or an error.
func (h *NotificationProviderHandler) Preview(c *gin.Context) {
var raw map[string]interface{}
var raw map[string]any
if err := c.ShouldBindJSON(&raw); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
@@ -115,13 +115,13 @@ func (h *NotificationProviderHandler) Preview(c *gin.Context) {
if b, err := json.Marshal(raw); err == nil {
_ = json.Unmarshal(b, &provider)
}
var payload map[string]interface{}
if d, ok := raw["data"].(map[string]interface{}); ok {
var payload map[string]any
if d, ok := raw["data"].(map[string]any); ok {
payload = d
}
if payload == nil {
payload = map[string]interface{}{}
payload = map[string]any{}
}
// Add some defaults for preview

View File

@@ -212,7 +212,7 @@ func TestNotificationProviderHandler_Preview(t *testing.T) {
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Contains(t, resp, "rendered")

View File

@@ -1,10 +1,11 @@
package handlers
import (
"net/http"
"github.com/Wikid82/charon/backend/internal/models"
"github.com/Wikid82/charon/backend/internal/services"
"github.com/gin-gonic/gin"
"net/http"
)
type NotificationTemplateHandler struct {
@@ -63,7 +64,7 @@ func (h *NotificationTemplateHandler) Delete(c *gin.Context) {
// Preview allows rendering an arbitrary template (provided in request) or a stored template by id.
func (h *NotificationTemplateHandler) Preview(c *gin.Context) {
var raw map[string]interface{}
var raw map[string]any
if err := c.ShouldBindJSON(&raw); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
@@ -81,8 +82,8 @@ func (h *NotificationTemplateHandler) Preview(c *gin.Context) {
tmplStr = s
}
data := map[string]interface{}{}
if d, ok := raw["data"].(map[string]interface{}); ok {
data := map[string]any{}
if d, ok := raw["data"].(map[string]any); ok {
data = d
}

View File

@@ -71,7 +71,7 @@ func TestNotificationTemplateHandler_CRUDAndPreview(t *testing.T) {
w = httptest.NewRecorder()
r.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
var previewResp map[string]interface{}
var previewResp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &previewResp))
require.NotEmpty(t, previewResp["rendered"])

View File

@@ -84,7 +84,7 @@ func (h *ProxyHostHandler) Create(c *gin.Context) {
// Validate and normalize advanced config if present
if host.AdvancedConfig != "" {
var parsed interface{}
var parsed any
if err := json.Unmarshal([]byte(host.AdvancedConfig), &parsed); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid advanced_config JSON: " + err.Error()})
return
@@ -129,7 +129,7 @@ func (h *ProxyHostHandler) Create(c *gin.Context) {
"proxy_host",
"Proxy Host Created",
fmt.Sprintf("Proxy Host %s (%s) created", util.SanitizeForLog(host.Name), util.SanitizeForLog(host.DomainNames)),
map[string]interface{}{
map[string]any{
"Name": util.SanitizeForLog(host.Name),
"Domains": util.SanitizeForLog(host.DomainNames),
"Action": "created",
@@ -164,7 +164,7 @@ func (h *ProxyHostHandler) Update(c *gin.Context) {
}
// Perform a partial update: only mutate fields present in payload
var payload map[string]interface{}
var payload map[string]any
if err := c.ShouldBindJSON(&payload); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
@@ -334,7 +334,7 @@ func (h *ProxyHostHandler) Update(c *gin.Context) {
}
// Locations: replace only if provided
if v, ok := payload["locations"].([]interface{}); ok {
if v, ok := payload["locations"].([]any); ok {
// Rebind to []models.Location
b, _ := json.Marshal(v)
var locs []models.Location
@@ -355,7 +355,7 @@ func (h *ProxyHostHandler) Update(c *gin.Context) {
// Advanced config: normalize if provided and changed
if v, ok := payload["advanced_config"].(string); ok {
if v != "" && v != host.AdvancedConfig {
var parsed interface{}
var parsed any
if err := json.Unmarshal([]byte(v), &parsed); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid advanced_config JSON: " + err.Error()})
return
@@ -439,7 +439,7 @@ func (h *ProxyHostHandler) Delete(c *gin.Context) {
"proxy_host",
"Proxy Host Deleted",
fmt.Sprintf("Proxy Host %s deleted", host.Name),
map[string]interface{}{
map[string]any{
"Name": host.Name,
"Action": "deleted",
},

View File

@@ -83,7 +83,7 @@ func TestBulkUpdateSecurityHeaders_Success(t *testing.T) {
require.NoError(t, db.Create(&host3).Error)
// Apply profile to all hosts
reqBody := map[string]interface{}{
reqBody := map[string]any{
"host_uuids": []string{host1.UUID, host2.UUID, host3.UUID},
"security_header_profile_id": profile.ID,
}
@@ -96,7 +96,7 @@ func TestBulkUpdateSecurityHeaders_Success(t *testing.T) {
require.Equal(t, http.StatusOK, resp.Code)
var result map[string]interface{}
var result map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &result))
assert.Equal(t, float64(3), result["updated"])
assert.Empty(t, result["errors"])
@@ -150,7 +150,7 @@ func TestBulkUpdateSecurityHeaders_RemoveProfile(t *testing.T) {
require.NoError(t, db.Create(&host2).Error)
// Remove profile from all hosts (set to null)
reqBody := map[string]interface{}{
reqBody := map[string]any{
"host_uuids": []string{host1.UUID, host2.UUID},
"security_header_profile_id": nil,
}
@@ -163,7 +163,7 @@ func TestBulkUpdateSecurityHeaders_RemoveProfile(t *testing.T) {
require.Equal(t, http.StatusOK, resp.Code)
var result map[string]interface{}
var result map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &result))
assert.Equal(t, float64(2), result["updated"])
@@ -192,7 +192,7 @@ func TestBulkUpdateSecurityHeaders_InvalidProfileID(t *testing.T) {
// Try to apply non-existent profile
nonExistentProfileID := uint(99999)
reqBody := map[string]interface{}{
reqBody := map[string]any{
"host_uuids": []string{host.UUID},
"security_header_profile_id": nonExistentProfileID,
}
@@ -205,7 +205,7 @@ func TestBulkUpdateSecurityHeaders_InvalidProfileID(t *testing.T) {
require.Equal(t, http.StatusBadRequest, resp.Code)
var result map[string]interface{}
var result map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &result))
assert.Contains(t, result["error"], "security header profile not found")
}
@@ -214,7 +214,7 @@ func TestBulkUpdateSecurityHeaders_EmptyUUIDs(t *testing.T) {
router, _ := setupTestRouterForSecurityHeaders(t)
// Try to update with empty host UUIDs
reqBody := map[string]interface{}{
reqBody := map[string]any{
"host_uuids": []string{},
"security_header_profile_id": nil,
}
@@ -227,7 +227,7 @@ func TestBulkUpdateSecurityHeaders_EmptyUUIDs(t *testing.T) {
require.Equal(t, http.StatusBadRequest, resp.Code)
var result map[string]interface{}
var result map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &result))
assert.Contains(t, result["error"], "host_uuids cannot be empty")
}
@@ -257,7 +257,7 @@ func TestBulkUpdateSecurityHeaders_PartialFailure(t *testing.T) {
// Include one valid and one invalid UUID
invalidUUID := "non-existent-uuid"
reqBody := map[string]interface{}{
reqBody := map[string]any{
"host_uuids": []string{host1.UUID, invalidUUID},
"security_header_profile_id": profile.ID,
}
@@ -270,16 +270,16 @@ func TestBulkUpdateSecurityHeaders_PartialFailure(t *testing.T) {
require.Equal(t, http.StatusOK, resp.Code)
var result map[string]interface{}
var result map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &result))
assert.Equal(t, float64(1), result["updated"])
// Check errors array
errors, ok := result["errors"].([]interface{})
errors, ok := result["errors"].([]any)
require.True(t, ok)
require.Len(t, errors, 1)
errorMap := errors[0].(map[string]interface{})
errorMap := errors[0].(map[string]any)
assert.Equal(t, invalidUUID, errorMap["uuid"])
assert.Contains(t, errorMap["error"], "proxy host not found")
@@ -296,7 +296,7 @@ func TestBulkUpdateSecurityHeaders_TransactionRollback(t *testing.T) {
// Try to update with all invalid UUIDs
invalidUUID1 := "invalid-uuid-1"
invalidUUID2 := "invalid-uuid-2"
reqBody := map[string]interface{}{
reqBody := map[string]any{
"host_uuids": []string{invalidUUID1, invalidUUID2},
"security_header_profile_id": nil,
}
@@ -309,7 +309,7 @@ func TestBulkUpdateSecurityHeaders_TransactionRollback(t *testing.T) {
require.Equal(t, http.StatusBadRequest, resp.Code)
var result map[string]interface{}
var result map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &result))
assert.Contains(t, result["error"], "All updates failed")
assert.Equal(t, float64(0), result["updated"])
@@ -383,7 +383,7 @@ func TestBulkUpdateSecurityHeaders_MixedProfileStates(t *testing.T) {
require.NoError(t, db.Create(&host3).Error)
// Apply profile2 to all hosts
reqBody := map[string]interface{}{
reqBody := map[string]any{
"host_uuids": []string{host1.UUID, host2.UUID, host3.UUID},
"security_header_profile_id": profile2.ID,
}
@@ -396,7 +396,7 @@ func TestBulkUpdateSecurityHeaders_MixedProfileStates(t *testing.T) {
require.Equal(t, http.StatusOK, resp.Code)
var result map[string]interface{}
var result map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &result))
assert.Equal(t, float64(3), result["updated"])
@@ -438,7 +438,7 @@ func TestBulkUpdateSecurityHeaders_SingleHost(t *testing.T) {
require.NoError(t, db.Create(&host).Error)
// Apply profile to single host
reqBody := map[string]interface{}{
reqBody := map[string]any{
"host_uuids": []string{host.UUID},
"security_header_profile_id": profile.ID,
}
@@ -451,7 +451,7 @@ func TestBulkUpdateSecurityHeaders_SingleHost(t *testing.T) {
require.Equal(t, http.StatusOK, resp.Code)
var result map[string]interface{}
var result map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &result))
assert.Equal(t, float64(1), result["updated"])
assert.Empty(t, result["errors"])

View File

@@ -295,7 +295,7 @@ func TestProxyHostCreate_AdvancedConfig_Normalization(t *testing.T) {
// Provide an advanced_config value that will be normalized by caddy.NormalizeAdvancedConfig
adv := `{"handler":"headers","response":{"set":{"X-Test":"1"}}}`
payload := map[string]interface{}{
payload := map[string]any{
"name": "AdvHost",
"domain_names": "adv.example.com",
"forward_scheme": "http",
@@ -318,7 +318,7 @@ func TestProxyHostCreate_AdvancedConfig_Normalization(t *testing.T) {
require.NotEmpty(t, created.AdvancedConfig)
// Confirm it can be unmarshaled and that headers are normalized to array/strings
var parsed map[string]interface{}
var parsed map[string]any
require.NoError(t, json.Unmarshal([]byte(created.AdvancedConfig), &parsed))
// a basic assertion: ensure 'handler' field exists in parsed config when normalized
require.Contains(t, parsed, "handler")
@@ -513,7 +513,7 @@ func TestProxyHostHandler_BulkUpdateACL_Success(t *testing.T) {
router.ServeHTTP(resp, req)
require.Equal(t, http.StatusOK, resp.Code)
var result map[string]interface{}
var result map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &result))
require.Equal(t, float64(2), result["updated"])
require.Empty(t, result["errors"])
@@ -563,7 +563,7 @@ func TestProxyHostHandler_BulkUpdateACL_RemoveACL(t *testing.T) {
router.ServeHTTP(resp, req)
require.Equal(t, http.StatusOK, resp.Code)
var result map[string]interface{}
var result map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &result))
require.Equal(t, float64(1), result["updated"])
require.Empty(t, result["errors"])
@@ -607,13 +607,13 @@ func TestProxyHostHandler_BulkUpdateACL_PartialFailure(t *testing.T) {
router.ServeHTTP(resp, req)
require.Equal(t, http.StatusOK, resp.Code)
var result map[string]interface{}
var result map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &result))
require.Equal(t, float64(1), result["updated"])
errors := result["errors"].([]interface{})
errors := result["errors"].([]any)
require.Len(t, errors, 1)
errorMap := errors[0].(map[string]interface{})
errorMap := errors[0].(map[string]any)
require.Equal(t, nonExistentUUID, errorMap["uuid"])
require.Equal(t, "proxy host not found", errorMap["error"])
@@ -635,7 +635,7 @@ func TestProxyHostHandler_BulkUpdateACL_EmptyUUIDs(t *testing.T) {
router.ServeHTTP(resp, req)
require.Equal(t, http.StatusBadRequest, resp.Code)
var result map[string]interface{}
var result map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &result))
require.Contains(t, result["error"], "host_uuids cannot be empty")
}
@@ -883,7 +883,7 @@ func TestProxyHostCreate_WithCertificateAndLocations(t *testing.T) {
require.NoError(t, db.Create(cert).Error)
adv := `{"handler":"headers","response":{"set":{"X-Test":"1"}}}`
payload := map[string]interface{}{
payload := map[string]any{
"name": "Create With Cert",
"domain_names": "cert.example.com",
"forward_scheme": "http",
@@ -891,7 +891,7 @@ func TestProxyHostCreate_WithCertificateAndLocations(t *testing.T) {
"forward_port": 8080,
"enabled": true,
"certificate_id": cert.ID,
"locations": []map[string]interface{}{{"path": "/app", "forward_scheme": "http", "forward_host": "localhost", "forward_port": 8080}},
"locations": []map[string]any{{"path": "/app", "forward_scheme": "http", "forward_host": "localhost", "forward_port": 8080}},
"advanced_config": adv,
}
body, _ := json.Marshal(payload)
@@ -930,7 +930,7 @@ func TestProxyHostCreate_WithSecurityHeaderProfile(t *testing.T) {
require.NoError(t, db.Create(profile).Error)
// Create proxy host with security_header_profile_id
payload := map[string]interface{}{
payload := map[string]any{
"name": "Host With Security Profile",
"domain_names": "secure.example.com",
"forward_scheme": "http",
@@ -1303,7 +1303,7 @@ func TestProxyHostUpdate_SecurityHeaderProfile_InvalidString(t *testing.T) {
router.ServeHTTP(resp, req)
require.Equal(t, http.StatusBadRequest, resp.Code)
var result map[string]interface{}
var result map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &result))
require.Contains(t, result["error"], "invalid security_header_profile_id")
}
@@ -1334,7 +1334,7 @@ func TestProxyHostUpdate_SecurityHeaderProfile_InvalidFloat(t *testing.T) {
router.ServeHTTP(resp, req)
require.Equal(t, http.StatusBadRequest, resp.Code)
var result map[string]interface{}
var result map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &result))
require.Contains(t, result["error"], "invalid security_header_profile_id")
}
@@ -1407,7 +1407,7 @@ func TestProxyHostUpdate_SecurityHeaderProfile_UnsupportedType(t *testing.T) {
router.ServeHTTP(resp, req)
require.Equal(t, http.StatusBadRequest, resp.Code)
var result map[string]interface{}
var result map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &result))
require.Contains(t, result["error"], "invalid security_header_profile_id")
}

View File

@@ -74,7 +74,7 @@ func (h *RemoteServerHandler) Create(c *gin.Context) {
"remote_server",
"Remote Server Added",
fmt.Sprintf("Remote Server %s (%s:%d) added", util.SanitizeForLog(server.Name), util.SanitizeForLog(server.Host), server.Port),
map[string]interface{}{
map[string]any{
"Name": util.SanitizeForLog(server.Name),
"Host": util.SanitizeForLog(server.Host),
"Port": server.Port,
@@ -143,7 +143,7 @@ func (h *RemoteServerHandler) Delete(c *gin.Context) {
"remote_server",
"Remote Server Deleted",
fmt.Sprintf("Remote Server %s deleted", util.SanitizeForLog(server.Name)),
map[string]interface{}{
map[string]any{
"Name": util.SanitizeForLog(server.Name),
"Action": "deleted",
},

View File

@@ -43,7 +43,7 @@ func TestRemoteServerHandler_TestConnectionCustom(t *testing.T) {
r, _ := setupRemoteServerTest_New(t)
// Test with a likely closed port
payload := map[string]interface{}{
payload := map[string]any{
"host": "127.0.0.1",
"port": 54321,
}
@@ -54,7 +54,7 @@ func TestRemoteServerHandler_TestConnectionCustom(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var result map[string]interface{}
var result map[string]any
err := json.Unmarshal(w.Body.Bytes(), &result)
require.NoError(t, err)
assert.Equal(t, false, result["reachable"])

View File

@@ -477,7 +477,7 @@ func (h *SecurityHandler) Disable(c *gin.Context) {
// GetRateLimitPresets returns predefined rate limit configurations
func (h *SecurityHandler) GetRateLimitPresets(c *gin.Context) {
presets := []map[string]interface{}{
presets := []map[string]any{
{
"id": "standard",
"name": "Standard Web",
@@ -518,17 +518,17 @@ func (h *SecurityHandler) GetRateLimitPresets(c *gin.Context) {
func (h *SecurityHandler) GetGeoIPStatus(c *gin.Context) {
if h.geoipSvc == nil {
c.JSON(http.StatusOK, gin.H{
"loaded": false,
"message": "GeoIP service not initialized",
"db_path": "",
"loaded": false,
"message": "GeoIP service not initialized",
"db_path": "",
})
return
}
c.JSON(http.StatusOK, gin.H{
"loaded": h.geoipSvc.IsLoaded(),
"db_path": h.geoipSvc.GetDatabasePath(),
"message": "GeoIP service available",
"loaded": h.geoipSvc.IsLoaded(),
"db_path": h.geoipSvc.GetDatabasePath(),
"message": "GeoIP service available",
})
}

View File

@@ -33,7 +33,7 @@ func TestSecurityHandler_GetConfigAndUpdateConfig(t *testing.T) {
c.Request = req
h.GetConfig(c)
require.Equal(t, http.StatusOK, w.Code)
var body map[string]interface{}
var body map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body))
// Should return config: null
if _, ok := body["config"]; !ok {
@@ -57,9 +57,9 @@ func TestSecurityHandler_GetConfigAndUpdateConfig(t *testing.T) {
c.Request = req
h.GetConfig(c)
require.Equal(t, http.StatusOK, w.Code)
var body2 map[string]interface{}
var body2 map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body2))
cfgVal, ok := body2["config"].(map[string]interface{})
cfgVal, ok := body2["config"].(map[string]any)
if !ok {
t.Fatalf("expected config object, got %v", body2["config"])
}

View File

@@ -72,7 +72,7 @@ func TestSecurityHandler_GetStatus_SQLInjection(t *testing.T) {
// Should return 200 and valid JSON despite malicious data
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
assert.NoError(t, err)
assert.Contains(t, resp, "cerberus")
@@ -134,7 +134,7 @@ func TestSecurityHandler_UpsertRuleSet_MassivePayload(t *testing.T) {
// Try to submit a 3MB payload (should be rejected by service)
hugeContent := strings.Repeat("SecRule REQUEST_URI \"@contains /admin\" \"id:1000,phase:1,deny\"\n", 50000)
payload := map[string]interface{}{
payload := map[string]any{
"name": "huge-ruleset",
"content": hugeContent,
}
@@ -163,7 +163,7 @@ func TestSecurityHandler_UpsertRuleSet_EmptyName(t *testing.T) {
router := gin.New()
router.POST("/api/v1/security/rulesets", h.UpsertRuleSet)
payload := map[string]interface{}{
payload := map[string]any{
"name": "",
"content": "SecRule REQUEST_URI \"@contains /admin\"",
}
@@ -176,7 +176,7 @@ func TestSecurityHandler_UpsertRuleSet_EmptyName(t *testing.T) {
assert.Equal(t, http.StatusBadRequest, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Contains(t, resp, "error")
}
@@ -264,7 +264,7 @@ func TestSecurityHandler_GetStatus_SettingsOverride(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]map[string]interface{}
var resp map[string]map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
@@ -310,7 +310,7 @@ func TestSecurityHandler_GetStatus_DisabledViaSettings(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]map[string]interface{}
var resp map[string]map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
@@ -379,7 +379,7 @@ func TestSecurityHandler_UpsertRuleSet_XSSInContent(t *testing.T) {
// Store content with XSS payload
xssPayload := `<script>alert('XSS')</script>`
payload := map[string]interface{}{
payload := map[string]any{
"name": "xss-test",
"content": xssPayload,
}
@@ -423,27 +423,27 @@ func TestSecurityHandler_UpdateConfig_RateLimitBounds(t *testing.T) {
testCases := []struct {
name string
payload map[string]interface{}
payload map[string]any
wantOK bool
}{
{
"valid_limits",
map[string]interface{}{"rate_limit_requests": 100, "rate_limit_burst": 10, "rate_limit_window_sec": 60},
map[string]any{"rate_limit_requests": 100, "rate_limit_burst": 10, "rate_limit_window_sec": 60},
true,
},
{
"zero_requests",
map[string]interface{}{"rate_limit_requests": 0, "rate_limit_burst": 10},
map[string]any{"rate_limit_requests": 0, "rate_limit_burst": 10},
true, // Backend accepts, frontend validates
},
{
"negative_burst",
map[string]interface{}{"rate_limit_requests": 100, "rate_limit_burst": -1},
map[string]any{"rate_limit_requests": 100, "rate_limit_burst": -1},
true, // Backend accepts, frontend validates
},
{
"huge_values",
map[string]interface{}{"rate_limit_requests": 999999999, "rate_limit_burst": 999999999},
map[string]any{"rate_limit_requests": 999999999, "rate_limit_burst": 999999999},
true, // Backend accepts (no upper bound validation currently)
},
}
@@ -577,7 +577,7 @@ func TestSecurityHandler_GetStatus_CrowdSecModeValidation(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]map[string]interface{}
var resp map[string]map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
// Invalid modes should be normalized to "disabled"

View File

@@ -50,7 +50,7 @@ func TestSecurityHandler_GetStatus_Clean(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
// response body intentionally not printed in clean test
@@ -76,10 +76,10 @@ func TestSecurityHandler_Cerberus_DBOverride(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
cerb := response["cerberus"].(map[string]interface{})
cerb := response["cerberus"].(map[string]any)
assert.Equal(t, true, cerb["enabled"].(bool))
}
@@ -112,10 +112,10 @@ func TestSecurityHandler_ACL_DBOverride(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
acl := response["acl"].(map[string]interface{})
acl := response["acl"].(map[string]any)
assert.Equal(t, true, acl["enabled"].(bool))
}
@@ -130,7 +130,7 @@ func TestSecurityHandler_GenerateBreakGlass_ReturnsToken(t *testing.T) {
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{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
assert.NoError(t, err)
token, ok := resp["token"].(string)
@@ -160,12 +160,12 @@ func TestSecurityHandler_ACL_DisabledWhenCerberusOff(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
cerb := response["cerberus"].(map[string]interface{})
cerb := response["cerberus"].(map[string]any)
assert.Equal(t, false, cerb["enabled"].(bool))
acl := response["acl"].(map[string]interface{})
acl := response["acl"].(map[string]any)
// ACL must be false because Cerberus is disabled
assert.Equal(t, false, acl["enabled"].(bool))
}
@@ -189,10 +189,10 @@ func TestSecurityHandler_CrowdSec_Mode_DBOverride(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
cs := response["crowdsec"].(map[string]interface{})
cs := response["crowdsec"].(map[string]any)
assert.Equal(t, "local", cs["mode"].(string))
}
@@ -212,10 +212,10 @@ func TestSecurityHandler_CrowdSec_ExternalMappedToDisabled_DBOverride(t *testing
req, _ := http.NewRequest("GET", "/security/status", http.NoBody)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
cs := response["crowdsec"].(map[string]interface{})
cs := response["crowdsec"].(map[string]any)
assert.Equal(t, "disabled", cs["mode"].(string))
assert.Equal(t, false, cs["enabled"].(bool))
}
@@ -236,10 +236,10 @@ func TestSecurityHandler_ExternalModeMappedToDisabled(t *testing.T) {
req, _ := http.NewRequest("GET", "/security/status", http.NoBody)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
cs := response["crowdsec"].(map[string]interface{})
cs := response["crowdsec"].(map[string]any)
assert.Equal(t, "disabled", cs["mode"].(string))
assert.Equal(t, false, cs["enabled"].(bool))
}

View File

@@ -28,7 +28,7 @@ func TestSecurityHandler_UpdateConfig_Success(t *testing.T) {
router := gin.New()
router.POST("/security/config", handler.UpdateConfig)
payload := map[string]interface{}{
payload := map[string]any{
"name": "default",
"admin_whitelist": "192.168.1.0/24",
"waf_mode": "monitor",
@@ -41,7 +41,7 @@ func TestSecurityHandler_UpdateConfig_Success(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.NotNil(t, resp["config"])
@@ -57,7 +57,7 @@ func TestSecurityHandler_UpdateConfig_DefaultName(t *testing.T) {
router.POST("/security/config", handler.UpdateConfig)
// Payload without name - should default to "default"
payload := map[string]interface{}{
payload := map[string]any{
"admin_whitelist": "10.0.0.0/8",
}
body, _ := json.Marshal(payload)
@@ -106,7 +106,7 @@ func TestSecurityHandler_GetConfig_Success(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.NotNil(t, resp["config"])
@@ -126,7 +126,7 @@ func TestSecurityHandler_GetConfig_NotFound(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Nil(t, resp["config"])
@@ -151,10 +151,10 @@ func TestSecurityHandler_ListDecisions_Success(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
decisions := resp["decisions"].([]interface{})
decisions := resp["decisions"].([]any)
assert.Len(t, decisions, 2)
}
@@ -177,10 +177,10 @@ func TestSecurityHandler_ListDecisions_WithLimit(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
decisions := resp["decisions"].([]interface{})
decisions := resp["decisions"].([]any)
assert.Len(t, decisions, 2)
}
@@ -194,7 +194,7 @@ func TestSecurityHandler_CreateDecision_Success(t *testing.T) {
router := gin.New()
router.POST("/security/decisions", handler.CreateDecision)
payload := map[string]interface{}{
payload := map[string]any{
"ip": "10.0.0.1",
"action": "block",
"reason": "manual block",
@@ -219,7 +219,7 @@ func TestSecurityHandler_CreateDecision_MissingIP(t *testing.T) {
router := gin.New()
router.POST("/security/decisions", handler.CreateDecision)
payload := map[string]interface{}{
payload := map[string]any{
"action": "block",
}
body, _ := json.Marshal(payload)
@@ -241,7 +241,7 @@ func TestSecurityHandler_CreateDecision_MissingAction(t *testing.T) {
router := gin.New()
router.POST("/security/decisions", handler.CreateDecision)
payload := map[string]interface{}{
payload := map[string]any{
"ip": "10.0.0.1",
}
body, _ := json.Marshal(payload)
@@ -290,10 +290,10 @@ func TestSecurityHandler_ListRuleSets_Success(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
rulesets := resp["rulesets"].([]interface{})
rulesets := resp["rulesets"].([]any)
assert.Len(t, rulesets, 2)
}
@@ -307,7 +307,7 @@ func TestSecurityHandler_UpsertRuleSet_Success(t *testing.T) {
router := gin.New()
router.POST("/security/rulesets", handler.UpsertRuleSet)
payload := map[string]interface{}{
payload := map[string]any{
"name": "test-ruleset",
"mode": "blocking",
"content": "# Test rules",
@@ -331,7 +331,7 @@ func TestSecurityHandler_UpsertRuleSet_MissingName(t *testing.T) {
router := gin.New()
router.POST("/security/rulesets", handler.UpsertRuleSet)
payload := map[string]interface{}{
payload := map[string]any{
"mode": "blocking",
"content": "# Test rules",
}
@@ -381,7 +381,7 @@ func TestSecurityHandler_DeleteRuleSet_Success(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.True(t, resp["deleted"].(bool))
@@ -585,7 +585,7 @@ func TestSecurityHandler_Disable_FromLocalhost(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.False(t, resp["enabled"].(bool))
}
@@ -694,7 +694,7 @@ func TestSecurityHandler_GenerateBreakGlass_NoConfig(t *testing.T) {
// Should succeed and create a new config with the token
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.NotEmpty(t, resp["token"])

View File

@@ -19,7 +19,7 @@ func TestSecurityHandler_GetStatus_Fixed(t *testing.T) {
name string
cfg config.SecurityConfig
expectedStatus int
expectedBody map[string]interface{}
expectedBody map[string]any
}{
{
name: "All Disabled",
@@ -30,22 +30,22 @@ func TestSecurityHandler_GetStatus_Fixed(t *testing.T) {
ACLMode: "disabled",
},
expectedStatus: http.StatusOK,
expectedBody: map[string]interface{}{
"cerberus": map[string]interface{}{"enabled": false},
"crowdsec": map[string]interface{}{
expectedBody: map[string]any{
"cerberus": map[string]any{"enabled": false},
"crowdsec": map[string]any{
"mode": "disabled",
"api_url": "",
"enabled": false,
},
"waf": map[string]interface{}{
"waf": map[string]any{
"mode": "disabled",
"enabled": false,
},
"rate_limit": map[string]interface{}{
"rate_limit": map[string]any{
"mode": "disabled",
"enabled": false,
},
"acl": map[string]interface{}{
"acl": map[string]any{
"mode": "disabled",
"enabled": false,
},
@@ -61,22 +61,22 @@ func TestSecurityHandler_GetStatus_Fixed(t *testing.T) {
ACLMode: "enabled",
},
expectedStatus: http.StatusOK,
expectedBody: map[string]interface{}{
"cerberus": map[string]interface{}{"enabled": true},
"crowdsec": map[string]interface{}{
expectedBody: map[string]any{
"cerberus": map[string]any{"enabled": true},
"crowdsec": map[string]any{
"mode": "local",
"api_url": "",
"enabled": true,
},
"waf": map[string]interface{}{
"waf": map[string]any{
"mode": "enabled",
"enabled": true,
},
"rate_limit": map[string]interface{}{
"rate_limit": map[string]any{
"mode": "enabled",
"enabled": true,
},
"acl": map[string]interface{}{
"acl": map[string]any{
"mode": "enabled",
"enabled": true,
},
@@ -96,12 +96,12 @@ func TestSecurityHandler_GetStatus_Fixed(t *testing.T) {
assert.Equal(t, tt.expectedStatus, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
expectedJSON, _ := json.Marshal(tt.expectedBody)
var expectedNormalized map[string]interface{}
var expectedNormalized map[string]any
if err := json.Unmarshal(expectedJSON, &expectedNormalized); err != nil {
t.Fatalf("failed to unmarshal expected JSON: %v", err)
}

View File

@@ -53,7 +53,7 @@ func TestSecurityHandler_CreateAndListDecisionAndRulesets(t *testing.T) {
t.Fatalf("Create decision expected status 200, got %d; body: %s", resp.Code, resp.Body.String())
}
var decisionResp map[string]interface{}
var decisionResp map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &decisionResp))
require.NotNil(t, decisionResp["decision"])
@@ -63,7 +63,7 @@ func TestSecurityHandler_CreateAndListDecisionAndRulesets(t *testing.T) {
if resp.Code != http.StatusOK {
t.Fatalf("Upsert ruleset expected status 200, got %d; body: %s", resp.Code, resp.Body.String())
}
var listResp map[string][]map[string]interface{}
var listResp map[string][]map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &listResp))
require.GreaterOrEqual(t, len(listResp["decisions"]), 1)
@@ -76,7 +76,7 @@ func TestSecurityHandler_CreateAndListDecisionAndRulesets(t *testing.T) {
if resp.Code != http.StatusOK {
t.Fatalf("Upsert ruleset expected status 200, got %d; body: %s", resp.Code, resp.Body.String())
}
var rsResp map[string]interface{}
var rsResp map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &rsResp))
require.NotNil(t, rsResp["ruleset"])
@@ -86,7 +86,7 @@ func TestSecurityHandler_CreateAndListDecisionAndRulesets(t *testing.T) {
if resp.Code != http.StatusOK {
t.Fatalf("List rulesets expected status 200, got %d; body: %s", resp.Code, resp.Body.String())
}
var listRsResp map[string][]map[string]interface{}
var listRsResp map[string][]map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &listRsResp))
require.GreaterOrEqual(t, len(listRsResp["rulesets"]), 1)
@@ -98,7 +98,7 @@ func TestSecurityHandler_CreateAndListDecisionAndRulesets(t *testing.T) {
resp = httptest.NewRecorder()
r.ServeHTTP(resp, req)
assert.Equal(t, http.StatusOK, resp.Code)
var delResp map[string]interface{}
var delResp map[string]any
require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &delResp))
require.Equal(t, true, delResp["deleted"].(bool))
}

View File

@@ -142,20 +142,20 @@ func TestSecurityHandler_GetStatus_RespectsSettingsTable(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
// Check WAF enabled
waf := response["waf"].(map[string]interface{})
waf := response["waf"].(map[string]any)
assert.Equal(t, tt.expectedWAF, waf["enabled"].(bool), "WAF enabled mismatch")
// Check Rate Limit enabled
rateLimit := response["rate_limit"].(map[string]interface{})
rateLimit := response["rate_limit"].(map[string]any)
assert.Equal(t, tt.expectedRate, rateLimit["enabled"].(bool), "Rate Limit enabled mismatch")
// Check CrowdSec enabled
crowdsec := response["crowdsec"].(map[string]interface{})
crowdsec := response["crowdsec"].(map[string]any)
assert.Equal(t, tt.expectedCrowd, crowdsec["enabled"].(bool), "CrowdSec enabled mismatch")
})
}
@@ -185,11 +185,11 @@ func TestSecurityHandler_GetStatus_WAFModeFromSettings(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
waf := response["waf"].(map[string]interface{})
waf := response["waf"].(map[string]any)
// When enabled via settings, mode should reflect "enabled" state
assert.True(t, waf["enabled"].(bool))
}
@@ -218,10 +218,10 @@ func TestSecurityHandler_GetStatus_RateLimitModeFromSettings(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
rateLimit := response["rate_limit"].(map[string]interface{})
rateLimit := response["rate_limit"].(map[string]any)
assert.True(t, rateLimit["enabled"].(bool))
}

View File

@@ -22,7 +22,7 @@ func TestSecurityHandler_GetStatus_Fixed(t *testing.T) {
name string
cfg config.SecurityConfig
expectedStatus int
expectedBody map[string]interface{}
expectedBody map[string]any
}{
{
name: "All Disabled",
@@ -33,22 +33,22 @@ func TestSecurityHandler_GetStatus_Fixed(t *testing.T) {
ACLMode: "disabled",
},
expectedStatus: http.StatusOK,
expectedBody: map[string]interface{}{
"cerberus": map[string]interface{}{"enabled": false},
"crowdsec": map[string]interface{}{
expectedBody: map[string]any{
"cerberus": map[string]any{"enabled": false},
"crowdsec": map[string]any{
"mode": "disabled",
"api_url": "",
"enabled": false,
},
"waf": map[string]interface{}{
"waf": map[string]any{
"mode": "disabled",
"enabled": false,
},
"rate_limit": map[string]interface{}{
"rate_limit": map[string]any{
"mode": "disabled",
"enabled": false,
},
"acl": map[string]interface{}{
"acl": map[string]any{
"mode": "disabled",
"enabled": false,
},
@@ -63,22 +63,22 @@ func TestSecurityHandler_GetStatus_Fixed(t *testing.T) {
ACLMode: "enabled",
},
expectedStatus: http.StatusOK,
expectedBody: map[string]interface{}{
"cerberus": map[string]interface{}{"enabled": true},
"crowdsec": map[string]interface{}{
expectedBody: map[string]any{
"cerberus": map[string]any{"enabled": true},
"crowdsec": map[string]any{
"mode": "local",
"api_url": "",
"enabled": true,
},
"waf": map[string]interface{}{
"waf": map[string]any{
"mode": "enabled",
"enabled": true,
},
"rate_limit": map[string]interface{}{
"rate_limit": map[string]any{
"mode": "enabled",
"enabled": true,
},
"acl": map[string]interface{}{
"acl": map[string]any{
"mode": "enabled",
"enabled": true,
},
@@ -98,12 +98,12 @@ func TestSecurityHandler_GetStatus_Fixed(t *testing.T) {
assert.Equal(t, tt.expectedStatus, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
expectedJSON, _ := json.Marshal(tt.expectedBody)
var expectedNormalized map[string]interface{}
var expectedNormalized map[string]any
if err := json.Unmarshal(expectedJSON, &expectedNormalized); err != nil {
t.Fatalf("failed to unmarshal expected JSON: %v", err)
}

View File

@@ -31,10 +31,10 @@ func TestSecurityHandler_GetWAFExclusions_Empty(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
exclusions := resp["exclusions"].([]interface{})
exclusions := resp["exclusions"].([]any)
assert.Len(t, exclusions, 0)
}
@@ -57,14 +57,14 @@ func TestSecurityHandler_GetWAFExclusions_WithExclusions(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
exclusions := resp["exclusions"].([]interface{})
exclusions := resp["exclusions"].([]any)
assert.Len(t, exclusions, 2)
// Verify first exclusion
first := exclusions[0].(map[string]interface{})
first := exclusions[0].(map[string]any)
assert.Equal(t, float64(942100), first["rule_id"])
assert.Equal(t, "SQL Injection rule", first["description"])
}
@@ -88,10 +88,10 @@ func TestSecurityHandler_GetWAFExclusions_InvalidJSON(t *testing.T) {
// Should return empty array on parse failure
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
exclusions := resp["exclusions"].([]interface{})
exclusions := resp["exclusions"].([]any)
assert.Len(t, exclusions, 0)
}
@@ -105,7 +105,7 @@ func TestSecurityHandler_AddWAFExclusion_Success(t *testing.T) {
router := gin.New()
router.POST("/security/waf/exclusions", handler.AddWAFExclusion)
payload := map[string]interface{}{
payload := map[string]any{
"rule_id": 942100,
"description": "SQL Injection false positive",
}
@@ -117,11 +117,11 @@ func TestSecurityHandler_AddWAFExclusion_Success(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
exclusion := resp["exclusion"].(map[string]interface{})
exclusion := resp["exclusion"].(map[string]any)
assert.Equal(t, float64(942100), exclusion["rule_id"])
assert.Equal(t, "SQL Injection false positive", exclusion["description"])
}
@@ -135,7 +135,7 @@ func TestSecurityHandler_AddWAFExclusion_WithTarget(t *testing.T) {
router := gin.New()
router.POST("/security/waf/exclusions", handler.AddWAFExclusion)
payload := map[string]interface{}{
payload := map[string]any{
"rule_id": 942100,
"target": "ARGS:password",
"description": "Skip password field for SQL injection",
@@ -148,11 +148,11 @@ func TestSecurityHandler_AddWAFExclusion_WithTarget(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
exclusion := resp["exclusion"].(map[string]interface{})
exclusion := resp["exclusion"].(map[string]any)
assert.Equal(t, "ARGS:password", exclusion["target"])
}
@@ -172,7 +172,7 @@ func TestSecurityHandler_AddWAFExclusion_ToExistingConfig(t *testing.T) {
router.GET("/security/waf/exclusions", handler.GetWAFExclusions)
// Add new exclusion
payload := map[string]interface{}{
payload := map[string]any{
"rule_id": 942100,
"description": "SQL Injection rule",
}
@@ -190,9 +190,9 @@ func TestSecurityHandler_AddWAFExclusion_ToExistingConfig(t *testing.T) {
req, _ = http.NewRequest("GET", "/security/waf/exclusions", http.NoBody)
router.ServeHTTP(w, req)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
exclusions := resp["exclusions"].([]interface{})
exclusions := resp["exclusions"].([]any)
assert.Len(t, exclusions, 2)
}
@@ -211,7 +211,7 @@ func TestSecurityHandler_AddWAFExclusion_Duplicate(t *testing.T) {
router.POST("/security/waf/exclusions", handler.AddWAFExclusion)
// Try to add duplicate
payload := map[string]interface{}{
payload := map[string]any{
"rule_id": 942100,
"description": "Another description",
}
@@ -240,7 +240,7 @@ func TestSecurityHandler_AddWAFExclusion_DuplicateWithDifferentTarget(t *testing
router.POST("/security/waf/exclusions", handler.AddWAFExclusion)
// Add same rule_id with different target - should succeed
payload := map[string]interface{}{
payload := map[string]any{
"rule_id": 942100,
"target": "ARGS:password",
}
@@ -263,7 +263,7 @@ func TestSecurityHandler_AddWAFExclusion_MissingRuleID(t *testing.T) {
router := gin.New()
router.POST("/security/waf/exclusions", handler.AddWAFExclusion)
payload := map[string]interface{}{
payload := map[string]any{
"description": "Missing rule_id",
}
body, _ := json.Marshal(payload)
@@ -286,7 +286,7 @@ func TestSecurityHandler_AddWAFExclusion_InvalidRuleID(t *testing.T) {
router.POST("/security/waf/exclusions", handler.AddWAFExclusion)
// Zero rule_id
payload := map[string]interface{}{
payload := map[string]any{
"rule_id": 0,
}
body, _ := json.Marshal(payload)
@@ -308,7 +308,7 @@ func TestSecurityHandler_AddWAFExclusion_NegativeRuleID(t *testing.T) {
router := gin.New()
router.POST("/security/waf/exclusions", handler.AddWAFExclusion)
payload := map[string]interface{}{
payload := map[string]any{
"rule_id": -1,
}
body, _ := json.Marshal(payload)
@@ -359,7 +359,7 @@ func TestSecurityHandler_DeleteWAFExclusion_Success(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.True(t, resp["deleted"].(bool))
@@ -369,9 +369,9 @@ func TestSecurityHandler_DeleteWAFExclusion_Success(t *testing.T) {
router.ServeHTTP(w, req)
json.Unmarshal(w.Body.Bytes(), &resp)
exclusions := resp["exclusions"].([]interface{})
exclusions := resp["exclusions"].([]any)
assert.Len(t, exclusions, 1)
first := exclusions[0].(map[string]interface{})
first := exclusions[0].(map[string]any)
assert.Equal(t, float64(941100), first["rule_id"])
}
@@ -402,11 +402,11 @@ func TestSecurityHandler_DeleteWAFExclusion_WithTarget(t *testing.T) {
req, _ = http.NewRequest("GET", "/security/waf/exclusions", http.NoBody)
router.ServeHTTP(w, req)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
exclusions := resp["exclusions"].([]interface{})
exclusions := resp["exclusions"].([]any)
assert.Len(t, exclusions, 1)
first := exclusions[0].(map[string]interface{})
first := exclusions[0].(map[string]any)
assert.Equal(t, float64(942100), first["rule_id"])
assert.Empty(t, first["target"])
}
@@ -513,12 +513,12 @@ func TestSecurityHandler_WAFExclusion_FullWorkflow(t *testing.T) {
req, _ := http.NewRequest("GET", "/security/waf/exclusions", http.NoBody)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Len(t, resp["exclusions"].([]interface{}), 0)
assert.Len(t, resp["exclusions"].([]any), 0)
// Step 2: Add first exclusion (full rule removal)
payload := map[string]interface{}{
payload := map[string]any{
"rule_id": 942100,
"description": "SQL Injection false positive",
}
@@ -530,7 +530,7 @@ func TestSecurityHandler_WAFExclusion_FullWorkflow(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
// Step 3: Add second exclusion (targeted)
payload = map[string]interface{}{
payload = map[string]any{
"rule_id": 941100,
"target": "ARGS:content",
"description": "XSS false positive in content field",
@@ -547,7 +547,7 @@ func TestSecurityHandler_WAFExclusion_FullWorkflow(t *testing.T) {
req, _ = http.NewRequest("GET", "/security/waf/exclusions", http.NoBody)
router.ServeHTTP(w, req)
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Len(t, resp["exclusions"].([]interface{}), 2)
assert.Len(t, resp["exclusions"].([]any), 2)
// Step 5: Delete first exclusion
w = httptest.NewRecorder()
@@ -560,9 +560,9 @@ func TestSecurityHandler_WAFExclusion_FullWorkflow(t *testing.T) {
req, _ = http.NewRequest("GET", "/security/waf/exclusions", http.NoBody)
router.ServeHTTP(w, req)
json.Unmarshal(w.Body.Bytes(), &resp)
exclusions := resp["exclusions"].([]interface{})
exclusions := resp["exclusions"].([]any)
assert.Len(t, exclusions, 1)
first := exclusions[0].(map[string]interface{})
first := exclusions[0].(map[string]any)
assert.Equal(t, float64(941100), first["rule_id"])
assert.Equal(t, "ARGS:content", first["target"])
}
@@ -683,7 +683,7 @@ func TestSecurityConfig_WAFExclusions_JSONArray(t *testing.T) {
assert.Equal(t, exclusions, retrieved.WAFExclusions)
// Verify it can be parsed
var parsed []map[string]interface{}
var parsed []map[string]any
err := json.Unmarshal([]byte(retrieved.WAFExclusions), &parsed)
require.NoError(t, err)
assert.Len(t, parsed, 1)

View File

@@ -118,7 +118,7 @@ func TestGetProfile_NotFound(t *testing.T) {
func TestCreateProfile(t *testing.T) {
router, _ := setupSecurityHeadersTestRouter(t)
payload := map[string]interface{}{
payload := map[string]any{
"name": "New Profile",
"hsts_enabled": true,
"hsts_max_age": 31536000,
@@ -145,7 +145,7 @@ func TestCreateProfile(t *testing.T) {
func TestCreateProfile_MissingName(t *testing.T) {
router, _ := setupSecurityHeadersTestRouter(t)
payload := map[string]interface{}{
payload := map[string]any{
"hsts_enabled": true,
}
@@ -167,7 +167,7 @@ func TestUpdateProfile(t *testing.T) {
}
db.Create(&profile)
updates := map[string]interface{}{
updates := map[string]any{
"name": "Updated Name",
"hsts_enabled": false,
"csp_enabled": true,
@@ -200,7 +200,7 @@ func TestUpdateProfile_CannotModifyPreset(t *testing.T) {
}
db.Create(&preset)
updates := map[string]interface{}{
updates := map[string]any{
"name": "Modified Preset",
}
@@ -305,7 +305,7 @@ func TestGetPresets(t *testing.T) {
func TestApplyPreset(t *testing.T) {
router, _ := setupSecurityHeadersTestRouter(t)
payload := map[string]interface{}{
payload := map[string]any{
"preset_type": "basic",
"name": "My Basic Profile",
}
@@ -330,7 +330,7 @@ func TestApplyPreset(t *testing.T) {
func TestApplyPreset_InvalidType(t *testing.T) {
router, _ := setupSecurityHeadersTestRouter(t)
payload := map[string]interface{}{
payload := map[string]any{
"preset_type": "nonexistent",
"name": "Test",
}
@@ -347,7 +347,7 @@ func TestApplyPreset_InvalidType(t *testing.T) {
func TestCalculateScore(t *testing.T) {
router, _ := setupSecurityHeadersTestRouter(t)
payload := map[string]interface{}{
payload := map[string]any{
"hsts_enabled": true,
"hsts_max_age": 31536000,
"hsts_include_subdomains": true,
@@ -371,7 +371,7 @@ func TestCalculateScore(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, float64(100), response["score"])
@@ -382,7 +382,7 @@ func TestCalculateScore(t *testing.T) {
func TestValidateCSP_Valid(t *testing.T) {
router, _ := setupSecurityHeadersTestRouter(t)
payload := map[string]interface{}{
payload := map[string]any{
"csp": `{"default-src":["'self'"],"script-src":["'self'"]}`,
}
@@ -394,7 +394,7 @@ func TestValidateCSP_Valid(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.True(t, response["valid"].(bool))
@@ -403,7 +403,7 @@ func TestValidateCSP_Valid(t *testing.T) {
func TestValidateCSP_Invalid(t *testing.T) {
router, _ := setupSecurityHeadersTestRouter(t)
payload := map[string]interface{}{
payload := map[string]any{
"csp": `not valid json`,
}
@@ -415,7 +415,7 @@ func TestValidateCSP_Invalid(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.False(t, response["valid"].(bool))
@@ -425,7 +425,7 @@ func TestValidateCSP_Invalid(t *testing.T) {
func TestValidateCSP_UnsafeDirectives(t *testing.T) {
router, _ := setupSecurityHeadersTestRouter(t)
payload := map[string]interface{}{
payload := map[string]any{
"csp": `{"default-src":["'self'"],"script-src":["'self'","'unsafe-inline'","'unsafe-eval'"]}`,
}
@@ -437,19 +437,19 @@ func TestValidateCSP_UnsafeDirectives(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.False(t, response["valid"].(bool))
errors := response["errors"].([]interface{})
errors := response["errors"].([]any)
assert.NotEmpty(t, errors)
}
func TestBuildCSP(t *testing.T) {
router, _ := setupSecurityHeadersTestRouter(t)
payload := map[string]interface{}{
"directives": []map[string]interface{}{
payload := map[string]any{
"directives": []map[string]any{
{
"directive": "default-src",
"values": []string{"'self'"},

View File

@@ -99,11 +99,11 @@ func TestSecurityHandler_Priority_SettingsOverSecurityConfig(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
waf := response["waf"].(map[string]interface{})
waf := response["waf"].(map[string]any)
assert.Equal(t, tt.expectedWAFMode, waf["mode"].(string), "WAF mode mismatch")
assert.Equal(t, tt.expectedWAFEnable, waf["enabled"].(bool), "WAF enabled mismatch")
})
@@ -156,21 +156,21 @@ func TestSecurityHandler_Priority_AllModules(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
// Verify Settings table took precedence
waf := response["waf"].(map[string]interface{})
waf := response["waf"].(map[string]any)
assert.True(t, waf["enabled"].(bool), "WAF should be enabled via settings")
rateLimit := response["rate_limit"].(map[string]interface{})
rateLimit := response["rate_limit"].(map[string]any)
assert.False(t, rateLimit["enabled"].(bool), "Rate Limit should be disabled via settings")
crowdsec := response["crowdsec"].(map[string]interface{})
crowdsec := response["crowdsec"].(map[string]any)
assert.Equal(t, "disabled", crowdsec["mode"].(string), "CrowdSec should be disabled via settings")
assert.False(t, crowdsec["enabled"].(bool))
acl := response["acl"].(map[string]interface{})
acl := response["acl"].(map[string]any)
assert.True(t, acl["enabled"].(bool), "ACL should be enabled via settings")
}

View File

@@ -27,18 +27,18 @@ func TestSecurityHandler_GetRateLimitPresets(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
presets, ok := response["presets"].([]interface{})
presets, ok := response["presets"].([]any)
require.True(t, ok, "presets should be an array")
require.Len(t, presets, 4, "should have 4 presets")
// Verify preset structure
expectedIDs := []string{"standard", "api", "login", "relaxed"}
for i, p := range presets {
preset := p.(map[string]interface{})
preset := p.(map[string]any)
assert.Equal(t, expectedIDs[i], preset["id"])
assert.NotEmpty(t, preset["name"])
assert.NotEmpty(t, preset["description"])
@@ -60,12 +60,12 @@ func TestSecurityHandler_GetRateLimitPresets_StandardPreset(t *testing.T) {
req, _ := http.NewRequest("GET", "/security/rate-limit/presets", http.NoBody)
router.ServeHTTP(w, req)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
presets := response["presets"].([]interface{})
standardPreset := presets[0].(map[string]interface{})
presets := response["presets"].([]any)
standardPreset := presets[0].(map[string]any)
assert.Equal(t, "standard", standardPreset["id"])
assert.Equal(t, "Standard Web", standardPreset["name"])
@@ -86,12 +86,12 @@ func TestSecurityHandler_GetRateLimitPresets_LoginPreset(t *testing.T) {
req, _ := http.NewRequest("GET", "/security/rate-limit/presets", http.NoBody)
router.ServeHTTP(w, req)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
presets := response["presets"].([]interface{})
loginPreset := presets[2].(map[string]interface{})
presets := response["presets"].([]any)
loginPreset := presets[2].(map[string]any)
assert.Equal(t, "login", loginPreset["id"])
assert.Equal(t, "Login Protection", loginPreset["name"])

View File

@@ -153,7 +153,7 @@ func TestSettingsHandler_GetSMTPConfig(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Equal(t, "smtp.example.com", resp["host"])
assert.Equal(t, float64(587), resp["port"])
@@ -174,7 +174,7 @@ func TestSettingsHandler_GetSMTPConfig_Empty(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Equal(t, false, resp["configured"])
}
@@ -206,7 +206,7 @@ func TestSettingsHandler_UpdateSMTPConfig_NonAdmin(t *testing.T) {
})
router.PUT("/settings/smtp", handler.UpdateSMTPConfig)
body := map[string]interface{}{
body := map[string]any{
"host": "smtp.example.com",
"port": 587,
"from_address": "test@example.com",
@@ -251,7 +251,7 @@ func TestSettingsHandler_UpdateSMTPConfig_Success(t *testing.T) {
})
router.PUT("/settings/smtp", handler.UpdateSMTPConfig)
body := map[string]interface{}{
body := map[string]any{
"host": "smtp.example.com",
"port": 587,
"username": "user@example.com",
@@ -287,7 +287,7 @@ func TestSettingsHandler_UpdateSMTPConfig_KeepExistingPassword(t *testing.T) {
router.PUT("/settings/smtp", handler.UpdateSMTPConfig)
// Send masked password (simulating frontend sending back masked value)
body := map[string]interface{}{
body := map[string]any{
"host": "smtp.example.com",
"port": 587,
"password": "********", // Masked
@@ -342,7 +342,7 @@ func TestSettingsHandler_TestSMTPConfig_NotConfigured(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Equal(t, false, resp["success"])
}
@@ -406,7 +406,7 @@ func TestSettingsHandler_SendTestEmail_NotConfigured(t *testing.T) {
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Equal(t, false, resp["success"])
}

View File

@@ -42,7 +42,7 @@ func (h *UptimeHandler) GetHistory(c *gin.Context) {
func (h *UptimeHandler) Update(c *gin.Context) {
id := c.Param("id")
var updates map[string]interface{}
var updates map[string]any
if err := c.ShouldBindJSON(&updates); err != nil {
logger.Log().WithError(err).WithField("monitor_id", id).Warn("Invalid JSON payload for monitor update")
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})

View File

@@ -138,7 +138,7 @@ func TestUptimeHandler_Update(t *testing.T) {
}
db.Create(&monitor)
updates := map[string]interface{}{
updates := map[string]any{
"interval": 60,
"max_retries": 5,
}
@@ -172,7 +172,7 @@ func TestUptimeHandler_Update(t *testing.T) {
t.Run("not_found", func(t *testing.T) {
r, _ := setupUptimeHandlerTest(t)
updates := map[string]interface{}{
updates := map[string]any{
"interval": 60,
}
body, _ := json.Marshal(updates)
@@ -226,7 +226,7 @@ func TestUptimeHandler_DeleteAndSync(t *testing.T) {
monitor := models.UptimeMonitor{ID: "mon-enable", Name: "ToToggle", Type: "http", URL: "http://example.com", Enabled: true}
db.Create(&monitor)
updates := map[string]interface{}{"enabled": false}
updates := map[string]any{"enabled": false}
body, _ := json.Marshal(updates)
req, _ := http.NewRequest("PUT", "/api/v1/uptime/mon-enable", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")

View File

@@ -232,7 +232,7 @@ func (h *UserHandler) UpdateProfile(c *gin.Context) {
}
}
if err := h.DB.Model(&models.User{}).Where("id = ?", userID).Updates(map[string]interface{}{
if err := h.DB.Model(&models.User{}).Where("id = ?", userID).Updates(map[string]any{
"name": req.Name,
"email": req.Email,
}).Error; err != nil {
@@ -600,7 +600,7 @@ func (h *UserHandler) UpdateUser(c *gin.Context) {
return
}
updates := make(map[string]interface{})
updates := make(map[string]any)
if req.Name != "" {
updates["name"] = req.Name
@@ -813,7 +813,7 @@ func (h *UserHandler) AcceptInvite(c *gin.Context) {
return
}
if err := h.DB.Model(&user).Updates(map[string]interface{}{
if err := h.DB.Model(&user).Updates(map[string]any{
"name": req.Name,
"password_hash": user.PasswordHash,
"enabled": true,

View File

@@ -438,7 +438,7 @@ func TestUserHandler_ListUsers_Admin(t *testing.T) {
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var users []map[string]interface{}
var users []map[string]any
json.Unmarshal(w.Body.Bytes(), &users)
assert.Len(t, users, 2)
}
@@ -453,7 +453,7 @@ func TestUserHandler_CreateUser_NonAdmin(t *testing.T) {
})
r.POST("/users", handler.CreateUser)
body := map[string]interface{}{
body := map[string]any{
"email": "new@example.com",
"name": "New User",
"password": "password123",
@@ -477,7 +477,7 @@ func TestUserHandler_CreateUser_Admin(t *testing.T) {
})
r.POST("/users", handler.CreateUser)
body := map[string]interface{}{
body := map[string]any{
"email": "newuser@example.com",
"name": "New User",
"password": "password123",
@@ -523,7 +523,7 @@ func TestUserHandler_CreateUser_DuplicateEmail(t *testing.T) {
})
r.POST("/users", handler.CreateUser)
body := map[string]interface{}{
body := map[string]any{
"email": "existing@example.com",
"name": "New User",
"password": "password123",
@@ -551,7 +551,7 @@ func TestUserHandler_CreateUser_WithPermittedHosts(t *testing.T) {
})
r.POST("/users", handler.CreateUser)
body := map[string]interface{}{
body := map[string]any{
"email": "withhosts@example.com",
"name": "User With Hosts",
"password": "password123",
@@ -649,7 +649,7 @@ func TestUserHandler_UpdateUser_NonAdmin(t *testing.T) {
})
r.PUT("/users/:id", handler.UpdateUser)
body := map[string]interface{}{"name": "Updated"}
body := map[string]any{"name": "Updated"}
jsonBody, _ := json.Marshal(body)
req := httptest.NewRequest("PUT", "/users/1", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json")
@@ -669,7 +669,7 @@ func TestUserHandler_UpdateUser_InvalidID(t *testing.T) {
})
r.PUT("/users/:id", handler.UpdateUser)
body := map[string]interface{}{"name": "Updated"}
body := map[string]any{"name": "Updated"}
jsonBody, _ := json.Marshal(body)
req := httptest.NewRequest("PUT", "/users/invalid", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json")
@@ -712,7 +712,7 @@ func TestUserHandler_UpdateUser_NotFound(t *testing.T) {
})
r.PUT("/users/:id", handler.UpdateUser)
body := map[string]interface{}{"name": "Updated"}
body := map[string]any{"name": "Updated"}
jsonBody, _ := json.Marshal(body)
req := httptest.NewRequest("PUT", "/users/999", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json")
@@ -736,7 +736,7 @@ func TestUserHandler_UpdateUser_Success(t *testing.T) {
})
r.PUT("/users/:id", handler.UpdateUser)
body := map[string]interface{}{
body := map[string]any{
"name": "Updated Name",
"enabled": true,
}
@@ -855,7 +855,7 @@ func TestUserHandler_UpdateUserPermissions_NonAdmin(t *testing.T) {
})
r.PUT("/users/:id/permissions", handler.UpdateUserPermissions)
body := map[string]interface{}{"permission_mode": "allow_all"}
body := map[string]any{"permission_mode": "allow_all"}
jsonBody, _ := json.Marshal(body)
req := httptest.NewRequest("PUT", "/users/1/permissions", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json")
@@ -875,7 +875,7 @@ func TestUserHandler_UpdateUserPermissions_InvalidID(t *testing.T) {
})
r.PUT("/users/:id/permissions", handler.UpdateUserPermissions)
body := map[string]interface{}{"permission_mode": "allow_all"}
body := map[string]any{"permission_mode": "allow_all"}
jsonBody, _ := json.Marshal(body)
req := httptest.NewRequest("PUT", "/users/invalid/permissions", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json")
@@ -925,7 +925,7 @@ func TestUserHandler_UpdateUserPermissions_NotFound(t *testing.T) {
})
r.PUT("/users/:id/permissions", handler.UpdateUserPermissions)
body := map[string]interface{}{"permission_mode": "allow_all"}
body := map[string]any{"permission_mode": "allow_all"}
jsonBody, _ := json.Marshal(body)
req := httptest.NewRequest("PUT", "/users/999/permissions", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json")
@@ -957,7 +957,7 @@ func TestUserHandler_UpdateUserPermissions_Success(t *testing.T) {
})
r.PUT("/users/:id/permissions", handler.UpdateUserPermissions)
body := map[string]interface{}{
body := map[string]any{
"permission_mode": "deny_all",
"permitted_hosts": []uint{host.ID},
}
@@ -1069,7 +1069,7 @@ func TestUserHandler_ValidateInvite_Success(t *testing.T) {
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Equal(t, "valid@example.com", resp["email"])
}
@@ -1249,7 +1249,7 @@ func TestUserHandler_InviteUser_Success(t *testing.T) {
})
r.POST("/users/invite", handler.InviteUser)
body := map[string]interface{}{
body := map[string]any{
"email": "newinvite@example.com",
"role": "user",
}
@@ -1261,7 +1261,7 @@ func TestUserHandler_InviteUser_Success(t *testing.T) {
assert.Equal(t, http.StatusCreated, w.Code)
var resp map[string]interface{}
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
assert.NotEmpty(t, resp["invite_token"])
// email_sent is false because no SMTP is configured
@@ -1303,7 +1303,7 @@ func TestUserHandler_InviteUser_WithPermittedHosts(t *testing.T) {
})
r.POST("/users/invite", handler.InviteUser)
body := map[string]interface{}{
body := map[string]any{
"email": "invitee-perms@example.com",
"permission_mode": "deny_all",
"permitted_hosts": []uint{host.ID},

View File

@@ -54,12 +54,12 @@ func TestWebSocketStatusHandler_GetConnections(t *testing.T) {
// Verify response
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
assert.Equal(t, float64(2), response["count"])
connections, ok := response["connections"].([]interface{})
connections, ok := response["connections"].([]any)
require.True(t, ok)
assert.Len(t, connections, 2)
}
@@ -81,12 +81,12 @@ func TestWebSocketStatusHandler_GetConnectionsEmpty(t *testing.T) {
// Verify response
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
assert.Equal(t, float64(0), response["count"])
connections, ok := response["connections"].([]interface{})
connections, ok := response["connections"].([]any)
require.True(t, ok)
assert.Len(t, connections, 0)
}

View File

@@ -16,7 +16,7 @@ func Recovery(verbose bool) gin.HandlerFunc {
// Try to get a request-scoped logger; fall back to global logger
entry := GetRequestLogger(c)
if verbose {
entry.WithFields(map[string]interface{}{
entry.WithFields(map[string]any{
"method": c.Request.Method,
"path": SanitizePath(c.Request.URL.Path),
"headers": SanitizeHeaders(c.Request.Header),

View File

@@ -2,6 +2,7 @@ package middleware
import (
"context"
"github.com/Wikid82/charon/backend/internal/logger"
"github.com/Wikid82/charon/backend/internal/trace"
"github.com/gin-gonic/gin"
@@ -18,7 +19,7 @@ func RequestID() gin.HandlerFunc {
c.Set(string(trace.RequestIDKey), rid)
c.Writer.Header().Set(RequestIDHeader, rid)
// Add to logger fields for this request
entry := logger.WithFields(map[string]interface{}{"request_id": rid})
entry := logger.WithFields(map[string]any{"request_id": rid})
c.Set("logger", entry)
// Propagate into the request context so it can be used by services
ctx := context.WithValue(c.Request.Context(), trace.RequestIDKey, rid)

View File

@@ -1,9 +1,10 @@
package middleware
import (
"github.com/Wikid82/charon/backend/internal/util"
"time"
"github.com/Wikid82/charon/backend/internal/util"
"github.com/gin-gonic/gin"
)
@@ -14,7 +15,7 @@ func RequestLogger() gin.HandlerFunc {
c.Next()
latency := time.Since(start)
entry := GetRequestLogger(c)
entry.WithFields(map[string]interface{}{
entry.WithFields(map[string]any{
"status": c.Writer.Status(),
"method": c.Request.Method,
"path": SanitizePath(c.Request.URL.Path),

View File

@@ -97,7 +97,7 @@ func TestInviteToken_MustBeUnguessable(t *testing.T) {
require.Equal(t, http.StatusCreated, w.Code)
var resp map[string]interface{}
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
token := resp["invite_token"].(string)
@@ -239,7 +239,7 @@ func TestAcceptInvite_PasswordValidation(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Reset user to pending state for each test
db.Model(&user).Updates(map[string]interface{}{
db.Model(&user).Updates(map[string]any{
"invite_status": "pending",
"enabled": false,
"password_hash": "",
@@ -369,7 +369,7 @@ func TestSMTPConfig_PasswordMasked(t *testing.T) {
require.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
// Password MUST be masked
@@ -397,7 +397,7 @@ func TestSMTPConfig_PortValidation(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
body, _ := json.Marshal(map[string]interface{}{
body, _ := json.Marshal(map[string]any{
"host": "smtp.test.com",
"port": tc.port,
"from_address": "test@test.com",
@@ -432,7 +432,7 @@ func TestSMTPConfig_EncryptionValidation(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
body, _ := json.Marshal(map[string]interface{}{
body, _ := json.Marshal(map[string]any{
"host": "smtp.test.com",
"port": 587,
"from_address": "test@test.com",
@@ -549,7 +549,7 @@ func TestUpdatePermissions_ValidModes(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
body, _ := json.Marshal(map[string]interface{}{
body, _ := json.Marshal(map[string]any{
"permission_mode": tc.mode,
"permitted_hosts": []int{},
})

View File

@@ -179,7 +179,7 @@ func TestClient_NetworkErrors(t *testing.T) {
func TestClient_Load_MarshalFailure(t *testing.T) {
// Simulate json.Marshal failure
orig := jsonMarshalClient
jsonMarshalClient = func(v interface{}) ([]byte, error) { return nil, fmt.Errorf("marshal error") }
jsonMarshalClient = func(v any) ([]byte, error) { return nil, fmt.Errorf("marshal error") }
defer func() { jsonMarshalClient = orig }()
client := NewClient("http://localhost")

View File

@@ -74,12 +74,12 @@ func GenerateConfig(hosts []models.ProxyHost, storageDir, acmeEmail, frontendDir
}
if acmeEmail != "" {
var issuers []interface{}
var issuers []any
// Configure issuers based on provider preference
switch sslProvider {
case "letsencrypt":
acmeIssuer := map[string]interface{}{
acmeIssuer := map[string]any{
"module": "acme",
"email": acmeEmail,
}
@@ -88,11 +88,11 @@ func GenerateConfig(hosts []models.ProxyHost, storageDir, acmeEmail, frontendDir
}
issuers = append(issuers, acmeIssuer)
case "zerossl":
issuers = append(issuers, map[string]interface{}{
issuers = append(issuers, map[string]any{
"module": "zerossl",
})
default: // "both" or empty
acmeIssuer := map[string]interface{}{
acmeIssuer := map[string]any{
"module": "acme",
"email": acmeEmail,
}
@@ -100,7 +100,7 @@ func GenerateConfig(hosts []models.ProxyHost, storageDir, acmeEmail, frontendDir
acmeIssuer["ca"] = "https://acme-staging-v02.api.letsencrypt.org/directory"
}
issuers = append(issuers, acmeIssuer)
issuers = append(issuers, map[string]interface{}{
issuers = append(issuers, map[string]any{
"module": "zerossl",
})
}
@@ -243,8 +243,8 @@ func GenerateConfig(hosts []models.ProxyHost, storageDir, acmeEmail, frontendDir
// Build a subroute to match these remote IPs and serve 403
// Admin whitelist exclusion must be applied: exclude adminWhitelist if present
// Build matchParts
var matchParts []map[string]interface{}
matchParts = append(matchParts, map[string]interface{}{"remote_ip": map[string]interface{}{"ranges": decisionIPs}})
var matchParts []map[string]any
matchParts = append(matchParts, map[string]any{"remote_ip": map[string]any{"ranges": decisionIPs}})
if adminWhitelist != "" {
adminParts := strings.Split(adminWhitelist, ",")
trims := make([]string, 0)
@@ -256,15 +256,15 @@ func GenerateConfig(hosts []models.ProxyHost, storageDir, acmeEmail, frontendDir
trims = append(trims, p)
}
if len(trims) > 0 {
matchParts = append(matchParts, map[string]interface{}{"not": []map[string]interface{}{{"remote_ip": map[string]interface{}{"ranges": trims}}}})
matchParts = append(matchParts, map[string]any{"not": []map[string]any{{"remote_ip": map[string]any{"ranges": trims}}}})
}
}
decHandler := Handler{
"handler": "subroute",
"routes": []map[string]interface{}{
"routes": []map[string]any{
{
"match": matchParts,
"handle": []map[string]interface{}{
"handle": []map[string]any{
{
"handler": "static_response",
"status_code": 403,
@@ -355,12 +355,12 @@ func GenerateConfig(hosts []models.ProxyHost, storageDir, acmeEmail, frontendDir
// Insert user advanced config (if present) as headers or handlers before the reverse proxy
// so user-specified headers/handlers are applied prior to proxying.
if host.AdvancedConfig != "" {
var parsed interface{}
var parsed any
if err := json.Unmarshal([]byte(host.AdvancedConfig), &parsed); err != nil {
logger.Log().WithField("host", host.UUID).WithError(err).Warn("Failed to parse advanced_config for host")
} else {
switch v := parsed.(type) {
case map[string]interface{}:
case map[string]any:
// Append as a handler
// Ensure it has a "handler" key
if _, ok := v["handler"]; ok {
@@ -382,9 +382,9 @@ func GenerateConfig(hosts []models.ProxyHost, storageDir, acmeEmail, frontendDir
} else {
logger.Log().WithField("host", host.UUID).Warn("advanced_config for host is not a handler object")
}
case []interface{}:
case []any:
for _, it := range v {
if m, ok := it.(map[string]interface{}); ok {
if m, ok := it.(map[string]any); ok {
if rn, has := m["ruleset_name"]; has {
if rnStr, ok := rn.(string); ok && rnStr != "" {
if rulesetPaths != nil {
@@ -474,7 +474,7 @@ func GenerateConfig(hosts []models.ProxyHost, storageDir, acmeEmail, frontendDir
}
policy := &AutomationPolicy{
Subjects: ipSubjects,
IssuersRaw: []interface{}{map[string]interface{}{"module": "internal"}},
IssuersRaw: []any{map[string]any{"module": "internal"}},
}
if config.Apps.TLS.Automation == nil {
config.Apps.TLS.Automation = &AutomationConfig{}
@@ -487,26 +487,26 @@ func GenerateConfig(hosts []models.ProxyHost, storageDir, acmeEmail, frontendDir
// normalizeHandlerHeaders ensures header values in handlers are arrays of strings
// Caddy's JSON schema expects header values to be an array of strings (e.g. ["websocket"]) rather than a single string.
func normalizeHandlerHeaders(h map[string]interface{}) {
func normalizeHandlerHeaders(h map[string]any) {
// normalize top-level headers key
if headersRaw, ok := h["headers"].(map[string]interface{}); ok {
if headersRaw, ok := h["headers"].(map[string]any); ok {
normalizeHeaderOps(headersRaw)
}
// also normalize in nested request/response if present explicitly
for _, side := range []string{"request", "response"} {
if sideRaw, ok := h[side].(map[string]interface{}); ok {
if sideRaw, ok := h[side].(map[string]any); ok {
normalizeHeaderOps(sideRaw)
}
}
}
func normalizeHeaderOps(headerOps map[string]interface{}) {
if setRaw, ok := headerOps["set"].(map[string]interface{}); ok {
func normalizeHeaderOps(headerOps map[string]any) {
if setRaw, ok := headerOps["set"].(map[string]any); ok {
for k, v := range setRaw {
switch vv := v.(type) {
case string:
setRaw[k] = []string{vv}
case []interface{}:
case []any:
// convert to []string
arr := make([]string, 0, len(vv))
for _, it := range vv {
@@ -527,25 +527,25 @@ func normalizeHeaderOps(headerOps map[string]interface{}) {
// NormalizeAdvancedConfig traverses a parsed JSON advanced config (map or array)
// and normalizes any headers blocks so that header values are arrays of strings.
// It returns the modified config object which can be JSON marshaled again.
func NormalizeAdvancedConfig(parsed interface{}) interface{} {
func NormalizeAdvancedConfig(parsed any) any {
switch v := parsed.(type) {
case map[string]interface{}:
case map[string]any:
// This might be a handler object
normalizeHandlerHeaders(v)
// Also inspect nested 'handle' or 'routes' arrays for nested handlers
if handles, ok := v["handle"].([]interface{}); ok {
if handles, ok := v["handle"].([]any); ok {
for _, it := range handles {
if m, ok := it.(map[string]interface{}); ok {
if m, ok := it.(map[string]any); ok {
NormalizeAdvancedConfig(m)
}
}
}
if routes, ok := v["routes"].([]interface{}); ok {
if routes, ok := v["routes"].([]any); ok {
for _, rit := range routes {
if rm, ok := rit.(map[string]interface{}); ok {
if handles, ok := rm["handle"].([]interface{}); ok {
if rm, ok := rit.(map[string]any); ok {
if handles, ok := rm["handle"].([]any); ok {
for _, it := range handles {
if m, ok := it.(map[string]interface{}); ok {
if m, ok := it.(map[string]any); ok {
NormalizeAdvancedConfig(m)
}
}
@@ -554,9 +554,9 @@ func NormalizeAdvancedConfig(parsed interface{}) interface{} {
}
}
return v
case []interface{}:
case []any:
for _, it := range v {
if m, ok := it.(map[string]interface{}); ok {
if m, ok := it.(map[string]any); ok {
NormalizeAdvancedConfig(m)
}
}
@@ -586,18 +586,18 @@ func buildACLHandler(acl *models.AccessList, adminWhitelist string) (Handler, er
// For whitelist, block when NOT in the list
return Handler{
"handler": "subroute",
"routes": []map[string]interface{}{
"routes": []map[string]any{
{
"match": []map[string]interface{}{
"match": []map[string]any{
{
"not": []map[string]interface{}{
"not": []map[string]any{
{
"expression": expression,
},
},
},
},
"handle": []map[string]interface{}{
"handle": []map[string]any{
{
"handler": "static_response",
"status_code": 403,
@@ -613,14 +613,14 @@ func buildACLHandler(acl *models.AccessList, adminWhitelist string) (Handler, er
expression = fmt.Sprintf("{geoip2.country_code} in [%s]", strings.Join(trimmedCodes, ", "))
return Handler{
"handler": "subroute",
"routes": []map[string]interface{}{
"routes": []map[string]any{
{
"match": []map[string]interface{}{
"match": []map[string]any{
{
"expression": expression,
},
},
"handle": []map[string]interface{}{
"handle": []map[string]any{
{
"handler": "static_response",
"status_code": 403,
@@ -638,13 +638,13 @@ func buildACLHandler(acl *models.AccessList, adminWhitelist string) (Handler, er
// Allow only RFC1918 private networks
return Handler{
"handler": "subroute",
"routes": []map[string]interface{}{
"routes": []map[string]any{
{
"match": []map[string]interface{}{
"match": []map[string]any{
{
"not": []map[string]interface{}{
"not": []map[string]any{
{
"remote_ip": map[string]interface{}{
"remote_ip": map[string]any{
"ranges": []string{
"10.0.0.0/8",
"172.16.0.0/12",
@@ -660,7 +660,7 @@ func buildACLHandler(acl *models.AccessList, adminWhitelist string) (Handler, er
},
},
},
"handle": []map[string]interface{}{
"handle": []map[string]any{
{
"handler": "static_response",
"status_code": 403,
@@ -708,20 +708,20 @@ func buildACLHandler(acl *models.AccessList, adminWhitelist string) (Handler, er
}
return Handler{
"handler": "subroute",
"routes": []map[string]interface{}{
"routes": []map[string]any{
{
"match": []map[string]interface{}{
"match": []map[string]any{
{
"not": []map[string]interface{}{
"not": []map[string]any{
{
"remote_ip": map[string]interface{}{
"remote_ip": map[string]any{
"ranges": cidrs,
},
},
},
},
},
"handle": []map[string]interface{}{
"handle": []map[string]any{
{
"handler": "static_response",
"status_code": 403,
@@ -737,7 +737,7 @@ func buildACLHandler(acl *models.AccessList, adminWhitelist string) (Handler, er
if acl.Type == "blacklist" {
// Block these IPs (allow everything else)
// For blacklist, add an explicit 'not' clause excluding adminWhitelist ranges from the match
var adminExclusion interface{}
var adminExclusion any
if adminWhitelist != "" {
adminParts := strings.Split(adminWhitelist, ",")
trims := make([]string, 0)
@@ -749,21 +749,21 @@ func buildACLHandler(acl *models.AccessList, adminWhitelist string) (Handler, er
trims = append(trims, p)
}
if len(trims) > 0 {
adminExclusion = map[string]interface{}{"not": []map[string]interface{}{{"remote_ip": map[string]interface{}{"ranges": trims}}}}
adminExclusion = map[string]any{"not": []map[string]any{{"remote_ip": map[string]any{"ranges": trims}}}}
}
}
// Build matcher parts
matchParts := []map[string]interface{}{}
matchParts = append(matchParts, map[string]interface{}{"remote_ip": map[string]interface{}{"ranges": cidrs}})
matchParts := []map[string]any{}
matchParts = append(matchParts, map[string]any{"remote_ip": map[string]any{"ranges": cidrs}})
if adminExclusion != nil {
matchParts = append(matchParts, adminExclusion.(map[string]interface{}))
matchParts = append(matchParts, adminExclusion.(map[string]any))
}
return Handler{
"handler": "subroute",
"routes": []map[string]interface{}{
"routes": []map[string]any{
{
"match": matchParts,
"handle": []map[string]interface{}{
"handle": []map[string]any{
{
"handler": "static_response",
"status_code": 403,
@@ -876,7 +876,7 @@ func buildWAFHandler(host *models.ProxyHost, rulesets []models.SecurityRuleSet,
// If the host provided an advanced_config containing a 'ruleset_name', prefer that value
var hostRulesetName string
if host != nil && host.AdvancedConfig != "" {
var ac map[string]interface{}
var ac map[string]any
if err := json.Unmarshal([]byte(host.AdvancedConfig), &ac); err == nil {
if rn, ok := ac["ruleset_name"]; ok {
if rnStr, ok2 := rn.(string); ok2 && rnStr != "" {
@@ -1062,8 +1062,8 @@ func buildRateLimitHandler(_ *models.ProxyHost, secCfg *models.SecurityConfig) (
// Note: The caddy-ratelimit module uses a sliding window algorithm
// and does not have a separate burst parameter
rateLimitHandler := Handler{"handler": "rate_limit"}
rateLimitHandler["rate_limits"] = map[string]interface{}{
"static": map[string]interface{}{
rateLimitHandler["rate_limits"] = map[string]any{
"static": map[string]any{
"key": "{http.request.remote.host}",
"window": fmt.Sprintf("%ds", secCfg.RateLimitWindowSec),
"max_events": secCfg.RateLimitRequests,
@@ -1084,22 +1084,22 @@ func buildRateLimitHandler(_ *models.ProxyHost, secCfg *models.SecurityConfig) (
// 2. Everything else -> apply rate limiting
return Handler{
"handler": "subroute",
"routes": []map[string]interface{}{
"routes": []map[string]any{
{
// Route 1: Match bypass IPs - terminal with no handlers (skip rate limiting)
"match": []map[string]interface{}{
"match": []map[string]any{
{
"remote_ip": map[string]interface{}{
"remote_ip": map[string]any{
"ranges": bypassCIDRs,
},
},
},
// No handlers - just pass through without rate limiting
"handle": []map[string]interface{}{},
"handle": []map[string]any{},
},
{
// Route 2: Default - apply rate limiting to everyone else
"handle": []map[string]interface{}{
"handle": []map[string]any{
rateLimitHandler,
},
},
@@ -1238,7 +1238,7 @@ func buildSecurityHeadersHandler(host *models.ProxyHost) (Handler, error) {
return Handler{
"handler": "headers",
"response": map[string]interface{}{
"response": map[string]any{
"set": responseHeaders,
},
}, nil

View File

@@ -45,9 +45,9 @@ func TestGenerateConfig_AdvancedInvalidJSON(t *testing.T) {
}
func TestGenerateConfig_AdvancedArrayHandler(t *testing.T) {
array := []map[string]interface{}{{
array := []map[string]any{{
"handler": "headers",
"response": map[string]interface{}{
"response": map[string]any{
"set": map[string][]string{"X-Test": {"1"}},
},
}}
@@ -119,12 +119,12 @@ func TestGenerateConfig_AdvancedHeadersStringToArray(t *testing.T) {
require.Equal(t, "headers", first["handler"])
// request.set.Upgrade should be an array
if req, ok := first["request"].(map[string]interface{}); ok {
if set, ok := req["set"].(map[string]interface{}); ok {
if req, ok := first["request"].(map[string]any); ok {
if set, ok := req["set"].(map[string]any); ok {
switch val := set["Upgrade"].(type) {
case []string:
require.Equal(t, []string{"websocket"}, val)
case []interface{}:
case []any:
var out []string
for _, v := range val {
out = append(out, fmt.Sprintf("%v", v))
@@ -141,12 +141,12 @@ func TestGenerateConfig_AdvancedHeadersStringToArray(t *testing.T) {
}
// response.set.X-Obj should be an array
if resp, ok := first["response"].(map[string]interface{}); ok {
if set, ok := resp["set"].(map[string]interface{}); ok {
if resp, ok := first["response"].(map[string]any); ok {
if set, ok := resp["set"].(map[string]any); ok {
switch val := set["X-Obj"].(type) {
case []string:
require.Equal(t, []string{"1"}, val)
case []interface{}:
case []any:
var out []string
for _, v := range val {
out = append(out, fmt.Sprintf("%v", v))

View File

@@ -29,7 +29,7 @@ func TestGenerateConfig_ZerosslAndBothProviders(t *testing.T) {
issuers := cfgZ.Apps.TLS.Automation.Policies[0].IssuersRaw
foundZerossl := false
for _, i := range issuers {
m := i.(map[string]interface{})
m := i.(map[string]any)
if m["module"] == "zerossl" {
foundZerossl = true
}
@@ -251,13 +251,13 @@ func TestGenerateConfig_DecisionAdminPartsEmpty(t *testing.T) {
func TestNormalizeHeaderOps_PreserveStringArray(t *testing.T) {
// Construct a headers map where set has a []string value already
set := map[string]interface{}{
set := map[string]any{
"X-Array": []string{"1", "2"},
}
headerOps := map[string]interface{}{"set": set}
headerOps := map[string]any{"set": set}
normalizeHeaderOps(headerOps)
// Ensure the value remained a []string
if v, ok := headerOps["set"].(map[string]interface{}); ok {
if v, ok := headerOps["set"].(map[string]any); ok {
if arr, ok := v["X-Array"].([]string); ok {
require.Equal(t, []string{"1", "2"}, arr)
return
@@ -366,8 +366,8 @@ func TestGenerateConfig_RateLimitFromSecCfg(t *testing.T) {
for _, h := range route.Handle {
if hn, ok := h["handler"].(string); ok && hn == "rate_limit" {
// Check caddy-ratelimit format: rate_limits.static.max_events and window
if rateLimits, ok := h["rate_limits"].(map[string]interface{}); ok {
if static, ok := rateLimits["static"].(map[string]interface{}); ok {
if rateLimits, ok := h["rate_limits"].(map[string]any); ok {
if static, ok := rateLimits["static"].(map[string]any); ok {
if maxEvents, ok := static["max_events"].(int); ok && maxEvents == 10 {
if window, ok := static["window"].(string); ok && window == "60s" {
found = true
@@ -463,7 +463,7 @@ func TestGenerateConfig_DefaultAcmeStaging(t *testing.T) {
issuers := cfg.Apps.TLS.Automation.Policies[0].IssuersRaw
found := false
for _, i := range issuers {
if m, ok := i.(map[string]interface{}); ok {
if m, ok := i.(map[string]any); ok {
if m["module"] == "acme" {
if _, ok := m["ca"]; ok {
found = true

View File

@@ -36,7 +36,7 @@ func TestBuildSecurityHeadersHandler_AllEnabled(t *testing.T) {
assert.NotNil(t, handler)
assert.Equal(t, "headers", handler["handler"])
response := handler["response"].(map[string]interface{})
response := handler["response"].(map[string]any)
headers := response["set"].(map[string][]string)
assert.Contains(t, headers["Strict-Transport-Security"][0], "max-age=31536000")
@@ -73,7 +73,7 @@ func TestBuildSecurityHeadersHandler_HSTSOnly(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, handler)
response := handler["response"].(map[string]interface{})
response := handler["response"].(map[string]any)
headers := response["set"].(map[string][]string)
assert.Contains(t, headers["Strict-Transport-Security"][0], "max-age=31536000")
@@ -103,7 +103,7 @@ func TestBuildSecurityHeadersHandler_CSPOnly(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, handler)
response := handler["response"].(map[string]interface{})
response := handler["response"].(map[string]any)
headers := response["set"].(map[string][]string)
assert.NotContains(t, headers, "Strict-Transport-Security")
@@ -129,7 +129,7 @@ func TestBuildSecurityHeadersHandler_CSPReportOnly(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, handler)
response := handler["response"].(map[string]interface{})
response := handler["response"].(map[string]any)
headers := response["set"].(map[string][]string)
assert.NotContains(t, headers, "Content-Security-Policy")
@@ -147,7 +147,7 @@ func TestBuildSecurityHeadersHandler_NoProfile(t *testing.T) {
assert.NotNil(t, handler)
// Should use defaults
response := handler["response"].(map[string]interface{})
response := handler["response"].(map[string]any)
headers := response["set"].(map[string][]string)
assert.Contains(t, headers, "Strict-Transport-Security")
@@ -314,7 +314,7 @@ func TestBuildSecurityHeadersHandler_PermissionsPolicy(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, handler)
response := handler["response"].(map[string]interface{})
response := handler["response"].(map[string]any)
headers := response["set"].(map[string][]string)
assert.Contains(t, headers, "Permissions-Policy")
@@ -341,7 +341,7 @@ func TestBuildSecurityHeadersHandler_InvalidCSPJSON(t *testing.T) {
assert.NotNil(t, handler)
// Should skip CSP if invalid JSON
response := handler["response"].(map[string]interface{})
response := handler["response"].(map[string]any)
headers := response["set"].(map[string][]string)
assert.NotContains(t, headers, "Content-Security-Policy")
// But should include the other header
@@ -394,7 +394,7 @@ func TestBuildSecurityHeadersHandler_APIFriendlyPreset(t *testing.T) {
assert.NotNil(t, handler)
assert.Equal(t, "headers", handler["handler"])
response := handler["response"].(map[string]interface{})
response := handler["response"].(map[string]any)
headers := response["set"].(map[string][]string)
// Verify HSTS is present

View File

@@ -170,7 +170,7 @@ func TestGenerateConfig_IPHostsSkipAutoHTTPS(t *testing.T) {
if p.Subjects[0] == "192.0.2.10" {
foundIPPolicy = true
require.Len(t, p.IssuersRaw, 1)
issuer := p.IssuersRaw[0].(map[string]interface{})
issuer := p.IssuersRaw[0].(map[string]any)
require.Equal(t, "internal", issuer["module"])
}
}
@@ -259,7 +259,7 @@ func TestGenerateConfig_ACMEStaging(t *testing.T) {
issuers := config.Apps.TLS.Automation.Policies[0].IssuersRaw
require.Len(t, issuers, 1)
acmeIssuer := issuers[0].(map[string]interface{})
acmeIssuer := issuers[0].(map[string]any)
require.Equal(t, "acme", acmeIssuer["module"])
require.Equal(t, "admin@example.com", acmeIssuer["email"])
require.Equal(t, "https://acme-staging-v02.api.letsencrypt.org/directory", acmeIssuer["ca"])
@@ -274,7 +274,7 @@ func TestGenerateConfig_ACMEStaging(t *testing.T) {
issuers = config.Apps.TLS.Automation.Policies[0].IssuersRaw
require.Len(t, issuers, 1)
acmeIssuer = issuers[0].(map[string]interface{})
acmeIssuer = issuers[0].(map[string]any)
require.Equal(t, "acme", acmeIssuer["module"])
require.Equal(t, "admin@example.com", acmeIssuer["email"])
_, hasCA := acmeIssuer["ca"]
@@ -401,10 +401,10 @@ func TestBuildRateLimitHandler_ValidConfig(t *testing.T) {
require.Equal(t, "rate_limit", h["handler"])
// Verify rate_limits structure
rateLimits, ok := h["rate_limits"].(map[string]interface{})
rateLimits, ok := h["rate_limits"].(map[string]any)
require.True(t, ok, "rate_limits should be a map")
staticZone, ok := rateLimits["static"].(map[string]interface{})
staticZone, ok := rateLimits["static"].(map[string]any)
require.True(t, ok, "static zone should be a map")
// Verify caddy-ratelimit specific fields
@@ -498,9 +498,9 @@ func TestBuildRateLimitHandler_UsesBurst(t *testing.T) {
// Handler should be a plain rate_limit (no bypass list)
require.Equal(t, "rate_limit", h["handler"])
rateLimits, ok := h["rate_limits"].(map[string]interface{})
rateLimits, ok := h["rate_limits"].(map[string]any)
require.True(t, ok)
staticZone, ok := rateLimits["static"].(map[string]interface{})
staticZone, ok := rateLimits["static"].(map[string]any)
require.True(t, ok)
// Verify burst field is NOT present (not supported by caddy-ratelimit)
@@ -519,9 +519,9 @@ func TestBuildRateLimitHandler_DefaultBurst(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, h)
rateLimits, ok := h["rate_limits"].(map[string]interface{})
rateLimits, ok := h["rate_limits"].(map[string]any)
require.True(t, ok)
staticZone, ok := rateLimits["static"].(map[string]interface{})
staticZone, ok := rateLimits["static"].(map[string]any)
require.True(t, ok)
// Verify burst field is NOT present
@@ -538,9 +538,9 @@ func TestBuildRateLimitHandler_DefaultBurst(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, h2)
rateLimits2, ok := h2["rate_limits"].(map[string]interface{})
rateLimits2, ok := h2["rate_limits"].(map[string]any)
require.True(t, ok)
staticZone2, ok := rateLimits2["static"].(map[string]interface{})
staticZone2, ok := rateLimits2["static"].(map[string]any)
require.True(t, ok)
// Verify no burst field here either

View File

@@ -44,7 +44,7 @@ type CaddyHTTP struct {
type CaddyServer struct {
Listen []string `json:"listen,omitempty"`
Routes []*CaddyRoute `json:"routes,omitempty"`
TLSConnectionPolicies interface{} `json:"tls_connection_policies,omitempty"`
TLSConnectionPolicies any `json:"tls_connection_policies,omitempty"`
}
// CaddyRoute represents a single route with matchers and handlers.
@@ -60,10 +60,10 @@ type CaddyMatcher struct {
// CaddyHandler represents a handler in the route.
type CaddyHandler struct {
Handler string `json:"handler"`
Upstreams interface{} `json:"upstreams,omitempty"`
Headers interface{} `json:"headers,omitempty"`
Routes interface{} `json:"routes,omitempty"` // For subroute handlers
Handler string `json:"handler"`
Upstreams any `json:"upstreams,omitempty"`
Headers any `json:"headers,omitempty"`
Routes any `json:"routes,omitempty"` // For subroute handlers
}
// ParsedHost represents a single host detected during Caddyfile import.
@@ -139,21 +139,21 @@ func (i *Importer) extractHandlers(handles []*CaddyHandler) []*CaddyHandler {
}
// It's a subroute; extract handlers from its first route
routes, ok := handler.Routes.([]interface{})
routes, ok := handler.Routes.([]any)
if !ok || len(routes) == 0 {
continue
}
subroute, ok := routes[0].(map[string]interface{})
subroute, ok := routes[0].(map[string]any)
if !ok {
continue
}
subhandles, ok := subroute["handle"].([]interface{})
subhandles, ok := subroute["handle"].([]any)
if !ok {
continue
}
// Convert the subhandles to CaddyHandler objects
for _, sh := range subhandles {
shMap, ok := sh.(map[string]interface{})
shMap, ok := sh.(map[string]any)
if !ok {
continue
}
@@ -227,9 +227,9 @@ func (i *Importer) ExtractHosts(caddyJSON []byte) (*ImportResult, error) {
for _, handler := range handlers {
if handler.Handler == "reverse_proxy" {
upstreams, _ := handler.Upstreams.([]interface{})
upstreams, _ := handler.Upstreams.([]any)
if len(upstreams) > 0 {
if upstream, ok := upstreams[0].(map[string]interface{}); ok {
if upstream, ok := upstreams[0].(map[string]any); ok {
dial, _ := upstream["dial"].(string)
if dial != "" {
hostStr, portStr, err := net.SplitHostPort(dial)
@@ -258,8 +258,8 @@ func (i *Importer) ExtractHosts(caddyJSON []byte) (*ImportResult, error) {
}
// Check for websocket support
if headers, ok := handler.Headers.(map[string]interface{}); ok {
if upgrade, ok := headers["Upgrade"].([]interface{}); ok {
if headers, ok := handler.Headers.(map[string]any); ok {
if upgrade, ok := headers["Upgrade"].([]any); ok {
for _, v := range upgrade {
if v == "websocket" {
host.WebsocketSupport = true
@@ -286,7 +286,7 @@ func (i *Importer) ExtractHosts(caddyJSON []byte) (*ImportResult, error) {
}
// Store raw JSON for this route
routeJSON, _ := json.Marshal(map[string]interface{}{
routeJSON, _ := json.Marshal(map[string]any{
"server": serverName,
"route": routeIdx,
"data": route,

View File

@@ -22,13 +22,13 @@ func TestImporter_ExtractHosts_TLSConnectionPolicyAndDialWithoutPort(t *testing.
{
Match: []*CaddyMatcher{{Host: []string{"example.com"}}},
Handle: []*CaddyHandler{
{Handler: "reverse_proxy", Upstreams: []interface{}{map[string]interface{}{"dial": "app:9000"}}},
{Handler: "reverse_proxy", Upstreams: []any{map[string]any{"dial": "app:9000"}}},
},
},
{
Match: []*CaddyMatcher{{Host: []string{"nport.example.com"}}},
Handle: []*CaddyHandler{
{Handler: "reverse_proxy", Upstreams: []interface{}{map[string]interface{}{"dial": "app"}}},
{Handler: "reverse_proxy", Upstreams: []any{map[string]any{"dial": "app"}}},
},
},
},
@@ -52,7 +52,7 @@ func TestImporter_ExtractHosts_TLSConnectionPolicyAndDialWithoutPort(t *testing.
func TestExtractHandlers_Subroute_WithUnsupportedSubhandle(t *testing.T) {
// Build a handler with subroute whose handle contains a non-map item
h := []*CaddyHandler{
{Handler: "subroute", Routes: []interface{}{map[string]interface{}{"handle": []interface{}{"not-a-map", map[string]interface{}{"handler": "reverse_proxy"}}}}},
{Handler: "subroute", Routes: []any{map[string]any{"handle": []any{"not-a-map", map[string]any{"handler": "reverse_proxy"}}}}},
}
importer := NewImporter("")
res := importer.extractHandlers(h)
@@ -63,7 +63,7 @@ func TestExtractHandlers_Subroute_WithUnsupportedSubhandle(t *testing.T) {
func TestExtractHandlers_Subroute_WithNonMapRoutes(t *testing.T) {
h := []*CaddyHandler{
{Handler: "subroute", Routes: []interface{}{"not-a-map"}},
{Handler: "subroute", Routes: []any{"not-a-map"}},
}
importer := NewImporter("")
res := importer.extractHandlers(h)
@@ -76,7 +76,7 @@ func TestImporter_ExtractHosts_UpstreamsNonMapAndWarnings(t *testing.T) {
Listen: []string{":80"},
Routes: []*CaddyRoute{{
Match: []*CaddyMatcher{{Host: []string{"warn.example.com"}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []interface{}{"nonnmap"}}, {Handler: "rewrite"}, {Handler: "file_server"}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []any{"nonnmap"}}, {Handler: "rewrite"}, {Handler: "file_server"}},
}},
}}}},
}
@@ -99,7 +99,7 @@ func TestBackupCaddyfile_ReadFailure(t *testing.T) {
func TestExtractHandlers_Subroute_EmptyAndHandleNotArray(t *testing.T) {
// Empty routes array
h := []*CaddyHandler{
{Handler: "subroute", Routes: []interface{}{}},
{Handler: "subroute", Routes: []any{}},
}
importer := NewImporter("")
res := importer.extractHandlers(h)
@@ -107,7 +107,7 @@ func TestExtractHandlers_Subroute_EmptyAndHandleNotArray(t *testing.T) {
// Routes with a map but handle is not an array
h2 := []*CaddyHandler{
{Handler: "subroute", Routes: []interface{}{map[string]interface{}{"handle": "not-an-array"}}},
{Handler: "subroute", Routes: []any{map[string]any{"handle": "not-an-array"}}},
}
res2 := importer.extractHandlers(h2)
require.Len(t, res2, 0)
@@ -118,7 +118,7 @@ func TestImporter_ExtractHosts_ReverseProxyNoUpstreams(t *testing.T) {
Listen: []string{":80"},
Routes: []*CaddyRoute{{
Match: []*CaddyMatcher{{Host: []string{"noups.example.com"}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []interface{}{}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []any{}}},
}},
}}}}}
b, _ := json.Marshal(cfg)
@@ -147,16 +147,16 @@ func TestBackupCaddyfile_Success(t *testing.T) {
func TestExtractHandlers_Subroute_WithHeadersUpstreams(t *testing.T) {
h := []*CaddyHandler{
{Handler: "subroute", Routes: []interface{}{map[string]interface{}{"handle": []interface{}{map[string]interface{}{"handler": "reverse_proxy", "upstreams": []interface{}{map[string]interface{}{"dial": "app:8080"}}, "headers": map[string]interface{}{"Upgrade": []interface{}{"websocket"}}}}}}},
{Handler: "subroute", Routes: []any{map[string]any{"handle": []any{map[string]any{"handler": "reverse_proxy", "upstreams": []any{map[string]any{"dial": "app:8080"}}, "headers": map[string]any{"Upgrade": []any{"websocket"}}}}}}},
}
importer := NewImporter("")
res := importer.extractHandlers(h)
require.Len(t, res, 1)
require.Equal(t, "reverse_proxy", res[0].Handler)
// Upstreams should be present in extracted handler
_, ok := res[0].Upstreams.([]interface{})
_, ok := res[0].Upstreams.([]any)
require.True(t, ok)
_, ok = res[0].Headers.(map[string]interface{})
_, ok = res[0].Headers.(map[string]any)
require.True(t, ok)
}
@@ -169,14 +169,14 @@ func TestImporter_ExtractHosts_DuplicateHost(t *testing.T) {
Listen: []string{":80"},
Routes: []*CaddyRoute{{
Match: []*CaddyMatcher{{Host: []string{"dup.example.com"}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []interface{}{map[string]interface{}{"dial": "one:80"}}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []any{map[string]any{"dial": "one:80"}}}},
}},
},
"srv2": {
Listen: []string{":80"},
Routes: []*CaddyRoute{{
Match: []*CaddyMatcher{{Host: []string{"dup.example.com"}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []interface{}{map[string]interface{}{"dial": "two:80"}}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []any{map[string]any{"dial": "two:80"}}}},
}},
},
},
@@ -215,7 +215,7 @@ func TestImporter_ExtractHosts_SSLForcedByDomainScheme(t *testing.T) {
Listen: []string{":80"},
Routes: []*CaddyRoute{{
Match: []*CaddyMatcher{{Host: []string{"https://secure.example.com"}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []interface{}{map[string]interface{}{"dial": "one:80"}}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []any{map[string]any{"dial": "one:80"}}}},
}},
}}}}}
b, _ := json.Marshal(cfg)
@@ -232,7 +232,7 @@ func TestImporter_ExtractHosts_MultipleHostsInMatch(t *testing.T) {
Listen: []string{":80"},
Routes: []*CaddyRoute{{
Match: []*CaddyMatcher{{Host: []string{"m1.example.com", "m2.example.com"}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []interface{}{map[string]interface{}{"dial": "one:80"}}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []any{map[string]any{"dial": "one:80"}}}},
}},
}}}}}
b, _ := json.Marshal(cfg)
@@ -247,7 +247,7 @@ func TestImporter_ExtractHosts_UpgradeHeaderAsString(t *testing.T) {
Listen: []string{":80"},
Routes: []*CaddyRoute{{
Match: []*CaddyMatcher{{Host: []string{"ws.example.com"}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []interface{}{map[string]interface{}{"dial": "one:80"}}, Headers: map[string]interface{}{"Upgrade": []string{"websocket"}}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []any{map[string]any{"dial": "one:80"}}, Headers: map[string]any{"Upgrade": []string{"websocket"}}}},
}},
}}}}}
b, _ := json.Marshal(cfg)
@@ -265,7 +265,7 @@ func TestImporter_ExtractHosts_SscanfFailureOnPort(t *testing.T) {
Listen: []string{":80"},
Routes: []*CaddyRoute{{
Match: []*CaddyMatcher{{Host: []string{"sscanf.example.com"}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []interface{}{map[string]interface{}{"dial": "127.0.0.1:eighty"}}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []any{map[string]any{"dial": "127.0.0.1:eighty"}}}},
}},
}}}}}
b, _ := json.Marshal(cfg)
@@ -283,7 +283,7 @@ func TestImporter_ExtractHosts_PartsSscanfFail(t *testing.T) {
Listen: []string{":80"},
Routes: []*CaddyRoute{{
Match: []*CaddyMatcher{{Host: []string{"parts.example.com"}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []interface{}{map[string]interface{}{"dial": "tcp/127.0.0.1:badport"}}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []any{map[string]any{"dial": "tcp/127.0.0.1:badport"}}}},
}},
}}}}}
b, _ := json.Marshal(cfg)
@@ -300,7 +300,7 @@ func TestImporter_ExtractHosts_PartsEmptyPortField(t *testing.T) {
Listen: []string{":80"},
Routes: []*CaddyRoute{{
Match: []*CaddyMatcher{{Host: []string{"emptyparts.example.com"}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []interface{}{map[string]interface{}{"dial": "tcp/127.0.0.1:"}}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []any{map[string]any{"dial": "tcp/127.0.0.1:"}}}},
}},
}}}}}
b, _ := json.Marshal(cfg)
@@ -321,7 +321,7 @@ func TestImporter_ExtractHosts_ForceSplitFallback_PartsNumericPort(t *testing.T)
Listen: []string{":80"},
Routes: []*CaddyRoute{{
Match: []*CaddyMatcher{{Host: []string{"forced.example.com"}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []interface{}{map[string]interface{}{"dial": "127.0.0.1:8181"}}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []any{map[string]any{"dial": "127.0.0.1:8181"}}}},
}},
}}}}}
b, _ := json.Marshal(cfg)
@@ -343,7 +343,7 @@ func TestImporter_ExtractHosts_ForceSplitFallback_PartsSscanfFail(t *testing.T)
Listen: []string{":80"},
Routes: []*CaddyRoute{{
Match: []*CaddyMatcher{{Host: []string{"forcedfail.example.com"}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []interface{}{map[string]interface{}{"dial": "127.0.0.1:notnum"}}}},
Handle: []*CaddyHandler{{Handler: "reverse_proxy", Upstreams: []any{map[string]any{"dial": "127.0.0.1:notnum"}}}},
}},
}}}}}
b, _ := json.Marshal(cfg)

View File

@@ -61,18 +61,18 @@ func TestExtractHandlers_Subroute(t *testing.T) {
t.Fatal("Upstreams should not be nil")
}
upstreams, ok := handlers[1].Upstreams.([]interface{})
upstreams, ok := handlers[1].Upstreams.([]any)
if !ok {
t.Fatal("Upstreams should be []interface{}")
t.Fatal("Upstreams should be []any")
}
if len(upstreams) == 0 {
t.Fatal("Upstreams should not be empty")
}
upstream, ok := upstreams[0].(map[string]interface{})
upstream, ok := upstreams[0].(map[string]any)
if !ok {
t.Fatal("First upstream should be map[string]interface{}")
t.Fatal("First upstream should be map[string]any")
}
dial, ok := upstream["dial"].(string)

View File

@@ -231,7 +231,7 @@ func (m *Manager) ApplyConfig(ctx context.Context) error {
}
// Debug logging: WAF configuration state for troubleshooting integration issues
logger.Log().WithFields(map[string]interface{}{
logger.Log().WithFields(map[string]any{
"waf_enabled": wafEnabled,
"waf_mode": secCfg.WAFMode,
"waf_rules_source": secCfg.WAFRulesSource,
@@ -239,7 +239,7 @@ func (m *Manager) ApplyConfig(ctx context.Context) error {
"ruleset_paths_len": len(rulesetPaths),
}).Debug("WAF configuration state")
for rsName, rsPath := range rulesetPaths {
logger.Log().WithFields(map[string]interface{}{
logger.Log().WithFields(map[string]any{
"ruleset_name": rsName,
"ruleset_path": rsPath,
}).Debug("WAF ruleset path mapping")

View File

@@ -373,7 +373,7 @@ func TestManager_SaveSnapshot_MarshalError(t *testing.T) {
manager := NewManager(nil, nil, tmp, "", false, config.SecurityConfig{})
// Stub jsonMarshallFunc to return error
orig := jsonMarshalFunc
jsonMarshalFunc = func(v interface{}, prefix, indent string) ([]byte, error) {
jsonMarshalFunc = func(v any, prefix, indent string) ([]byte, error) {
return nil, fmt.Errorf("marshal fail")
}
defer func() { jsonMarshalFunc = orig }()
@@ -915,12 +915,12 @@ func TestManager_ApplyConfig_ReappliesOnFlagChange(t *testing.T) {
if h == "flag.example.com" {
for _, handle := range r.Handle {
if handlerName, ok := handle["handler"].(string); ok && handlerName == "subroute" {
if routes, ok := handle["routes"].([]interface{}); ok {
if routes, ok := handle["routes"].([]any); ok {
for _, rt := range routes {
if rtMap, ok := rt.(map[string]interface{}); ok {
if inner, ok := rtMap["handle"].([]interface{}); ok {
if rtMap, ok := rt.(map[string]any); ok {
if inner, ok := rtMap["handle"].([]any); ok {
for _, itm := range inner {
if itmMap, ok := itm.(map[string]interface{}); ok {
if itmMap, ok := itm.(map[string]any); ok {
if body, ok := itmMap["body"].(string); ok {
if strings.Contains(body, "Access denied") {
found = true
@@ -959,12 +959,12 @@ func TestManager_ApplyConfig_ReappliesOnFlagChange(t *testing.T) {
if h == "flag.example.com" {
for _, handle := range r.Handle {
if handlerName, ok := handle["handler"].(string); ok && handlerName == "subroute" {
if routes, ok := handle["routes"].([]interface{}); ok {
if routes, ok := handle["routes"].([]any); ok {
for _, rt := range routes {
if rtMap, ok := rt.(map[string]interface{}); ok {
if inner, ok := rtMap["handle"].([]interface{}); ok {
if rtMap, ok := rt.(map[string]any); ok {
if inner, ok := rtMap["handle"].([]any); ok {
for _, itm := range inner {
if itmMap, ok := itm.(map[string]interface{}); ok {
if itmMap, ok := itm.(map[string]any); ok {
if body, ok := itmMap["body"].(string); ok {
if strings.Contains(body, "Access denied") {
found = true
@@ -1162,7 +1162,7 @@ func TestManager_ApplyConfig_DebugMarshalFailure(t *testing.T) {
// Stub jsonMarshalDebugFunc to return an error (exercises the else branch in debug logging)
origMarshalDebug := jsonMarshalDebugFunc
jsonMarshalDebugFunc = func(v interface{}) ([]byte, error) {
jsonMarshalDebugFunc = func(v any) ([]byte, error) {
return nil, fmt.Errorf("simulated marshal error")
}
defer func() { jsonMarshalDebugFunc = origMarshalDebug }()

View File

@@ -10,18 +10,18 @@ import (
func TestNormalizeAdvancedConfig_MapWithNestedHandles(t *testing.T) {
// Build a map with nested 'handle' array containing headers with string values
raw := map[string]interface{}{
raw := map[string]any{
"handler": "subroute",
"routes": []interface{}{
map[string]interface{}{
"handle": []interface{}{
map[string]interface{}{
"routes": []any{
map[string]any{
"handle": []any{
map[string]any{
"handler": "headers",
"request": map[string]interface{}{
"set": map[string]interface{}{"Upgrade": "websocket"},
"request": map[string]any{
"set": map[string]any{"Upgrade": "websocket"},
},
"response": map[string]interface{}{
"set": map[string]interface{}{"X-Obj": "1"},
"response": map[string]any{
"set": map[string]any{"X-Obj": "1"},
},
},
},
@@ -31,21 +31,21 @@ func TestNormalizeAdvancedConfig_MapWithNestedHandles(t *testing.T) {
out := NormalizeAdvancedConfig(raw)
// Verify nested header values normalized
outMap, ok := out.(map[string]interface{})
outMap, ok := out.(map[string]any)
require.True(t, ok)
routes := outMap["routes"].([]interface{})
routes := outMap["routes"].([]any)
require.Len(t, routes, 1)
r := routes[0].(map[string]interface{})
handles := r["handle"].([]interface{})
r := routes[0].(map[string]any)
handles := r["handle"].([]any)
require.Len(t, handles, 1)
hdr := handles[0].(map[string]interface{})
hdr := handles[0].(map[string]any)
// request.set.Upgrade
req := hdr["request"].(map[string]interface{})
set := req["set"].(map[string]interface{})
// Could be []interface{} or []string depending on code path; normalize to []string representation
req := hdr["request"].(map[string]any)
set := req["set"].(map[string]any)
// Could be []any or []string depending on code path; normalize to []string representation
switch v := set["Upgrade"].(type) {
case []interface{}:
case []any:
var outArr []string
for _, it := range v {
outArr = append(outArr, fmt.Sprintf("%v", it))
@@ -58,10 +58,10 @@ func TestNormalizeAdvancedConfig_MapWithNestedHandles(t *testing.T) {
}
// response.set.X-Obj
resp := hdr["response"].(map[string]interface{})
rset := resp["set"].(map[string]interface{})
resp := hdr["response"].(map[string]any)
rset := resp["set"].(map[string]any)
switch v := rset["X-Obj"].(type) {
case []interface{}:
case []any:
var outArr []string
for _, it := range v {
outArr = append(outArr, fmt.Sprintf("%v", it))
@@ -75,23 +75,23 @@ func TestNormalizeAdvancedConfig_MapWithNestedHandles(t *testing.T) {
}
func TestNormalizeAdvancedConfig_ArrayTopLevel(t *testing.T) {
// Top-level array containing a headers handler with array value as []interface{}
raw := []interface{}{
map[string]interface{}{
// Top-level array containing a headers handler with array value as []any
raw := []any{
map[string]any{
"handler": "headers",
"response": map[string]interface{}{
"set": map[string]interface{}{"X-Obj": []interface{}{"1"}},
"response": map[string]any{
"set": map[string]any{"X-Obj": []any{"1"}},
},
},
}
out := NormalizeAdvancedConfig(raw)
outArr := out.([]interface{})
outArr := out.([]any)
require.Len(t, outArr, 1)
hdr := outArr[0].(map[string]interface{})
resp := hdr["response"].(map[string]interface{})
set := resp["set"].(map[string]interface{})
hdr := outArr[0].(map[string]any)
resp := hdr["response"].(map[string]any)
set := resp["set"].(map[string]any)
switch v := set["X-Obj"].(type) {
case []interface{}:
case []any:
var outArr2 []string
for _, it := range v {
outArr2 = append(outArr2, fmt.Sprintf("%v", it))
@@ -114,13 +114,13 @@ func TestNormalizeAdvancedConfig_DefaultPrimitives(t *testing.T) {
func TestNormalizeAdvancedConfig_CoerceNonStandardTypes(t *testing.T) {
// Use a header value that is numeric and ensure it's coerced to string
raw := map[string]interface{}{"handler": "headers", "response": map[string]interface{}{"set": map[string]interface{}{"X-Num": 1}}}
out := NormalizeAdvancedConfig(raw).(map[string]interface{})
resp := out["response"].(map[string]interface{})
set := resp["set"].(map[string]interface{})
raw := map[string]any{"handler": "headers", "response": map[string]any{"set": map[string]any{"X-Num": 1}}}
out := NormalizeAdvancedConfig(raw).(map[string]any)
resp := out["response"].(map[string]any)
set := resp["set"].(map[string]any)
// Should be a []string with "1"
switch v := set["X-Num"].(type) {
case []interface{}:
case []any:
var outArr []string
for _, it := range v {
outArr = append(outArr, fmt.Sprintf("%v", it))
@@ -135,28 +135,28 @@ func TestNormalizeAdvancedConfig_CoerceNonStandardTypes(t *testing.T) {
func TestNormalizeAdvancedConfig_JSONRoundtrip(t *testing.T) {
// Ensure normalized config can be marshaled back to JSON and unmarshaled
raw := map[string]interface{}{"handler": "headers", "request": map[string]interface{}{"set": map[string]interface{}{"Upgrade": "websocket"}}}
raw := map[string]any{"handler": "headers", "request": map[string]any{"set": map[string]any{"Upgrade": "websocket"}}}
out := NormalizeAdvancedConfig(raw)
b, err := json.Marshal(out)
require.NoError(t, err)
// Marshal back and read result
var parsed interface{}
var parsed any
require.NoError(t, json.Unmarshal(b, &parsed))
}
func TestNormalizeAdvancedConfig_TopLevelHeaders(t *testing.T) {
// Top-level 'headers' key should be normalized similar to request/response
raw := map[string]interface{}{
raw := map[string]any{
"handler": "headers",
"headers": map[string]interface{}{
"set": map[string]interface{}{"Upgrade": "websocket"},
"headers": map[string]any{
"set": map[string]any{"Upgrade": "websocket"},
},
}
out := NormalizeAdvancedConfig(raw).(map[string]interface{})
hdrs := out["headers"].(map[string]interface{})
set := hdrs["set"].(map[string]interface{})
out := NormalizeAdvancedConfig(raw).(map[string]any)
hdrs := out["headers"].(map[string]any)
set := hdrs["set"].(map[string]any)
switch v := set["Upgrade"].(type) {
case []interface{}:
case []any:
var outArr []string
for _, it := range v {
outArr = append(outArr, fmt.Sprintf("%v", it))
@@ -171,17 +171,17 @@ func TestNormalizeAdvancedConfig_TopLevelHeaders(t *testing.T) {
func TestNormalizeAdvancedConfig_HeadersAlreadyArray(t *testing.T) {
// If the header value is already a []string it should be left as-is
raw := map[string]interface{}{
raw := map[string]any{
"handler": "headers",
"headers": map[string]interface{}{
"set": map[string]interface{}{"X-Test": []string{"a", "b"}},
"headers": map[string]any{
"set": map[string]any{"X-Test": []string{"a", "b"}},
},
}
out := NormalizeAdvancedConfig(raw).(map[string]interface{})
hdrs := out["headers"].(map[string]interface{})
set := hdrs["set"].(map[string]interface{})
out := NormalizeAdvancedConfig(raw).(map[string]any)
hdrs := out["headers"].(map[string]any)
set := hdrs["set"].(map[string]any)
switch v := set["X-Test"].(type) {
case []interface{}:
case []any:
var outArr []string
for _, it := range v {
outArr = append(outArr, fmt.Sprintf("%v", it))
@@ -195,23 +195,23 @@ func TestNormalizeAdvancedConfig_HeadersAlreadyArray(t *testing.T) {
}
func TestNormalizeAdvancedConfig_MapWithTopLevelHandle(t *testing.T) {
raw := map[string]interface{}{
raw := map[string]any{
"handler": "subroute",
"handle": []interface{}{
map[string]interface{}{
"handle": []any{
map[string]any{
"handler": "headers",
"request": map[string]interface{}{"set": map[string]interface{}{"Upgrade": "websocket"}},
"request": map[string]any{"set": map[string]any{"Upgrade": "websocket"}},
},
},
}
out := NormalizeAdvancedConfig(raw).(map[string]interface{})
handles := out["handle"].([]interface{})
out := NormalizeAdvancedConfig(raw).(map[string]any)
handles := out["handle"].([]any)
require.Len(t, handles, 1)
hdr := handles[0].(map[string]interface{})
req := hdr["request"].(map[string]interface{})
set := req["set"].(map[string]interface{})
hdr := handles[0].(map[string]any)
req := hdr["request"].(map[string]any)
set := req["set"].(map[string]any)
switch v := set["Upgrade"].(type) {
case []interface{}:
case []any:
var outArr []string
for _, it := range v {
outArr = append(outArr, fmt.Sprintf("%v", it))

View File

@@ -119,7 +119,7 @@ type Match struct {
// Handler is the interface for all handler types.
// Actual types will implement handler-specific fields.
type Handler map[string]interface{}
type Handler map[string]any
// ReverseProxyHandler creates a reverse_proxy handler.
// application: "none", "plex", "jellyfin", "emby", "homeassistant", "nextcloud", "vaultwarden"
@@ -128,14 +128,14 @@ func ReverseProxyHandler(dial string, enableWS bool, application string, enableS
h := Handler{
"handler": "reverse_proxy",
"flush_interval": -1, // Disable buffering for better streaming performance (Plex, etc.)
"upstreams": []map[string]interface{}{
"upstreams": []map[string]any{
{"dial": dial},
},
}
// Build headers configuration
headers := make(map[string]interface{})
requestHeaders := make(map[string]interface{})
headers := make(map[string]any)
requestHeaders := make(map[string]any)
setHeaders := make(map[string][]string)
// STEP 1: Standard proxy headers (if feature enabled)
@@ -202,7 +202,7 @@ func ReverseProxyHandler(dial string, enableWS bool, application string, enableS
func HeaderHandler(headers map[string][]string) Handler {
return Handler{
"handler": "headers",
"response": map[string]interface{}{
"response": map[string]any{
"set": headers,
},
}
@@ -260,5 +260,5 @@ type AutomationConfig struct {
// AutomationPolicy defines certificate management for specific domains.
type AutomationPolicy struct {
Subjects []string `json:"subjects,omitempty"`
IssuersRaw []interface{} `json:"issuers,omitempty"`
IssuersRaw []any `json:"issuers,omitempty"`
}

View File

@@ -12,8 +12,8 @@ func TestReverseProxyHandler_PlexAndOthers(t *testing.T) {
h := ReverseProxyHandler("app:32400", false, "plex", true)
require.Equal(t, "reverse_proxy", h["handler"])
// Assert headers exist
if hdrs, ok := h["headers"].(map[string]interface{}); ok {
req := hdrs["request"].(map[string]interface{})
if hdrs, ok := h["headers"].(map[string]any); ok {
req := hdrs["request"].(map[string]any)
set := req["set"].(map[string][]string)
require.Contains(t, set, "X-Plex-Client-Identifier")
require.Contains(t, set, "X-Real-IP")
@@ -27,8 +27,8 @@ func TestReverseProxyHandler_PlexAndOthers(t *testing.T) {
// Jellyfin should include X-Real-IP and standard headers when enabled
h2 := ReverseProxyHandler("app:8096", true, "jellyfin", true)
require.Equal(t, "reverse_proxy", h2["handler"])
if hdrs, ok := h2["headers"].(map[string]interface{}); ok {
req := hdrs["request"].(map[string]interface{})
if hdrs, ok := h2["headers"].(map[string]any); ok {
req := hdrs["request"].(map[string]any)
set := req["set"].(map[string][]string)
require.Contains(t, set, "X-Real-IP")
require.Contains(t, set, "X-Forwarded-Proto")
@@ -52,10 +52,10 @@ func TestReverseProxyHandler_WebSocketHeaders(t *testing.T) {
h := ReverseProxyHandler("app:8080", true, "none", true)
require.Equal(t, "reverse_proxy", h["handler"])
hdrs, ok := h["headers"].(map[string]interface{})
hdrs, ok := h["headers"].(map[string]any)
require.True(t, ok, "expected headers map when enableWS=true and enableStandardHeaders=true")
req, ok := hdrs["request"].(map[string]interface{})
req, ok := hdrs["request"].(map[string]any)
require.True(t, ok, "expected request headers")
set, ok := req["set"].(map[string][]string)
@@ -97,10 +97,10 @@ func TestReverseProxyHandler_StandardProxyHeadersAlwaysSet(t *testing.T) {
require.Equal(t, "reverse_proxy", h["handler"])
// With enableStandardHeaders=true, headers should exist
hdrs, ok := h["headers"].(map[string]interface{})
hdrs, ok := h["headers"].(map[string]any)
require.True(t, ok, "expected headers map when enableStandardHeaders=true")
req, ok := hdrs["request"].(map[string]interface{})
req, ok := hdrs["request"].(map[string]any)
require.True(t, ok, "expected request headers")
set, ok := req["set"].(map[string][]string)
@@ -136,8 +136,8 @@ func TestReverseProxyHandler_StandardProxyHeadersAlwaysSet(t *testing.T) {
func TestReverseProxyHandler_ApplicationSpecificHeaders(t *testing.T) {
// Test Plex with standard headers enabled
hPlex := ReverseProxyHandler("app:32400", false, "plex", true)
hdrs := hPlex["headers"].(map[string]interface{})
set := hdrs["request"].(map[string]interface{})["set"].(map[string][]string)
hdrs := hPlex["headers"].(map[string]any)
set := hdrs["request"].(map[string]any)["set"].(map[string][]string)
// Verify Plex-specific headers
require.Contains(t, set, "X-Plex-Client-Identifier")
@@ -156,8 +156,8 @@ func TestReverseProxyHandler_ApplicationSpecificHeaders(t *testing.T) {
// Test Jellyfin with standard headers enabled
hJellyfin := ReverseProxyHandler("app:8096", false, "jellyfin", true)
hdrsJ := hJellyfin["headers"].(map[string]interface{})
setJ := hdrsJ["request"].(map[string]interface{})["set"].(map[string][]string)
hdrsJ := hJellyfin["headers"].(map[string]any)
setJ := hdrsJ["request"].(map[string]any)["set"].(map[string][]string)
// Verify standard headers present for Jellyfin
require.Contains(t, setJ, "X-Real-IP")
@@ -175,8 +175,8 @@ func TestReverseProxyHandler_WebSocketWithApplication(t *testing.T) {
h := ReverseProxyHandler("app:8096", true, "jellyfin", true)
require.Equal(t, "reverse_proxy", h["handler"])
hdrs := h["headers"].(map[string]interface{})
set := hdrs["request"].(map[string]interface{})["set"].(map[string][]string)
hdrs := h["headers"].(map[string]any)
set := hdrs["request"].(map[string]any)["set"].(map[string][]string)
// Verify all 6 headers present (4 standard + 2 WebSocket)
require.Contains(t, set, "X-Real-IP")
@@ -210,8 +210,8 @@ func TestReverseProxyHandler_FeatureFlagDisabled(t *testing.T) {
// Test: Standard headers disabled with Plex (backward compatibility)
hPlex := ReverseProxyHandler("app:32400", false, "plex", false)
hdrsPlex := hPlex["headers"].(map[string]interface{})
setPlex := hdrsPlex["request"].(map[string]interface{})["set"].(map[string][]string)
hdrsPlex := hPlex["headers"].(map[string]any)
setPlex := hdrsPlex["request"].(map[string]any)["set"].(map[string][]string)
// Should still have X-Real-IP and X-Forwarded-Host from application logic
require.Contains(t, setPlex, "X-Real-IP")
@@ -225,23 +225,23 @@ func TestReverseProxyHandler_FeatureFlagDisabled(t *testing.T) {
func TestReverseProxyHandler_XForwardedForNotDuplicated(t *testing.T) {
// Test with standard headers enabled
h := ReverseProxyHandler("app:8080", false, "none", true)
hdrs := h["headers"].(map[string]interface{})
set := hdrs["request"].(map[string]interface{})["set"].(map[string][]string)
hdrs := h["headers"].(map[string]any)
set := hdrs["request"].(map[string]any)["set"].(map[string][]string)
// Verify X-Forwarded-For is NOT in the setHeaders map
require.NotContains(t, set, "X-Forwarded-For", "X-Forwarded-For must NOT be explicitly set (Caddy handles it natively)")
// Test with WebSocket enabled
h2 := ReverseProxyHandler("app:8080", true, "none", true)
hdrs2 := h2["headers"].(map[string]interface{})
set2 := hdrs2["request"].(map[string]interface{})["set"].(map[string][]string)
hdrs2 := h2["headers"].(map[string]any)
set2 := hdrs2["request"].(map[string]any)["set"].(map[string][]string)
require.NotContains(t, set2, "X-Forwarded-For", "X-Forwarded-For must NOT be explicitly set even with WebSocket")
// Test with application
h3 := ReverseProxyHandler("app:32400", false, "plex", true)
hdrs3 := h3["headers"].(map[string]interface{})
set3 := hdrs3["request"].(map[string]interface{})["set"].(map[string][]string)
hdrs3 := h3["headers"].(map[string]any)
set3 := hdrs3["request"].(map[string]any)["set"].(map[string][]string)
require.NotContains(t, set3, "X-Forwarded-For", "X-Forwarded-For must NOT be explicitly set even with Plex")
}

View File

@@ -124,7 +124,7 @@ func validateHandler(handler Handler) error {
}
func validateReverseProxy(handler Handler) error {
upstreams, ok := handler["upstreams"].([]map[string]interface{})
upstreams, ok := handler["upstreams"].([]map[string]any)
if !ok {
return fmt.Errorf("reverse_proxy missing upstreams")
}

View File

@@ -74,7 +74,7 @@ func TestValidateListenAddr_InvalidPortNonNumeric(t *testing.T) {
func TestValidate_MarshalError(t *testing.T) {
// stub jsonMarshalValidate to cause Marshal error
orig := jsonMarshalValidate
jsonMarshalValidate = func(v interface{}) ([]byte, error) { return nil, fmt.Errorf("marshal error") }
jsonMarshalValidate = func(v any) ([]byte, error) { return nil, fmt.Errorf("marshal error") }
defer func() { jsonMarshalValidate = orig }()
cfg := &Config{Apps: Apps{HTTP: &HTTPApp{Servers: map[string]*Server{"srv": {Listen: []string{":80"}, Routes: []*Route{{Match: []Match{{Host: []string{"x.com"}}}, Handle: []Handler{{"handler": "file_server"}}}}}}}}}

Some files were not shown because too many files have changed in this diff Show More