towerops/test/snmpkit/snmp_lib/cache_test.exs
2026-06-15 11:15:46 -05:00

464 lines
11 KiB
Elixir

defmodule SnmpKit.SnmpLib.CacheTest do
use ExUnit.Case, async: false
alias SnmpKit.SnmpLib.Cache
doctest Cache
@moduletag :cache_test
setup do
# Ensure cache process is stopped before each test
if Process.whereis(Cache) do
try do
GenServer.stop(Cache, :normal, 1000)
catch
:exit, _ -> :ok
end
end
:timer.sleep(10)
:ok
end
describe "Cache.start_link/1" do
test "starts with default configuration" do
assert {:ok, pid} = Cache.start_link()
assert Process.alive?(pid)
try do
GenServer.stop(Cache)
catch
:exit, _ -> :ok
end
end
test "starts with custom configuration" do
opts = [
max_size: 50_000,
compression_enabled: true,
adaptive_ttl_enabled: true
]
assert {:ok, pid} = Cache.start_link(opts)
assert Process.alive?(pid)
try do
GenServer.stop(Cache)
catch
:exit, _ -> :ok
end
end
end
describe "Cache.put/3 and Cache.get/1" do
setup do
{:ok, _pid} = Cache.start_link()
on_exit(fn ->
if Process.whereis(Cache) do
try do
GenServer.stop(Cache, :normal, 1000)
catch
:exit, _ -> :ok
end
end
end)
:ok
end
test "stores and retrieves simple values" do
key = "test_key"
value = "test_value"
:ok = Cache.put(key, value)
assert {:ok, ^value} = Cache.get(key)
end
test "returns miss for non-existent keys" do
assert :miss = Cache.get("non_existent_key")
end
test "handles complex data structures" do
key = "complex_data"
value = %{
device: "192.168.1.1",
oids: [1, 3, 6, 1, 2, 1, 1, 1, 0],
response: {:string, "Cisco Router"},
timestamp: System.system_time(:millisecond)
}
:ok = Cache.put(key, value)
assert {:ok, ^value} = Cache.get(key)
end
test "respects TTL expiration" do
key = "ttl_test"
value = "expires_soon"
# Set very short TTL
:ok = Cache.put(key, value, ttl: 50)
assert {:ok, ^value} = Cache.get(key)
# Wait for expiration
:timer.sleep(100)
assert :miss = Cache.get(key)
end
test "handles compression for large values" do
key = "large_data"
# Create a large value that should trigger compression
large_value = String.duplicate("A", 2000)
:ok = Cache.put(key, large_value, compress: true)
assert {:ok, ^large_value} = Cache.get(key)
end
end
describe "Cache.put_adaptive/4" do
setup do
{:ok, _pid} = Cache.start_link()
on_exit(fn ->
if Process.whereis(Cache) do
try do
GenServer.stop(Cache, :normal, 1000)
catch
:exit, _ -> :ok
end
end
end)
:ok
end
test "stores values with adaptive TTL" do
key = "adaptive_test"
value = "adaptive_value"
:ok = Cache.put_adaptive(key, value, 1000, :medium)
assert {:ok, ^value} = Cache.get(key)
end
test "adjusts TTL based on volatility" do
# Low volatility should have longer TTL
:ok = Cache.put_adaptive("low_volatility", "stable_data", 1000, :low)
# High volatility should have shorter TTL
:ok = Cache.put_adaptive("high_volatility", "changing_data", 1000, :high)
# Both should be retrievable immediately
assert {:ok, "stable_data"} = Cache.get("low_volatility")
assert {:ok, "changing_data"} = Cache.get("high_volatility")
end
end
describe "Cache.delete/1" do
setup do
{:ok, _pid} = Cache.start_link()
on_exit(fn ->
if Process.whereis(Cache) do
try do
GenServer.stop(Cache, :normal, 1000)
catch
:exit, _ -> :ok
end
end
end)
:ok
end
test "deletes specific cache entries" do
key = "delete_test"
value = "to_be_deleted"
:ok = Cache.put(key, value)
assert {:ok, ^value} = Cache.get(key)
:ok = Cache.delete(key)
assert :miss = Cache.get(key)
end
end
describe "Cache.invalidate_pattern/1" do
setup do
{:ok, _pid} = Cache.start_link()
on_exit(fn ->
if Process.whereis(Cache) do
try do
GenServer.stop(Cache, :normal, 1000)
catch
:exit, _ -> :ok
end
end
end)
:ok
end
test "invalidates entries matching pattern" do
# Store entries with common prefix
Cache.put("device_1:sysDescr", "Router 1")
Cache.put("device_1:sysName", "R1")
Cache.put("device_2:sysDescr", "Router 2")
# Verify they exist
assert {:ok, "Router 1"} = Cache.get("device_1:sysDescr")
assert {:ok, "R1"} = Cache.get("device_1:sysName")
assert {:ok, "Router 2"} = Cache.get("device_2:sysDescr")
# Invalidate device_1 entries
:ok = Cache.invalidate_pattern("device_1:*")
# device_1 entries should be gone
assert :miss = Cache.get("device_1:sysDescr")
assert :miss = Cache.get("device_1:sysName")
# device_2 entries should remain
assert {:ok, "Router 2"} = Cache.get("device_2:sysDescr")
end
end
describe "Cache.invalidate_by_tag/1" do
setup do
{:ok, _pid} = Cache.start_link()
on_exit(fn ->
if Process.whereis(Cache) do
try do
GenServer.stop(Cache, :normal, 1000)
catch
:exit, _ -> :ok
end
end
end)
:ok
end
test "invalidates entries by tag" do
# Store entries with tags
Cache.put("entry1", "data1", tags: [:interface_data])
Cache.put("entry2", "data2", tags: [:interface_data, :statistics])
Cache.put("entry3", "data3", tags: [:system_data])
# Verify they exist
assert {:ok, "data1"} = Cache.get("entry1")
assert {:ok, "data2"} = Cache.get("entry2")
assert {:ok, "data3"} = Cache.get("entry3")
# Invalidate by tag
:ok = Cache.invalidate_by_tag(:interface_data)
# Interface data entries should be gone
assert :miss = Cache.get("entry1")
assert :miss = Cache.get("entry2")
# System data should remain
assert {:ok, "data3"} = Cache.get("entry3")
end
end
describe "Cache.warm_cache/3" do
setup do
{:ok, _pid} = Cache.start_link()
on_exit(fn ->
if Process.whereis(Cache) do
try do
GenServer.stop(Cache, :normal, 1000)
catch
:exit, _ -> :ok
end
end
end)
:ok
end
test "performs immediate cache warming" do
device_id = "192.168.1.1"
oids = ["1.3.6.1.2.1.1.1.0", "1.3.6.1.2.1.1.3.0"]
assert :ok = Cache.warm_cache(device_id, oids, strategy: :immediate)
# Allow time for async processing
:timer.sleep(10)
end
test "performs predictive cache warming" do
device_id = "192.168.1.1"
assert :ok = Cache.warm_cache(device_id, :auto, strategy: :predictive)
# Allow time for async processing
:timer.sleep(10)
end
test "schedules cache warming" do
device_id = "192.168.1.1"
oids = ["1.3.6.1.2.1.1.1.0"]
assert :ok = Cache.warm_cache(device_id, oids, strategy: :scheduled)
# Allow time for async processing
:timer.sleep(10)
end
end
describe "Cache.get_stats/0" do
setup do
{:ok, _pid} = Cache.start_link()
on_exit(fn ->
if Process.whereis(Cache) do
try do
GenServer.stop(Cache, :normal, 1000)
catch
:exit, _ -> :ok
end
end
end)
:ok
end
test "returns cache statistics" do
# Perform some cache operations to generate stats
Cache.put("test1", "value1")
Cache.put("test2", "value2")
# Hit
Cache.get("test1")
# Miss
Cache.get("test3")
stats = Cache.get_stats()
assert Map.has_key?(stats, :total_entries)
assert Map.has_key?(stats, :hit_rate)
assert Map.has_key?(stats, :miss_rate)
assert Map.has_key?(stats, :eviction_count)
assert Map.has_key?(stats, :memory_usage_mb)
assert Map.has_key?(stats, :compression_ratio)
assert is_integer(stats.total_entries)
assert is_float(stats.hit_rate)
assert is_float(stats.miss_rate)
assert is_integer(stats.eviction_count)
assert is_float(stats.memory_usage_mb)
assert is_float(stats.compression_ratio)
# Should have some entries
assert stats.total_entries > 0
end
end
describe "Cache.clear/0" do
setup do
{:ok, _pid} = Cache.start_link()
on_exit(fn ->
if Process.whereis(Cache) do
try do
GenServer.stop(Cache, :normal, 1000)
catch
:exit, _ -> :ok
end
end
end)
:ok
end
test "clears all cached data" do
# Add some entries
Cache.put("test1", "value1")
Cache.put("test2", "value2")
# Verify they exist
assert {:ok, "value1"} = Cache.get("test1")
assert {:ok, "value2"} = Cache.get("test2")
# Clear cache
:ok = Cache.clear()
# All entries should be gone
assert :miss = Cache.get("test1")
assert :miss = Cache.get("test2")
# Stats should be reset
stats = Cache.get_stats()
assert stats.total_entries == 0
end
end
describe "SNMP-specific caching patterns" do
setup do
{:ok, _pid} = Cache.start_link()
on_exit(fn ->
if Process.whereis(Cache) do
try do
GenServer.stop(Cache, :normal, 1000)
catch
:exit, _ -> :ok
end
end
end)
:ok
end
test "caches SNMP responses by device and OID" do
device = "192.168.1.1"
oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]
response = {:string, "Cisco 2960 Switch"}
key = "#{device}:#{Enum.join(oid, ".")}"
:ok = Cache.put(key, response, ttl: 300_000)
assert {:ok, ^response} = Cache.get(key)
end
test "caches table walk results" do
device = "192.168.1.1"
table_oid = [1, 3, 6, 1, 2, 1, 2, 2]
table_data = [
{[1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1], {:integer, 1}},
{[1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 2], {:integer, 2}},
{[1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 3], {:integer, 3}}
]
key = "#{device}:walk:#{Enum.join(table_oid, ".")}"
:ok = Cache.put_adaptive(key, table_data, 60_000, :medium)
assert {:ok, ^table_data} = Cache.get(key)
end
test "uses device-specific cache invalidation" do
device1 = "192.168.1.1"
device2 = "192.168.1.2"
# Cache data for both devices
Cache.put("#{device1}:sysDescr", "Router 1")
Cache.put("#{device1}:sysName", "R1")
Cache.put("#{device2}:sysDescr", "Router 2")
# Invalidate all data for device1
:ok = Cache.invalidate_pattern("#{device1}:*")
# Device1 data should be gone
assert :miss = Cache.get("#{device1}:sysDescr")
assert :miss = Cache.get("#{device1}:sysName")
# Device2 data should remain
assert {:ok, "Router 2"} = Cache.get("#{device2}:sysDescr")
end
end
end