{-# LANGUAGE OverloadedStrings, TypeApplications, DataKinds #-}
{-|
Module : Keter.RateLimiter.IPZonesTests
Description : Tests for IP-based zoning and cache management.
Copyright : (c) 2025 Oleksandr Zhabenko
License : MIT
Maintainer : oleksandr.zhabenko@yahoo.com
Stability : experimental
Portability : POSIX
This module contains tests for the IP zone functionality within the rate-limiting system.
IP zones are used to segment the cache, allowing for different rate-limiting rules or counters
to be applied based on the client's IP address.
The tests verify:
* Correct conversion of 'SockAddr' (both IPv4 and IPv6) to a textual IP zone identifier.
* Graceful handling of non-IP 'SockAddr' types (e.g., 'SockAddrUnix').
* The functionality of incrementing and reading counters within a specific IP zone.
* The ability to reset all caches, effectively clearing all rate-limiting state.
* Correct expiration of cache entries based on their TTL (Time To Live).
-}
module Keter.RateLimiter.IPZonesTests (
-- * Test Suite
tests
) where
import Test.Tasty
import Test.Tasty.HUnit
import Network.Socket (SockAddr(..), tupleToHostAddress)
import Keter.RateLimiter.IPZones
import Keter.RateLimiter.Cache
import Keter.RateLimiter.CacheWithZone
import Control.Monad.IO.Class (liftIO)
import Control.Concurrent (threadDelay)
-- | The main test suite for IP zone functionality.
-- It groups together all the individual test cases in this module.
tests :: TestTree
tests = testGroup "Keter.RateLimiter.IPZones Tests"
[ testCase "sockAddrToIPZone produces correct IPv4 address" $ do
let addr = SockAddrInet 0 (tupleToHostAddress (192, 168, 1, 100))
result <- sockAddrToIPZone addr
assertEqual "IPv4 address should be correct" "192.168.1.100" result
, testCase "sockAddrToIPZone produces correct IPv6 address" $ do
let addr = SockAddrInet6 0 0 (0, 0, 0, 1) 0
result <- sockAddrToIPZone addr
assertEqual "IPv6 address should be correct" "0000:0000:0000:0000:0000:0000:0000:0001" result
, testCase "sockAddrToIPZone handles non-IP address" $ do
let addr = SockAddrUnix "/tmp/socket"
result <- sockAddrToIPZone addr
assertEqual "Non-IP address should return default" "default" result
, testCase "incStoreWithZone with IPv4 address" $ do
store <- createInMemoryStore @'FixedWindow
let cache = newCache FixedWindow store
_ <- incStoreWithZone cache "test-throttle" "192.168.1.100" "user123" 60
result <- readCacheWithZone cache "test-throttle" "192.168.1.100" "user123" :: IO (Maybe Int)
assertEqual "readCacheWithZone should return Just 1" (Just 1) result
, testCase "incStoreWithZone with IPv6 address" $ do
store <- createInMemoryStore @'FixedWindow
let cache = newCache FixedWindow store
_ <- incStoreWithZone cache "test-throttle" "2001:0db8:0000:0000:0000:0000:0000:0001" "user123" 60
result <- readCacheWithZone cache "test-throttle" "2001:0db8:0000:0000:0000:0000:0000:0001" "user123" :: IO (Maybe Int)
assertEqual "readCacheWithZone should return Just 1" (Just 1) result
, testCase "incStoreWithZone increments existing counter" $ do
store <- createInMemoryStore @'FixedWindow
let cache = newCache FixedWindow store
_ <- incStoreWithZone cache "test-throttle" "192.168.1.100" "user123" 60
result <- incStoreWithZone cache "test-throttle" "192.168.1.100" "user123" 60
assertEqual "incStoreWithZone should increment to 2" 2 result
, testCase "resetSingleZoneCaches clears all caches" $ do
zoneCaches <- createZoneCaches
_ <- incStoreWithZone (zscCounterCache zoneCaches) "test-throttle" "192.168.1.100" "user123" 60
_ <- resetSingleZoneCaches zoneCaches
result <- readCacheWithZone (zscCounterCache zoneCaches) "test-throttle" "192.168.1.100" "user123" :: IO (Maybe Int)
assertEqual "Should not retrieve counter after reset" Nothing result
, testCase "incStoreWithZone with expired entry" $ do
store <- createInMemoryStore @'FixedWindow
let cache = newCache FixedWindow store
-- Set a short TTL of 1 second.
_ <- incStoreWithZone cache "test-throttle" "192.168.1.100" "user123" 1
-- Wait for the entry to expire.
liftIO $ threadDelay (2 * 1000000) -- Wait for 2 seconds
-- Increment again, which should create a new entry.
_ <- incStoreWithZone cache "test-throttle" "192.168.1.100" "user123" 60
result <- readCacheWithZone cache "test-throttle" "192.168.1.100" "user123" :: IO (Maybe Int)
assertEqual "Should retrieve new counter after expiration" (Just 1) result
]