Files
PrivateCaptcha/pkg/db/staticcache_test.go
Copilot bc5f8a9d6e Add test coverage for maintenance jobs, rate limiter, cache, and portal handlers (#241)
* Initial plan

* Add unit tests for improved test coverage

Co-authored-by: ribtoks <505555+ribtoks@users.noreply.github.com>

* Address code review feedback

Co-authored-by: ribtoks <505555+ribtoks@users.noreply.github.com>

* Move integration tests to pkg/api and pkg/portal, fix TestResend2FA logic

Co-authored-by: ribtoks <505555+ribtoks@users.noreply.github.com>

* Fix code review issues: remove unused import and fix job name generation

Co-authored-by: ribtoks <505555+ribtoks@users.noreply.github.com>

* Fix TestResend2FA tests: add CSRF token and verify codes are different

Co-authored-by: ribtoks <505555+ribtoks@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ribtoks <505555+ribtoks@users.noreply.github.com>
2026-01-08 19:56:39 +02:00

221 lines
5.6 KiB
Go

package db
import (
"context"
"testing"
"time"
"github.com/PrivateCaptcha/PrivateCaptcha/pkg/common"
)
func TestStaticCacheNewCache(t *testing.T) {
cache := NewStaticCache[string, int](100, -1)
if cache.Missing() != -1 {
t.Errorf("Expected missing value to be -1, got %d", cache.Missing())
}
if cache.HitRatio() != 0.0 {
t.Errorf("Expected hit ratio to be 0.0, got %f", cache.HitRatio())
}
}
func TestStaticCacheSetAndGet(t *testing.T) {
ctx := context.Background()
cache := NewStaticCache[string, int](100, -1)
// Set a value
err := cache.Set(ctx, "key1", 42)
if err != nil {
t.Fatalf("Failed to set value: %v", err)
}
// Get the value
val, err := cache.Get(ctx, "key1")
if err != nil {
t.Fatalf("Failed to get value: %v", err)
}
if val != 42 {
t.Errorf("Expected value 42, got %d", val)
}
}
func TestStaticCacheGetMiss(t *testing.T) {
ctx := context.Background()
cache := NewStaticCache[string, int](100, -1)
// Try to get a non-existent key
_, err := cache.Get(ctx, "nonexistent")
if err != ErrCacheMiss {
t.Errorf("Expected ErrCacheMiss, got %v", err)
}
}
func TestStaticCacheSetMissing(t *testing.T) {
ctx := context.Background()
cache := NewStaticCache[string, int](100, -1)
// Set a key as missing
err := cache.SetMissing(ctx, "missing_key")
if err != nil {
t.Fatalf("Failed to set missing: %v", err)
}
// Try to get the missing key
_, err = cache.Get(ctx, "missing_key")
if err != ErrNegativeCacheHit {
t.Errorf("Expected ErrNegativeCacheHit, got %v", err)
}
}
func TestStaticCacheDelete(t *testing.T) {
ctx := context.Background()
cache := NewStaticCache[string, int](100, -1)
// Set and then delete
_ = cache.Set(ctx, "to_delete", 100)
found := cache.Delete(ctx, "to_delete")
if !found {
t.Error("Expected Delete to return true for existing key")
}
// Verify deletion
_, err := cache.Get(ctx, "to_delete")
if err != ErrCacheMiss {
t.Errorf("Expected ErrCacheMiss after delete, got %v", err)
}
// Delete non-existent key
found = cache.Delete(ctx, "nonexistent")
if found {
t.Error("Expected Delete to return false for non-existent key")
}
}
func TestStaticCacheSetWithTTL(t *testing.T) {
ctx := context.Background()
cache := NewStaticCache[string, int](100, -1)
// SetWithTTL should work like Set (TTL is ignored in static cache)
err := cache.SetWithTTL(ctx, "ttl_key", 200, 1*time.Hour)
if err != nil {
t.Fatalf("Failed to SetWithTTL: %v", err)
}
val, err := cache.Get(ctx, "ttl_key")
if err != nil {
t.Fatalf("Failed to get value: %v", err)
}
if val != 200 {
t.Errorf("Expected value 200, got %d", val)
}
}
func TestStaticCacheSetTTL(t *testing.T) {
ctx := context.Background()
cache := NewStaticCache[string, int](100, -1)
// SetTTL should return ErrInvalidInput (not supported)
err := cache.SetTTL(ctx, "key1", 1*time.Hour)
if err != ErrInvalidInput {
t.Errorf("Expected ErrInvalidInput from SetTTL, got %v", err)
}
}
func TestStaticCacheCompression(t *testing.T) {
ctx := context.Background()
capacity := 10
cache := NewStaticCache[int, int](capacity, -1)
// Fill the cache beyond capacity to trigger compression
for i := 0; i < capacity+5; i++ {
_ = cache.Set(ctx, i, i*10)
}
// After compression, the cache should have approximately lowerBound entries
// lowerBound = capacity/2 + capacity/4 = 5 + 2 = 7
// Plus some items added after compression
lowerBound := capacity/2 + capacity/4
actualCount := 0
for i := 0; i < capacity+5; i++ {
if _, err := cache.Get(ctx, i); err == nil {
actualCount++
}
}
// Just verify compression happened - the cache should have less than capacity+5 items
// and ideally around lowerBound + a few more
if actualCount > capacity {
t.Errorf("Cache should have compressed, expected at most %d items, got %d", capacity, actualCount)
}
if actualCount < lowerBound {
t.Errorf("Cache should have at least %d items after compression, got %d", lowerBound, actualCount)
}
}
type stubCacheLoader struct {
loadCount int
value int
}
func (l *stubCacheLoader) Load(ctx context.Context, key string) (int, error) {
l.loadCount++
return l.value, nil
}
func (l *stubCacheLoader) Reload(ctx context.Context, key string, oldValue int) (int, error) {
l.loadCount++
return l.value, nil
}
var _ common.CacheLoader[string, int] = (*stubCacheLoader)(nil)
func TestStaticCacheGetEx(t *testing.T) {
ctx := context.Background()
cache := NewStaticCache[string, int](100, -1)
loader := &stubCacheLoader{value: 999}
// First call should trigger the loader
val, err := cache.GetEx(ctx, "new_key", loader)
if err != nil {
t.Fatalf("GetEx failed: %v", err)
}
if val != 999 {
t.Errorf("Expected value 999, got %d", val)
}
if loader.loadCount != 1 {
t.Errorf("Expected loader to be called once, called %d times", loader.loadCount)
}
// Second call should use cached value (loader not called again)
val, err = cache.GetEx(ctx, "new_key", loader)
if err != nil {
t.Fatalf("GetEx failed on second call: %v", err)
}
if val != 999 {
t.Errorf("Expected value 999, got %d", val)
}
if loader.loadCount != 1 {
t.Errorf("Expected loader to still be called once, called %d times", loader.loadCount)
}
}
func TestStaticCacheGetExMissingValue(t *testing.T) {
ctx := context.Background()
missingValue := -1
cache := NewStaticCache[string, int](100, missingValue)
// Loader that returns the missing value
loader := &stubCacheLoader{value: missingValue}
// When loader returns missing value, GetEx should return ErrNegativeCacheHit
_, err := cache.GetEx(ctx, "will_be_missing", loader)
if err != ErrNegativeCacheHit {
t.Errorf("Expected ErrNegativeCacheHit when loader returns missing value, got %v", err)
}
}