Complete lint remediation addressing errcheck, gosec, and staticcheck violations across backend test files. Tighten pre-commit configuration to prevent future blind spots. Key Changes: - Fix 61 Go linting issues (errcheck, gosec G115/G301/G304/G306, bodyclose) - Add proper error handling for json.Unmarshal, os.Setenv, db.Close(), w.Write() - Fix gosec G115 integer overflow with strconv.FormatUint - Add #nosec annotations with justifications for test fixtures - Fix SecurityService goroutine leaks (add Close() calls) - Fix CrowdSec tar.gz non-deterministic ordering with sorted keys Pre-commit Hardening: - Remove test file exclusion from golangci-lint hook - Add gosec to .golangci-fast.yml with critical checks (G101, G110, G305) - Replace broad .golangci.yml exclusions with targeted path-specific rules - Test files now linted on every commit Test Fixes: - Fix emergency route count assertions (1→2 for dual-port setup) - Fix DNS provider service tests with proper mock setup - Fix certificate service tests with deterministic behavior Backend: 27 packages pass, 83.5% coverage Frontend: 0 lint warnings, 0 TypeScript errors Pre-commit: All 14 hooks pass (~37s)
169 lines
5.5 KiB
Go
169 lines
5.5 KiB
Go
package services
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/Wikid82/charon/backend/internal/config"
|
|
"github.com/Wikid82/charon/backend/internal/models"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestLogService(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "cpm-log-service-test")
|
|
require.NoError(t, err)
|
|
defer func() { _ = os.RemoveAll(tmpDir) }()
|
|
|
|
dataDir := filepath.Join(tmpDir, "data")
|
|
logsDir := filepath.Join(dataDir, "logs")
|
|
err = os.MkdirAll(logsDir, 0o750) // #nosec G301 -- test directory
|
|
require.NoError(t, err)
|
|
|
|
// Create sample JSON logs
|
|
logEntry1 := models.CaddyAccessLog{
|
|
Level: "info",
|
|
Ts: 1600000000,
|
|
Msg: "request handled",
|
|
Status: 200,
|
|
}
|
|
logEntry1.Request.Method = "GET"
|
|
logEntry1.Request.Host = "example.com"
|
|
logEntry1.Request.URI = "/"
|
|
logEntry1.Request.RemoteIP = "1.2.3.4"
|
|
|
|
logEntry2 := models.CaddyAccessLog{
|
|
Level: "error",
|
|
Ts: 1600000060,
|
|
Msg: "error handled",
|
|
Status: 500,
|
|
}
|
|
logEntry2.Request.Method = "POST"
|
|
logEntry2.Request.Host = "api.example.com"
|
|
logEntry2.Request.URI = "/submit"
|
|
logEntry2.Request.RemoteIP = "5.6.7.8"
|
|
|
|
line1, _ := json.Marshal(logEntry1)
|
|
line2, _ := json.Marshal(logEntry2)
|
|
|
|
content := string(line1) + "\n" + string(line2) + "\n"
|
|
|
|
err = os.WriteFile(filepath.Join(logsDir, "access.log"), []byte(content), 0o600) // #nosec G306 -- test fixture
|
|
require.NoError(t, err)
|
|
err = os.WriteFile(filepath.Join(logsDir, "other.txt"), []byte("ignore me"), 0o600) // #nosec G306 -- test fixture
|
|
require.NoError(t, err)
|
|
|
|
cfg := &config.Config{DatabasePath: filepath.Join(dataDir, "charon.db")}
|
|
service := NewLogService(cfg)
|
|
|
|
// Test List
|
|
logs, err := service.ListLogs()
|
|
require.NoError(t, err)
|
|
assert.Len(t, logs, 1)
|
|
assert.Equal(t, "access.log", logs[0].Name)
|
|
|
|
// Test QueryLogs - All
|
|
results, total, err := service.QueryLogs("access.log", models.LogFilter{Limit: 10})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(2), total)
|
|
assert.Len(t, results, 2)
|
|
// Should be reversed (newest first)
|
|
assert.Equal(t, 500, results[0].Status)
|
|
assert.Equal(t, 200, results[1].Status)
|
|
|
|
// Test QueryLogs - Filter Status
|
|
results, total, err = service.QueryLogs("access.log", models.LogFilter{Status: "5xx", Limit: 10})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), total)
|
|
assert.Len(t, results, 1)
|
|
assert.Equal(t, 500, results[0].Status)
|
|
|
|
// Test QueryLogs - Filter Host
|
|
results, total, err = service.QueryLogs("access.log", models.LogFilter{Host: "api.example.com", Limit: 10})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), total)
|
|
assert.Len(t, results, 1)
|
|
assert.Equal(t, "api.example.com", results[0].Request.Host)
|
|
|
|
// Test QueryLogs - Search
|
|
results, total, err = service.QueryLogs("access.log", models.LogFilter{Search: "submit", Limit: 10})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), total)
|
|
assert.Len(t, results, 1)
|
|
assert.Equal(t, "/submit", results[0].Request.URI)
|
|
|
|
// Test GetLogPath
|
|
path, err := service.GetLogPath("access.log")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, filepath.Join(logsDir, "access.log"), path)
|
|
|
|
// Test GetLogPath non-existent
|
|
_, err = service.GetLogPath("missing.log")
|
|
assert.Error(t, err)
|
|
|
|
// Test GetLogPath - Invalid
|
|
_, err = service.GetLogPath("nonexistent.log")
|
|
assert.Error(t, err)
|
|
|
|
// Test GetLogPath - Traversal
|
|
_, err = service.GetLogPath("../../etc/passwd")
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "invalid filename")
|
|
|
|
// Test ListLogs - Directory Not Exist
|
|
nonExistService := NewLogService(&config.Config{DatabasePath: filepath.Join(t.TempDir(), "missing", "charon.db")})
|
|
logs, err = nonExistService.ListLogs()
|
|
require.NoError(t, err)
|
|
assert.Empty(t, logs)
|
|
|
|
// Test QueryLogs - Non-JSON Logs
|
|
plainContent := "2023/10/27 10:00:00 Application started\nJust a plain line\n"
|
|
err = os.WriteFile(filepath.Join(logsDir, "app.log"), []byte(plainContent), 0o600) // #nosec G306 -- test fixture
|
|
require.NoError(t, err)
|
|
|
|
results, total, err = service.QueryLogs("app.log", models.LogFilter{Limit: 10})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(2), total)
|
|
// Reverse order check
|
|
assert.Equal(t, "Just a plain line", results[0].Msg)
|
|
assert.Equal(t, "Application started", results[1].Msg)
|
|
assert.Equal(t, "INFO", results[1].Level)
|
|
|
|
// Test QueryLogs - Pagination
|
|
// We have 2 logs in access.log
|
|
results, _, err = service.QueryLogs("access.log", models.LogFilter{Limit: 1, Offset: 0})
|
|
require.NoError(t, err)
|
|
assert.Len(t, results, 1)
|
|
assert.Equal(t, 500, results[0].Status) // Newest first
|
|
|
|
results, _, err = service.QueryLogs("access.log", models.LogFilter{Limit: 1, Offset: 1})
|
|
require.NoError(t, err)
|
|
assert.Len(t, results, 1)
|
|
assert.Equal(t, 200, results[0].Status) // Second newest
|
|
|
|
results, _, err = service.QueryLogs("access.log", models.LogFilter{Limit: 10, Offset: 5})
|
|
require.NoError(t, err)
|
|
assert.Empty(t, results)
|
|
|
|
// Test QueryLogs - Exact Status Match
|
|
results, total, err = service.QueryLogs("access.log", models.LogFilter{Status: "200", Limit: 10})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), total)
|
|
assert.Equal(t, 200, results[0].Status)
|
|
|
|
// Test QueryLogs - Search Fields
|
|
// Search Method
|
|
results, total, err = service.QueryLogs("access.log", models.LogFilter{Search: "POST", Limit: 10})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), total)
|
|
assert.Equal(t, "POST", results[0].Request.Method)
|
|
|
|
// Search RemoteIP
|
|
results, total, err = service.QueryLogs("access.log", models.LogFilter{Search: "5.6.7.8", Limit: 10})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), total)
|
|
assert.Equal(t, "5.6.7.8", results[0].Request.RemoteIP)
|
|
}
|