mirror of
https://github.com/lukaszraczylo/lolcathost.git
synced 2026-06-05 23:29:18 +00:00
29263dc8a2
* gosec govulncheck runs
* Fix flaky TestRateLimiter_Matrix test
The test was failing due to two issues:
1. Test name generation used invalid character conversion (string(rune('0'+limit)))
which produced non-printable characters for limits >= 10
2. Using 10ms windows with 100 requests caused race conditions - early requests
would expire before all 100 were made, allowing the 101st request
Changed to use struct-based test cases with proper fmt.Sprintf naming and
a consistent 1-second window that won't expire during rapid test execution.
213 lines
4.6 KiB
Go
213 lines
4.6 KiB
Go
package daemon
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestRateLimiter_Allow(t *testing.T) {
|
|
t.Run("under limit", func(t *testing.T) {
|
|
rl := NewRateLimiter(5, time.Minute)
|
|
|
|
for i := 0; i < 5; i++ {
|
|
assert.True(t, rl.Allow(123), "request %d should be allowed", i)
|
|
}
|
|
})
|
|
|
|
t.Run("over limit", func(t *testing.T) {
|
|
rl := NewRateLimiter(3, time.Minute)
|
|
|
|
for i := 0; i < 3; i++ {
|
|
assert.True(t, rl.Allow(123))
|
|
}
|
|
|
|
// 4th request should be blocked
|
|
assert.False(t, rl.Allow(123))
|
|
})
|
|
|
|
t.Run("different PIDs", func(t *testing.T) {
|
|
rl := NewRateLimiter(2, time.Minute)
|
|
|
|
// PID 1
|
|
assert.True(t, rl.Allow(1))
|
|
assert.True(t, rl.Allow(1))
|
|
assert.False(t, rl.Allow(1))
|
|
|
|
// PID 2 should have its own limit
|
|
assert.True(t, rl.Allow(2))
|
|
assert.True(t, rl.Allow(2))
|
|
assert.False(t, rl.Allow(2))
|
|
})
|
|
|
|
t.Run("window expiration", func(t *testing.T) {
|
|
rl := NewRateLimiter(2, 10*time.Millisecond)
|
|
|
|
assert.True(t, rl.Allow(123))
|
|
assert.True(t, rl.Allow(123))
|
|
assert.False(t, rl.Allow(123))
|
|
|
|
// Wait for window to expire
|
|
time.Sleep(15 * time.Millisecond)
|
|
|
|
// Should be allowed again
|
|
assert.True(t, rl.Allow(123))
|
|
})
|
|
}
|
|
|
|
func TestRateLimiter_Cleanup(t *testing.T) {
|
|
rl := NewRateLimiter(10, 10*time.Millisecond)
|
|
|
|
// Add requests from multiple PIDs
|
|
for pid := int32(1); pid <= 5; pid++ {
|
|
rl.Allow(pid)
|
|
}
|
|
|
|
assert.Len(t, rl.requests, 5)
|
|
|
|
// Wait for expiration
|
|
time.Sleep(15 * time.Millisecond)
|
|
|
|
// Cleanup
|
|
rl.Cleanup()
|
|
|
|
assert.Empty(t, rl.requests)
|
|
}
|
|
|
|
func TestAuditLogger_Log(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
logPath := filepath.Join(tmpDir, "audit.log")
|
|
|
|
logger, err := NewAuditLogger(logPath)
|
|
require.NoError(t, err)
|
|
defer logger.Close()
|
|
|
|
logger.Log(1000, 12345, "set", map[string]string{"alias": "test"}, true, "")
|
|
logger.Log(1000, 12345, "sync", nil, false, "sync failed")
|
|
|
|
// Read log file
|
|
content, err := os.ReadFile(logPath)
|
|
require.NoError(t, err)
|
|
|
|
contentStr := string(content)
|
|
assert.Contains(t, contentStr, `"action":"set"`)
|
|
assert.Contains(t, contentStr, `"uid":1000`)
|
|
assert.Contains(t, contentStr, `"pid":12345`)
|
|
assert.Contains(t, contentStr, `"success":true`)
|
|
assert.Contains(t, contentStr, `"action":"sync"`)
|
|
assert.Contains(t, contentStr, `"success":false`)
|
|
assert.Contains(t, contentStr, `"error":"sync failed"`)
|
|
}
|
|
|
|
func TestAuditLogger_CreatesDirectory(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
logPath := filepath.Join(tmpDir, "subdir", "audit.log")
|
|
|
|
logger, err := NewAuditLogger(logPath)
|
|
require.NoError(t, err)
|
|
defer logger.Close()
|
|
|
|
// Verify directory was created
|
|
_, err = os.Stat(filepath.Dir(logPath))
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestAuditLogger_Close(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
logPath := filepath.Join(tmpDir, "audit.log")
|
|
|
|
logger, err := NewAuditLogger(logPath)
|
|
require.NoError(t, err)
|
|
|
|
err = logger.Close()
|
|
assert.NoError(t, err)
|
|
|
|
// Closing again should not error
|
|
err = logger.Close()
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestPeerCredentials(t *testing.T) {
|
|
creds := &PeerCredentials{
|
|
UID: 501,
|
|
GID: 20,
|
|
PID: 12345,
|
|
}
|
|
|
|
assert.Equal(t, uint32(501), creds.UID)
|
|
assert.Equal(t, uint32(20), creds.GID)
|
|
assert.Equal(t, int32(12345), creds.PID)
|
|
}
|
|
|
|
// Matrix test for rate limiting
|
|
func TestRateLimiter_Matrix(t *testing.T) {
|
|
testCases := []struct {
|
|
limit int
|
|
window time.Duration
|
|
}{
|
|
{1, time.Second},
|
|
{5, time.Second},
|
|
{10, time.Second},
|
|
{100, time.Second},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(
|
|
fmt.Sprintf("limit=%d_window=%s", tc.limit, tc.window),
|
|
func(t *testing.T) {
|
|
rl := NewRateLimiter(tc.limit, tc.window)
|
|
|
|
// Should allow exactly 'limit' requests
|
|
for i := 0; i < tc.limit; i++ {
|
|
assert.True(t, rl.Allow(1), "request %d should be allowed", i)
|
|
}
|
|
|
|
// Next should be blocked
|
|
assert.False(t, rl.Allow(1), "request after limit should be blocked")
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
func BenchmarkRateLimiter_Allow(b *testing.B) {
|
|
rl := NewRateLimiter(RateLimit, RateLimitWindow)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
rl.Allow(int32(i % 100))
|
|
}
|
|
}
|
|
|
|
func BenchmarkRateLimiter_Cleanup(b *testing.B) {
|
|
rl := NewRateLimiter(RateLimit, RateLimitWindow)
|
|
|
|
// Pre-populate with requests
|
|
for i := 0; i < 1000; i++ {
|
|
rl.Allow(int32(i))
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
rl.Cleanup()
|
|
}
|
|
}
|
|
|
|
func BenchmarkAuditLogger_Log(b *testing.B) {
|
|
tmpDir := b.TempDir()
|
|
logPath := filepath.Join(tmpDir, "audit.log")
|
|
|
|
logger, err := NewAuditLogger(logPath)
|
|
require.NoError(b, err)
|
|
defer logger.Close()
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
logger.Log(1000, 12345, "set", map[string]string{"alias": "test"}, true, "")
|
|
}
|
|
}
|