412 lines
12 KiB
Go
412 lines
12 KiB
Go
package cache
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.nakama.town/fmartingr/gotoolkit/model"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNewFileCache(t *testing.T) {
|
|
// Create a test folder name
|
|
testFolderName := "test-cache"
|
|
|
|
// Test initialization with the temp directory
|
|
cache, err := NewFileCache(testFolderName)
|
|
require.NoError(t, err, "NewFileCache should not return an error")
|
|
require.NotNil(t, cache, "NewFileCache should return a non-nil cache")
|
|
|
|
// Verify the directory was created (not checking the exact path as it depends on the os.UserCacheDir output)
|
|
dirInfo, err := os.Stat(cache.path)
|
|
require.NoError(t, err, "Cache directory should exist")
|
|
assert.True(t, dirInfo.IsDir(), "Cache path should be a directory")
|
|
}
|
|
|
|
func TestFileCache_Set(t *testing.T) {
|
|
// Create a temporary directory for testing
|
|
tempDir := t.TempDir()
|
|
cache := setupTestFileCache(t, tempDir)
|
|
|
|
// Test simple set operation
|
|
testKey := "test-key"
|
|
testValue := []byte("test-value")
|
|
err := cache.Set(testKey, testValue)
|
|
require.NoError(t, err, "Set should not return an error")
|
|
|
|
// Verify the file exists
|
|
filePath := filepath.Join(cache.path, testKey)
|
|
assert.FileExists(t, filePath, "Cache file should exist")
|
|
assert.FileExists(t, filePath+".metadata", "Cache metadata file should exist")
|
|
|
|
// Read the file contents
|
|
content, err := os.ReadFile(filePath)
|
|
require.NoError(t, err, "Should be able to read cache file")
|
|
assert.Equal(t, testValue, content, "File content should match set value")
|
|
|
|
// Test set with TTL
|
|
ttlKey := "ttl-key"
|
|
ttlValue := []byte("ttl-value")
|
|
ttlDuration := 1 * time.Hour
|
|
withTTL := func(item *model.CacheItem) {
|
|
expiry := time.Now().Add(ttlDuration)
|
|
item.TTL = &expiry
|
|
}
|
|
|
|
err = cache.Set(ttlKey, ttlValue, withTTL)
|
|
require.NoError(t, err, "Set with TTL should not return an error")
|
|
|
|
// Verify file exists
|
|
ttlFilePath := filepath.Join(cache.path, ttlKey)
|
|
assert.FileExists(t, ttlFilePath, "Cache file with TTL should exist")
|
|
assert.FileExists(t, ttlFilePath+".metadata", "Cache metadata file with TTL should exist")
|
|
|
|
// Verify the metadata has TTL set
|
|
metadata, err := cache.getMetadata(ttlKey)
|
|
require.NoError(t, err, "Should be able to get metadata")
|
|
assert.NotNil(t, metadata.TTL, "TTL should be set in metadata")
|
|
}
|
|
|
|
func TestFileCache_SetTableDriven(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
tests := []struct {
|
|
name string
|
|
key string
|
|
value []byte
|
|
withTTL bool
|
|
ttlDelta time.Duration
|
|
}{
|
|
{
|
|
name: "basic set without TTL",
|
|
key: "key1",
|
|
value: []byte("value1"),
|
|
},
|
|
{
|
|
name: "set with future TTL",
|
|
key: "key2",
|
|
value: []byte("value2"),
|
|
withTTL: true,
|
|
ttlDelta: 1 * time.Hour, // Future time
|
|
},
|
|
{
|
|
name: "set with past TTL",
|
|
key: "key3",
|
|
value: []byte("value3"),
|
|
withTTL: true,
|
|
ttlDelta: -1 * time.Hour, // Past time
|
|
},
|
|
{
|
|
name: "set with empty value",
|
|
key: "key4",
|
|
value: []byte{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cache := setupTestFileCache(t, tempDir)
|
|
|
|
var opts []model.CacheOption
|
|
if tt.withTTL {
|
|
opts = append(opts, func(item *model.CacheItem) {
|
|
expiry := time.Now().Add(tt.ttlDelta)
|
|
item.TTL = &expiry
|
|
})
|
|
}
|
|
|
|
err := cache.Set(tt.key, tt.value, opts...)
|
|
require.NoError(t, err, "Set should not return an error")
|
|
|
|
// Verify file and metadata exist
|
|
filePath := filepath.Join(cache.path, tt.key)
|
|
assert.FileExists(t, filePath, "Cache file should exist")
|
|
assert.FileExists(t, filePath+".metadata", "Cache metadata file should exist")
|
|
|
|
// Verify file content
|
|
content, err := os.ReadFile(filePath)
|
|
require.NoError(t, err, "Should be able to read cache file")
|
|
assert.Equal(t, tt.value, content, "File content should match set value")
|
|
|
|
// Verify metadata
|
|
metadata, err := cache.getMetadata(tt.key)
|
|
require.NoError(t, err, "Should be able to get metadata")
|
|
assert.Equal(t, tt.key, metadata.Key, "Metadata key should match")
|
|
|
|
if tt.withTTL {
|
|
assert.NotNil(t, metadata.TTL, "TTL should be set in metadata")
|
|
expectedTime := time.Now().Add(tt.ttlDelta)
|
|
assert.WithinDuration(t, expectedTime, *metadata.TTL, 2*time.Second, "TTL should be close to expected value")
|
|
} else {
|
|
assert.Nil(t, metadata.TTL, "TTL should not be set for items without TTL")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFileCache_Get(t *testing.T) {
|
|
// Create a temporary directory for testing
|
|
tempDir := t.TempDir()
|
|
cache := setupTestFileCache(t, tempDir)
|
|
|
|
// Set up test data
|
|
validKey := "valid-key"
|
|
validValue := []byte("valid-value")
|
|
err := cache.Set(validKey, validValue)
|
|
require.NoError(t, err, "Set should not return an error for setup")
|
|
|
|
// Test getting valid key
|
|
retrievedValue, err := cache.Get(validKey)
|
|
require.NoError(t, err, "Get should not return an error for valid key")
|
|
assert.Equal(t, validValue, retrievedValue, "Retrieved value should match original")
|
|
|
|
// Test getting non-existent key
|
|
_, err = cache.Get("nonexistent-key")
|
|
assert.ErrorIs(t, err, model.ErrCacheKeyDontExist, "Get should return ErrCacheKeyDontExist for non-existent key")
|
|
|
|
// Test getting expired key
|
|
expiredKey := "expired-key"
|
|
expiredValue := []byte("expired-value")
|
|
expiredTTL := func(item *model.CacheItem) {
|
|
expiry := time.Now().Add(-1 * time.Minute) // Past time
|
|
item.TTL = &expiry
|
|
}
|
|
|
|
err = cache.Set(expiredKey, expiredValue, expiredTTL)
|
|
require.NoError(t, err, "Set should not return an error for expired key setup")
|
|
|
|
_, err = cache.Get(expiredKey)
|
|
assert.ErrorIs(t, err, model.ErrCacheKeyDontExist, "Get should return ErrCacheKeyDontExist for expired key")
|
|
|
|
// Verify the expired files are deleted
|
|
expiredFilePath := filepath.Join(cache.path, expiredKey)
|
|
expiredMetadataPath := expiredFilePath + ".metadata"
|
|
assert.NoFileExists(t, expiredFilePath, "Expired cache file should be deleted")
|
|
assert.NoFileExists(t, expiredMetadataPath, "Expired cache metadata file should be deleted")
|
|
}
|
|
|
|
func TestFileCache_GetTableDriven(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
tests := []struct {
|
|
name string
|
|
setupKeys map[string][]byte
|
|
setupTTLs map[string]time.Duration
|
|
getKey string
|
|
expectValue []byte
|
|
expectError error
|
|
checkFileDeleted bool
|
|
}{
|
|
{
|
|
name: "get existing key",
|
|
setupKeys: map[string][]byte{
|
|
"test-key": []byte("test-value"),
|
|
},
|
|
getKey: "test-key",
|
|
expectValue: []byte("test-value"),
|
|
expectError: nil,
|
|
},
|
|
{
|
|
name: "get non-existent key",
|
|
setupKeys: map[string][]byte{
|
|
"test-key": []byte("test-value"),
|
|
},
|
|
getKey: "nonexistent-key",
|
|
expectValue: nil,
|
|
expectError: model.ErrCacheKeyDontExist,
|
|
},
|
|
{
|
|
name: "get expired key",
|
|
setupKeys: map[string][]byte{
|
|
"expired-key": []byte("expired-value"),
|
|
},
|
|
setupTTLs: map[string]time.Duration{
|
|
"expired-key": -1 * time.Minute, // Past time
|
|
},
|
|
getKey: "expired-key",
|
|
expectValue: nil,
|
|
expectError: model.ErrCacheKeyDontExist,
|
|
checkFileDeleted: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cache := setupTestFileCache(t, tempDir)
|
|
|
|
// Setup test data
|
|
for key, value := range tt.setupKeys {
|
|
var opts []model.CacheOption
|
|
if ttl, ok := tt.setupTTLs[key]; ok {
|
|
opts = append(opts, func(item *model.CacheItem) {
|
|
expiry := time.Now().Add(ttl)
|
|
item.TTL = &expiry
|
|
})
|
|
}
|
|
|
|
err := cache.Set(key, value, opts...)
|
|
require.NoError(t, err, "Setup: Set should not return an error")
|
|
}
|
|
|
|
// Test get
|
|
value, err := cache.Get(tt.getKey)
|
|
|
|
if tt.expectError != nil {
|
|
assert.ErrorIs(t, err, tt.expectError)
|
|
assert.Nil(t, value)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.expectValue, value)
|
|
}
|
|
|
|
// Check if expired files are deleted
|
|
if tt.checkFileDeleted {
|
|
filePath := filepath.Join(cache.path, tt.getKey)
|
|
metadataPath := filePath + ".metadata"
|
|
assert.NoFileExists(t, filePath, "Expired cache file should be deleted")
|
|
assert.NoFileExists(t, metadataPath, "Expired cache metadata file should be deleted")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFileCache_Delete(t *testing.T) {
|
|
// Create a temporary directory for testing
|
|
tempDir := t.TempDir()
|
|
cache := setupTestFileCache(t, tempDir)
|
|
|
|
// Set up test data
|
|
testKey := "delete-key"
|
|
testValue := []byte("delete-value")
|
|
err := cache.Set(testKey, testValue)
|
|
require.NoError(t, err, "Set should not return an error for setup")
|
|
|
|
// Verify files exist before delete
|
|
filePath := filepath.Join(cache.path, testKey)
|
|
metadataPath := filePath + ".metadata"
|
|
assert.FileExists(t, filePath, "Cache file should exist before delete")
|
|
assert.FileExists(t, metadataPath, "Cache metadata file should exist before delete")
|
|
|
|
// Test delete
|
|
err = cache.Delete(testKey)
|
|
require.NoError(t, err, "Delete should not return an error")
|
|
|
|
// Verify files are deleted
|
|
assert.NoFileExists(t, filePath, "Cache file should be deleted")
|
|
assert.NoFileExists(t, metadataPath, "Cache metadata file should be deleted")
|
|
|
|
// Test deleting non-existent key
|
|
err = cache.Delete("nonexistent-key")
|
|
assert.Error(t, err, "Delete should return an error for non-existent key")
|
|
}
|
|
|
|
func TestFileCache_DeleteTableDriven(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
tests := []struct {
|
|
name string
|
|
setupKey string
|
|
setupValue []byte
|
|
deleteKey string
|
|
expectError bool
|
|
checkNotExist bool
|
|
}{
|
|
{
|
|
name: "delete existing key",
|
|
setupKey: "existing-key",
|
|
setupValue: []byte("existing-value"),
|
|
deleteKey: "existing-key",
|
|
expectError: false,
|
|
checkNotExist: true,
|
|
},
|
|
{
|
|
name: "delete non-existent key",
|
|
deleteKey: "nonexistent-key",
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cache := setupTestFileCache(t, tempDir)
|
|
|
|
// Setup test data if needed
|
|
if tt.setupKey != "" {
|
|
err := cache.Set(tt.setupKey, tt.setupValue)
|
|
require.NoError(t, err, "Setup: Set should not return an error")
|
|
|
|
// Verify files exist before delete
|
|
filePath := filepath.Join(cache.path, tt.setupKey)
|
|
metadataPath := filePath + ".metadata"
|
|
assert.FileExists(t, filePath, "Cache file should exist before delete")
|
|
assert.FileExists(t, metadataPath, "Cache metadata file should exist before delete")
|
|
}
|
|
|
|
// Test delete
|
|
err := cache.Delete(tt.deleteKey)
|
|
|
|
if tt.expectError {
|
|
assert.Error(t, err, "Delete should return an error")
|
|
} else {
|
|
assert.NoError(t, err, "Delete should not return an error")
|
|
}
|
|
|
|
// Check files are deleted if needed
|
|
if tt.checkNotExist {
|
|
filePath := filepath.Join(cache.path, tt.deleteKey)
|
|
metadataPath := filePath + ".metadata"
|
|
assert.NoFileExists(t, filePath, "Cache file should be deleted")
|
|
assert.NoFileExists(t, metadataPath, "Cache metadata file should be deleted")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFileCache_GetPathForItem(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
cache := setupTestFileCache(t, tempDir)
|
|
|
|
key := "test-key"
|
|
expectedPath := filepath.Join(cache.path, key)
|
|
actualPath := cache.getPathForItem(key)
|
|
|
|
assert.Equal(t, expectedPath, actualPath, "Path for item should match expected format")
|
|
}
|
|
|
|
func TestFileCache_GetSetMetadata(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
cache := setupTestFileCache(t, tempDir)
|
|
|
|
// Create test metadata
|
|
key := "metadata-key"
|
|
now := time.Now()
|
|
metadata := model.CacheItem{
|
|
Key: key,
|
|
Value: nil,
|
|
TTL: &now,
|
|
}
|
|
|
|
// Test setMetadata
|
|
err := cache.setMetadata(key, metadata)
|
|
require.NoError(t, err, "setMetadata should not return an error")
|
|
|
|
// Verify metadata file exists
|
|
metadataPath := filepath.Join(cache.path, key) + ".metadata"
|
|
assert.FileExists(t, metadataPath, "Metadata file should exist")
|
|
|
|
// Test getMetadata
|
|
retrievedMetadata, err := cache.getMetadata(key)
|
|
require.NoError(t, err, "getMetadata should not return an error")
|
|
assert.Equal(t, key, retrievedMetadata.Key, "Retrieved metadata key should match original")
|
|
assert.WithinDuration(t, now, *retrievedMetadata.TTL, time.Second, "Retrieved metadata TTL should be close to original")
|
|
}
|
|
|
|
// Helper function to create a FileCache for testing
|
|
func setupTestFileCache(t *testing.T, tempDir string) *FileCache {
|
|
return &FileCache{
|
|
path: tempDir,
|
|
}
|
|
}
|