-- Unit tests for caching utilities local cache = require('notex.utils.cache') describe("cache utilities", function() before_each(function() -- Reset cache before each test cache.init({ memory = {enabled = true, max_size = 10}, lru = {enabled = true, max_size = 5}, timed = {enabled = true, default_ttl = 1} }) end) after_each(function() cache.cleanup() end) describe("memory cache", function() it("should store and retrieve values", function() local ok = cache.memory_set("test_key", "test_value") assert.is_true(ok) local value = cache.memory_get("test_key") assert.equals("test_value", value) end) it("should return nil for non-existent keys", function() local value = cache.memory_get("non_existent") assert.is_nil(value) end) it("should handle cache size limits", function() -- Fill cache beyond max size for i = 1, 15 do cache.memory_set("key_" .. i, "value_" .. i) end -- Should still be able to set new values (eviction happens) local ok = cache.memory_set("new_key", "new_value") assert.is_true(ok) -- Should still be able to get some values local value = cache.memory_get("new_key") assert.equals("new_value", value) end) end) describe("LRU cache", function() it("should store and retrieve values with LRU eviction", function() cache.lru_set("key1", "value1") cache.lru_set("key2", "value2") cache.lru_set("key3", "value3") -- Access key1 to make it most recently used local value = cache.lru_get("key1") assert.equals("value1", value) -- Add more items to trigger eviction cache.lru_set("key4", "value4") cache.lru_set("key5", "value5") cache.lru_set("key6", "value6") -- Should evict key2 (least recently used) -- key2 should be evicted, key1 should still exist assert.is_nil(cache.lru_get("key2")) assert.equals("value1", cache.lru_get("key1")) end) it("should update access order on get", function() cache.lru_set("key1", "value1") cache.lru_set("key2", "value2") -- Get key1 to make it most recently used cache.lru_get("key1") -- Fill cache to evict cache.lru_set("key3", "value3") cache.lru_set("key4", "value4") cache.lru_set("key5", "value4") cache.lru_set("key6", "value4") -- Should evict key2 assert.is_not_nil(cache.lru_get("key1")) -- Should still exist assert.is_nil(cache.lru_get("key2")) -- Should be evicted end) end) describe("timed cache", function() it("should store values with TTL", function() cache.timed_set("test_key", "test_value", 2) -- 2 second TTL local value = cache.timed_get("test_key") assert.equals("test_value", value) end) it("should expire values after TTL", function() cache.timed_set("test_key", "test_value", 1) -- 1 second TTL -- Should be available immediately local value = cache.timed_get("test_key") assert.equals("test_value", value) -- Wait for expiration vim.defer_fn(function() value = cache.timed_get("test_key") assert.is_nil(value) end, 1100) end) it("should use default TTL when not specified", function() cache.timed_set("test_key", "test_value") local value = cache.timed_get("test_key") assert.equals("test_value", value) end) end) describe("generic cache operations", function() it("should set and get with specified cache type", function() local ok = cache.set("test_key", "test_value", "memory") assert.is_true(ok) local value = cache.get("test_key", "memory") assert.equals("test_value", value) end) it("should default to memory cache", function() local ok = cache.set("test_key", "test_value") assert.is_true(ok) local value = cache.get("test_key") assert.equals("test_value", value) end) it("should handle unknown cache types", function() local ok = cache.set("test_key", "test_value", "unknown") assert.is_false(ok) local value = cache.get("test_key", "unknown") assert.is_nil(value) end) end) describe("get_or_set", function() it("should return cached value when exists", function() cache.set("test_key", "cached_value") local value = cache.get_or_set("test_key", function() return "computed_value" end) assert.equals("cached_value", value) end) it("should compute and cache value when not exists", function() local call_count = 0 local value = cache.get_or_set("test_key", function() call_count = call_count + 1 return "computed_value" end) assert.equals("computed_value", value) assert.equals(1, call_count) -- Second call should use cached value value = cache.get_or_set("test_key", function() call_count = call_count + 1 return "new_value" end) assert.equals("computed_value", value) assert.equals(1, call_count) -- Should not be called again end) it("should handle computation errors", function() assert.has_error(function() cache.get_or_set("test_key", function() error("Computation failed") end) end) end) end) describe("multi_get", function() it("should try multiple cache types in order", function() cache.set("test_key", "memory_value", "memory") cache.set("test_key", "lru_value", "lru") local value, cache_type = cache.multi_get("test_key", {"lru", "memory"}) assert.equals("lru_value", value) assert.equals("lru", cache_type) -- Should try memory if lru doesn't have it cache.invalidate("test_key", "lru") value, cache_type = cache.multi_get("test_key", {"lru", "memory"}) assert.equals("memory_value", value) assert.equals("memory", cache_type) end) it("should return nil when not found in any cache", function() local value = cache.multi_get("non_existent", {"memory", "lru", "timed"}) assert.is_nil(value) end) it("should use default cache types when not specified", function() cache.set("test_key", "memory_value", "memory") local value = cache.multi_get("test_key") assert.equals("memory_value", value) end) end) describe("invalidate", function() it("should invalidate from specific cache type", function() cache.set("test_key", "test_value", "memory") cache.set("test_key", "test_value", "lru") cache.invalidate("test_key", "memory") assert.is_nil(cache.get("test_key", "memory")) assert.equals("test_value", cache.get("test_key", "lru")) end) it("should invalidate from all caches when type not specified", function() cache.set("test_key", "test_value", "memory") cache.set("test_key", "test_value", "lru") cache.set("test_key", "test_value", "timed") cache.invalidate("test_key") assert.is_nil(cache.get("test_key", "memory")) assert.is_nil(cache.get("test_key", "lru")) assert.is_nil(cache.get("test_key", "timed")) end) end) describe("clear_all", function() it("should clear all caches", function() cache.set("key1", "value1", "memory") cache.set("key2", "value2", "lru") cache.set("key3", "value3", "timed") cache.clear_all() assert.is_nil(cache.get("key1", "memory")) assert.is_nil(cache.get("key2", "lru")) assert.is_nil(cache.get("key3", "timed")) end) it("should reset metrics", function() -- Generate some metrics cache.set("test", "value") cache.get("test") cache.get("non_existent") local stats_before = cache.get_stats() assert.is_true(stats_before.metrics.hits > 0) assert.is_true(stats_before.metrics.misses > 0) cache.clear_all() local stats_after = cache.get_stats() assert.equals(0, stats_after.metrics.hits) assert.equals(0, stats_after.metrics.misses) end) end) describe("get_stats", function() it("should return comprehensive statistics", function() -- Generate some activity cache.set("test1", "value1") cache.set("test2", "value2") cache.get("test1") cache.get("non_existent") local stats = cache.get_stats() assert.is_not_nil(stats.metrics) assert.is_not_nil(stats.sizes) assert.is_not_nil(stats.config) assert.is_true(stats.metrics.sets >= 2) assert.is_true(stats.metrics.hits >= 1) assert.is_true(stats.metrics.misses >= 1) assert.is_not_nil(stats.metrics.hit_ratio) end) it("should calculate hit ratio correctly", function() -- Generate known metrics cache.set("test", "value") cache.get("test") -- hit cache.get("test") -- hit cache.get("non_existent") -- miss local stats = cache.get_stats() -- Should be 2 hits out of 3 total requests = ~0.67 assert.is_true(stats.metrics.hit_ratio > 0.6) assert.is_true(stats.metrics.hit_ratio < 0.7) end) end) describe("configuration", function() it("should initialize with custom configuration", function() cache.init({ memory = {enabled = false}, lru = {enabled = true, max_size = 20}, timed = {enabled = true, default_ttl = 10} }) -- Memory cache should be disabled local ok = cache.memory_set("test", "value") assert.is_false(ok) -- LRU cache should work with new size ok = cache.lru_set("test", "value") assert.is_true(ok) -- Timed cache should work with new TTL cache.timed_set("test2", "value") local value = cache.timed_get("test2") assert.equals("value", value) end) end) end)