Add cache unit tests (10) and update data-test demo for TTL
- 10 new tests: cache key generation, set/get, TTL expiry, overwrite, key independence, complex nested data - Update data-test.sx with cache verification instructions: navigate away+back within 30s → client+cache, after 30s → new fetch Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
"""Tests for Phase 4 page data pipeline.
|
||||
|
||||
Tests the serialize→parse roundtrip for data dicts (SX wire format),
|
||||
the kebab-case key conversion, and component dep computation for
|
||||
:data pages.
|
||||
the kebab-case key conversion, component dep computation for
|
||||
:data pages, and the client data cache logic.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
@@ -283,3 +283,151 @@ class TestDataPipelineSimulation:
|
||||
for expr in pa('(~page :title title :count count)'):
|
||||
result = _trampoline(_eval(expr, page_env))
|
||||
assert result == expected
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Client data cache
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestDataCache:
|
||||
"""Test the page data cache logic from orchestration.sx.
|
||||
|
||||
The cache functions are pure SX evaluated with a mock now-ms primitive.
|
||||
"""
|
||||
|
||||
def _make_env(self, current_time_ms=1000):
|
||||
"""Create an env with cache functions and a controllable now-ms."""
|
||||
from shared.sx.parser import parse_all as pa
|
||||
from shared.sx.evaluator import _eval, _trampoline
|
||||
|
||||
env = {}
|
||||
# Mock now-ms as a callable that returns current_time_ms
|
||||
self._time = current_time_ms
|
||||
env["now-ms"] = lambda: self._time
|
||||
|
||||
# Mutating primitives needed by cache (available in JS, not bare Python)
|
||||
def _dict_set(d, k, v):
|
||||
d[k] = v
|
||||
return v
|
||||
def _append_b(lst, item):
|
||||
lst.append(item)
|
||||
return lst
|
||||
env["dict-set!"] = _dict_set
|
||||
env["append!"] = _append_b
|
||||
|
||||
# Define the cache functions from orchestration.sx
|
||||
cache_src = """
|
||||
(define _page-data-cache (dict))
|
||||
(define _page-data-cache-ttl 30000)
|
||||
|
||||
(define page-data-cache-key
|
||||
(fn (page-name params)
|
||||
(let ((base page-name))
|
||||
(if (or (nil? params) (empty? (keys params)))
|
||||
base
|
||||
(let ((parts (list)))
|
||||
(for-each
|
||||
(fn (k)
|
||||
(append! parts (str k "=" (get params k))))
|
||||
(keys params))
|
||||
(str base ":" (join "&" parts)))))))
|
||||
|
||||
(define page-data-cache-get
|
||||
(fn (cache-key)
|
||||
(let ((entry (get _page-data-cache cache-key)))
|
||||
(if (nil? entry)
|
||||
nil
|
||||
(if (> (- (now-ms) (get entry "ts")) _page-data-cache-ttl)
|
||||
(do
|
||||
(dict-set! _page-data-cache cache-key nil)
|
||||
nil)
|
||||
(get entry "data"))))))
|
||||
|
||||
(define page-data-cache-set
|
||||
(fn (cache-key data)
|
||||
(dict-set! _page-data-cache cache-key
|
||||
{"data" data "ts" (now-ms)})))
|
||||
"""
|
||||
for expr in pa(cache_src):
|
||||
_trampoline(_eval(expr, env))
|
||||
return env
|
||||
|
||||
def _eval(self, src, env):
|
||||
from shared.sx.parser import parse_all as pa
|
||||
from shared.sx.evaluator import _eval, _trampoline
|
||||
result = None
|
||||
for expr in pa(src):
|
||||
result = _trampoline(_eval(expr, env))
|
||||
return result
|
||||
|
||||
def test_cache_key_no_params(self):
|
||||
env = self._make_env()
|
||||
result = self._eval('(page-data-cache-key "data-test" {})', env)
|
||||
assert result == "data-test"
|
||||
|
||||
def test_cache_key_with_params(self):
|
||||
env = self._make_env()
|
||||
result = self._eval('(page-data-cache-key "reference" {"slug" "div"})', env)
|
||||
assert result == "reference:slug=div"
|
||||
|
||||
def test_cache_key_nil_params(self):
|
||||
env = self._make_env()
|
||||
result = self._eval('(page-data-cache-key "data-test" nil)', env)
|
||||
assert result == "data-test"
|
||||
|
||||
def test_cache_miss_returns_nil(self):
|
||||
env = self._make_env()
|
||||
result = self._eval('(page-data-cache-get "nonexistent")', env)
|
||||
assert result is NIL or result is None
|
||||
|
||||
def test_cache_set_then_get(self):
|
||||
env = self._make_env(current_time_ms=1000)
|
||||
self._eval('(page-data-cache-set "test-page" {"title" "Hello"})', env)
|
||||
result = self._eval('(page-data-cache-get "test-page")', env)
|
||||
assert result["title"] == "Hello"
|
||||
|
||||
def test_cache_hit_within_ttl(self):
|
||||
env = self._make_env(current_time_ms=1000)
|
||||
self._eval('(page-data-cache-set "test-page" {"val" 42})', env)
|
||||
# Advance time by 10 seconds (within 30s TTL)
|
||||
self._time = 11000
|
||||
result = self._eval('(page-data-cache-get "test-page")', env)
|
||||
assert result["val"] == 42
|
||||
|
||||
def test_cache_expired_returns_nil(self):
|
||||
env = self._make_env(current_time_ms=1000)
|
||||
self._eval('(page-data-cache-set "test-page" {"val" 42})', env)
|
||||
# Advance time by 31 seconds (past 30s TTL)
|
||||
self._time = 32000
|
||||
result = self._eval('(page-data-cache-get "test-page")', env)
|
||||
assert result is NIL or result is None
|
||||
|
||||
def test_cache_overwrite(self):
|
||||
env = self._make_env(current_time_ms=1000)
|
||||
self._eval('(page-data-cache-set "p" {"v" 1})', env)
|
||||
self._time = 2000
|
||||
self._eval('(page-data-cache-set "p" {"v" 2})', env)
|
||||
result = self._eval('(page-data-cache-get "p")', env)
|
||||
assert result["v"] == 2
|
||||
|
||||
def test_cache_different_keys_independent(self):
|
||||
env = self._make_env(current_time_ms=1000)
|
||||
self._eval('(page-data-cache-set "a" {"x" 1})', env)
|
||||
self._eval('(page-data-cache-set "b" {"x" 2})', env)
|
||||
a = self._eval('(page-data-cache-get "a")', env)
|
||||
b = self._eval('(page-data-cache-get "b")', env)
|
||||
assert a["x"] == 1
|
||||
assert b["x"] == 2
|
||||
|
||||
def test_cache_complex_data(self):
|
||||
"""Cache preserves nested dicts and lists."""
|
||||
env = self._make_env(current_time_ms=1000)
|
||||
self._eval("""
|
||||
(page-data-cache-set "complex"
|
||||
{"items" (list {"label" "A"} {"label" "B"})
|
||||
"count" 2})
|
||||
""", env)
|
||||
result = self._eval('(page-data-cache-get "complex")', env)
|
||||
assert result["count"] == 2
|
||||
assert len(result["items"]) == 2
|
||||
assert result["items"][0]["label"] == "A"
|
||||
|
||||
Reference in New Issue
Block a user