mirror of
https://github.com/status-im/eth-rpc-proxy.git
synced 2026-08-31 03:31:14 +00:00
336 lines
14 KiB
Lua
336 lines
14 KiB
Lua
-- Example of cache_test.lua migrated to busted syntax for mlcache with L3 cache
|
|
|
|
-- Setup KeyDB mocks before requiring modules that depend on resty.redis
|
|
local keydb_mocks = require("test_utils.keydb_mocks")
|
|
keydb_mocks.setup_require_mocks()
|
|
|
|
local cache_rules = require("cache.cache_rules")
|
|
local cache = require("cache.cache")
|
|
local keydb_l3_cache = require("cache.keydb_l3_cache")
|
|
local keydb_config = require("cache.keydb_config")
|
|
|
|
describe("cache.lua integration with mlcache", function()
|
|
local chain, network, request_body, response_body
|
|
|
|
before_each(function()
|
|
-- Reset test environment
|
|
test_helpers.setup_test_environment()
|
|
|
|
-- Setup test data
|
|
chain = "ethereum"
|
|
network = "mainnet"
|
|
request_body = '{"jsonrpc":"2.0","method":"eth_getBlockByHash","params":["0x123",true],"id":1}'
|
|
response_body = '{"jsonrpc":"2.0","result":{"number":"0x123","hash":"0x456"},"id":1}'
|
|
end)
|
|
|
|
describe("with permanent method", function()
|
|
before_each(function()
|
|
assert.is_true(cache_rules.init("/valid/config.yaml"))
|
|
cache_rules.load_config(false, "/valid/config.yaml")
|
|
end)
|
|
|
|
it("should handle full cache integration flow with mlcache", function()
|
|
-- Step 1: Check cache (should be empty initially)
|
|
local cache_check = cache.check_cache(chain, network, request_body)
|
|
|
|
assert.are.equal("permanent", cache_check.cache_type)
|
|
assert.are.equal(86400, cache_check.ttl)
|
|
assert.is_nil(cache_check.cached_response)
|
|
assert.is_not_nil(cache_check.cache_key)
|
|
assert.is_not_nil(cache_check.decoded_body)
|
|
assert.is_not_nil(cache_check.cache_instance)
|
|
assert.are.equal("eth_getBlockByHash", cache_check.decoded_body.method)
|
|
|
|
-- Step 2: Save to cache using mlcache
|
|
assert.is_true(cache.save_to_cache(cache_check, response_body))
|
|
|
|
-- Step 3: Check cache again (should be hit now)
|
|
local cache_check2 = cache.check_cache(chain, network, request_body)
|
|
|
|
assert.are.equal("permanent", cache_check2.cache_type)
|
|
|
|
-- Compare JSON objects instead of strings to ignore field order
|
|
local json = require("cjson")
|
|
local expected_response = json.decode(response_body)
|
|
local actual_response = json.decode(cache_check2.cached_response)
|
|
assert.are.same(expected_response, actual_response)
|
|
|
|
assert.are.equal(cache_check.cache_key, cache_check2.cache_key)
|
|
assert.is_not_nil(cache_check2.decoded_body)
|
|
assert.is_not_nil(cache_check2.cache_instance)
|
|
assert.are.equal("eth_getBlockByHash", cache_check2.decoded_body.method)
|
|
end)
|
|
|
|
it("should update cache statistics", function()
|
|
-- Initial check and save
|
|
local cache_check = cache.check_cache(chain, network, request_body)
|
|
cache.save_to_cache(cache_check, response_body)
|
|
|
|
-- Second check (cache hit)
|
|
cache.check_cache(chain, network, request_body)
|
|
|
|
-- Verify stats
|
|
local cache_mocks = require("test_utils.cache_mocks")
|
|
local storage = cache_mocks.get_storage()
|
|
local total_requests = storage.cache_stats["total_requests_permanent"] or 0
|
|
local cache_hits = storage.cache_stats["cache_hits_permanent"] or 0
|
|
|
|
assert.is_true(total_requests >= 2)
|
|
assert.is_true(cache_hits >= 1)
|
|
end)
|
|
end)
|
|
|
|
describe("error handling", function()
|
|
it("should handle invalid JSON correctly", function()
|
|
local invalid_request_body = "invalid json here"
|
|
|
|
local cache_check = cache.check_cache(chain, network, invalid_request_body)
|
|
|
|
assert.is_nil(cache_check.cache_type)
|
|
assert.is_nil(cache_check.cached_response)
|
|
assert.is_nil(cache_check.decoded_body)
|
|
end)
|
|
|
|
it("should handle save without cache_instance", function()
|
|
local invalid_cache_info = {
|
|
cache_type = "permanent",
|
|
cache_key = "test:key",
|
|
ttl = 3600
|
|
-- missing cache_instance
|
|
}
|
|
|
|
local result = cache.save_to_cache(invalid_cache_info, response_body)
|
|
assert.is_false(result)
|
|
end)
|
|
end)
|
|
|
|
describe("different cache types", function()
|
|
before_each(function()
|
|
assert.is_true(cache_rules.init("/valid/config.yaml"))
|
|
cache_rules.load_config(false, "/valid/config.yaml")
|
|
end)
|
|
|
|
local cache_scenarios = {
|
|
{
|
|
method = "eth_blockNumber",
|
|
expected_type = "short",
|
|
expected_ttl = 15, -- ethereum chain overrides default 5 to 15
|
|
description = "short cache type"
|
|
},
|
|
{
|
|
method = "eth_gasPrice",
|
|
expected_type = "minimal",
|
|
expected_ttl = 5, -- ethereum chain overrides default 3 to 5
|
|
description = "minimal cache type"
|
|
}
|
|
}
|
|
|
|
for _, scenario in ipairs(cache_scenarios) do
|
|
it("should handle " .. scenario.description .. " for " .. scenario.method .. " with mlcache", function()
|
|
local test_request = string.format(
|
|
'{"jsonrpc":"2.0","method":"%s","params":[],"id":1}',
|
|
scenario.method
|
|
)
|
|
|
|
local cache_check = cache.check_cache(chain, network, test_request)
|
|
|
|
assert.are.equal(scenario.expected_type, cache_check.cache_type)
|
|
assert.are.equal(scenario.expected_ttl, cache_check.ttl)
|
|
assert.is_not_nil(cache_check.cache_instance)
|
|
end)
|
|
end
|
|
end)
|
|
|
|
describe("mlcache specific features", function()
|
|
before_each(function()
|
|
assert.is_true(cache_rules.init("/valid/config.yaml"))
|
|
cache_rules.load_config(false, "/valid/config.yaml")
|
|
end)
|
|
|
|
it("should handle different cache instances for different cache types", function()
|
|
local permanent_request = '{"jsonrpc":"2.0","method":"eth_getBlockByHash","params":["0x123",true],"id":1}'
|
|
local short_request = '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
|
|
|
|
local permanent_check = cache.check_cache(chain, network, permanent_request)
|
|
local short_check = cache.check_cache(chain, network, short_request)
|
|
|
|
assert.are.equal("permanent", permanent_check.cache_type)
|
|
assert.are.equal("short", short_check.cache_type)
|
|
assert.is_not_nil(permanent_check.cache_instance)
|
|
assert.is_not_nil(short_check.cache_instance)
|
|
|
|
-- Different cache types should use different instances
|
|
-- In our mock implementation, they're different objects
|
|
assert.is_not_nil(permanent_check.cache_instance)
|
|
assert.is_not_nil(short_check.cache_instance)
|
|
end)
|
|
end)
|
|
|
|
describe("normalized cache key generation", function()
|
|
before_each(function()
|
|
assert.is_true(cache_rules.init("/valid/config.yaml"))
|
|
cache_rules.load_config(false, "/valid/config.yaml")
|
|
end)
|
|
|
|
it("should generate same cache key for same method with different IDs", function()
|
|
-- Same method, same parameters, but different IDs
|
|
local request1 = '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
|
|
local request2 = '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":123456789}'
|
|
local request3 = '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":"different_id"}'
|
|
|
|
local cache_check1 = cache.check_cache(chain, network, request1)
|
|
local cache_check2 = cache.check_cache(chain, network, request2)
|
|
local cache_check3 = cache.check_cache(chain, network, request3)
|
|
|
|
-- All should have the same cache key despite different IDs
|
|
assert.are.equal(cache_check1.cache_key, cache_check2.cache_key)
|
|
assert.are.equal(cache_check1.cache_key, cache_check3.cache_key)
|
|
assert.are.equal("permanent", cache_check1.cache_type)
|
|
assert.are.equal("permanent", cache_check2.cache_type)
|
|
assert.are.equal("permanent", cache_check3.cache_type)
|
|
end)
|
|
|
|
it("should generate different cache keys for different methods", function()
|
|
-- Different methods should have different cache keys
|
|
local request1 = '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
|
|
local request2 = '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
|
|
|
|
local cache_check1 = cache.check_cache(chain, network, request1)
|
|
local cache_check2 = cache.check_cache(chain, network, request2)
|
|
|
|
-- Should have different cache keys for different methods
|
|
assert.are_not.equal(cache_check1.cache_key, cache_check2.cache_key)
|
|
end)
|
|
|
|
it("should generate different cache keys for different parameters", function()
|
|
-- Same method, different parameters should have different cache keys
|
|
local request1 = '{"jsonrpc":"2.0","method":"eth_getBlockByHash","params":["0x123",true],"id":1}'
|
|
local request2 = '{"jsonrpc":"2.0","method":"eth_getBlockByHash","params":["0x456",true],"id":1}'
|
|
|
|
local cache_check1 = cache.check_cache(chain, network, request1)
|
|
local cache_check2 = cache.check_cache(chain, network, request2)
|
|
|
|
-- Should have different cache keys for different parameters
|
|
assert.are_not.equal(cache_check1.cache_key, cache_check2.cache_key)
|
|
end)
|
|
|
|
it("should cache responses consistently with normalized keys", function()
|
|
-- Test that caching works correctly with normalized keys
|
|
local request1 = '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
|
|
local request2 = '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":999}'
|
|
local response = '{"jsonrpc":"2.0","result":"0x1","id":1}'
|
|
|
|
-- First request should miss cache
|
|
local cache_check1 = cache.check_cache(chain, network, request1)
|
|
assert.is_nil(cache_check1.cached_response)
|
|
|
|
-- Save response to cache
|
|
assert.is_true(cache.save_to_cache(cache_check1, response))
|
|
|
|
-- Second request with different ID should hit cache with corrected id
|
|
local cache_check2 = cache.check_cache(chain, network, request2)
|
|
assert.is_not_nil(cache_check2.cached_response)
|
|
assert.are.equal(cache_check1.cache_key, cache_check2.cache_key)
|
|
|
|
-- Verify that the id in cached response matches request2's id
|
|
local json = require("cjson")
|
|
local response_data = json.decode(cache_check2.cached_response)
|
|
assert.are.equal(999, response_data.id) -- Should match request2's id, not the original
|
|
assert.are.equal("0x1", response_data.result) -- Result should be preserved
|
|
|
|
-- Also test that the id fixing works correctly
|
|
local original_response = json.decode('{"jsonrpc":"2.0","result":"0x1","id":1}')
|
|
local actual_response = json.decode(cache_check2.cached_response)
|
|
assert.are.equal(original_response.result, actual_response.result) -- Same result
|
|
assert.are.equal(original_response.jsonrpc, actual_response.jsonrpc) -- Same jsonrpc
|
|
assert.are_not.equal(original_response.id, actual_response.id) -- Different id
|
|
end)
|
|
end)
|
|
|
|
describe("L3 KeyDB cache integration", function()
|
|
before_each(function()
|
|
assert.is_true(cache_rules.init("/valid/config.yaml"))
|
|
cache_rules.load_config(false, "/valid/config.yaml")
|
|
|
|
-- Setup keydb_config mock to return proper values
|
|
keydb_config.enabled = function() return true end
|
|
keydb_config.get_default_ttl = function() return 3600 end
|
|
keydb_config.get_max_ttl = function() return 86400 end
|
|
keydb_config.get_keydb_url = function() return "redis://test-keydb:6379" end
|
|
keydb_config.get_connect_timeout = function() return 100 end
|
|
keydb_config.get_send_timeout = function() return 1000 end
|
|
keydb_config.get_read_timeout = function() return 1000 end
|
|
keydb_config.get_pool_size = function() return 10 end
|
|
keydb_config.get_max_idle_timeout = function() return 10000 end
|
|
end)
|
|
|
|
it("should check L3 cache enabled status", function()
|
|
-- Test that keydb_l3_cache.enabled() works
|
|
local enabled_status = keydb_l3_cache.enabled()
|
|
assert.is_boolean(enabled_status)
|
|
end)
|
|
|
|
it("should handle L3 cache disabled scenario", function()
|
|
-- Mock keydb_config.enabled to return false
|
|
local original_enabled = keydb_config.enabled
|
|
keydb_config.enabled = function() return false end
|
|
|
|
local cache_check = cache.check_cache(chain, network, request_body)
|
|
cache.save_to_cache(cache_check, response_body)
|
|
|
|
-- L3 disabled should not affect L1/L2 cache functionality
|
|
assert.is_not_nil(cache_check.cache_type)
|
|
|
|
-- Restore original function
|
|
keydb_config.enabled = original_enabled
|
|
end)
|
|
|
|
it("should integrate L3 cache in callback flow", function()
|
|
-- Test that L3 callback is properly integrated
|
|
local cache_check = cache.check_cache(chain, network, request_body)
|
|
|
|
-- Save to cache (should save to L1/L2 and L3 if enabled)
|
|
local save_result = cache.save_to_cache(cache_check, response_body)
|
|
assert.is_true(save_result)
|
|
|
|
-- Second check should hit cache (L1/L2 or L3)
|
|
local cache_check2 = cache.check_cache(chain, network, request_body)
|
|
assert.is_not_nil(cache_check2.cached_response)
|
|
end)
|
|
|
|
it("should handle L3 cache statistics", function()
|
|
-- Initial check and save
|
|
local cache_check = cache.check_cache(chain, network, request_body)
|
|
cache.save_to_cache(cache_check, response_body)
|
|
|
|
-- Second check (should hit cache)
|
|
cache.check_cache(chain, network, request_body)
|
|
|
|
-- Verify L3 cache stats are tracked
|
|
local cache_mocks = require("test_utils.cache_mocks")
|
|
local storage = cache_mocks.get_storage()
|
|
|
|
-- Check that L3 cache statistics exist
|
|
local l3_hits = storage.cache_stats["l3_cache_hits"] or 0
|
|
local l3_misses = storage.cache_stats["l3_cache_misses"] or 0
|
|
|
|
-- Should have L3 activity when L3 callback is invoked
|
|
assert.is_true(l3_misses > 0) -- L3 misses are expected on first cache access
|
|
end)
|
|
|
|
it("should save to both L1/L2 and L3 caches when enabled", function()
|
|
-- Mock keydb_config.enabled to return true
|
|
local original_enabled = keydb_config.enabled
|
|
keydb_config.enabled = function() return true end
|
|
|
|
local cache_check = cache.check_cache(chain, network, request_body)
|
|
local save_result = cache.save_to_cache(cache_check, response_body)
|
|
|
|
-- Should succeed even if L3 is enabled
|
|
assert.is_true(save_result)
|
|
|
|
-- Restore original function
|
|
keydb_config.enabled = original_enabled
|
|
end)
|
|
end)
|
|
end) |