openclaw/ra2/tests/test_token_gate.py
Claude 56d19a0130
feat(ra2): implement Context Sovereignty Layer (Phase 1)
Add deterministic context control layer that intercepts prompt
construction without modifying existing architecture:

- context_engine.py: single choke point (build_context) that assembles
  structured prompts from ledger + sigil + live window, with token
  budget enforcement and automatic window shrinking
- ledger.py: bounded per-stream JSON state (orientation, blockers,
  open questions, delta) with hard field/list limits
- sigil.py: FIFO shorthand memory (max 15 entries) with deterministic
  rule-based generation from message patterns
- token_gate.py: fast token estimation (~4 chars/token) and hard cap
  enforcement with configurable MAX_TOKENS/LIVE_WINDOW
- redact.py: secret pattern detection (Discord, OpenAI, Anthropic,
  AWS, Slack, GitHub, Telegram, Bearer, generic key=value) replaced
  with [REDACTED_SECRET] before any output path

All 64 tests passing. No modifications to existing agent spawning,
model routing, tool system, or Discord relay architecture.

https://claude.ai/code/session_01K7BWJY2gUoJi6dq91Yc7nx
2026-02-19 22:42:22 +00:00

70 lines
1.8 KiB
Python

"""Tests for ra2.token_gate"""
import pytest
from ra2.token_gate import (
estimate_tokens,
check_budget,
shrink_window,
TokenBudgetExceeded,
LIVE_WINDOW_MIN,
)
class TestEstimateTokens:
def test_empty_string(self):
assert estimate_tokens("") == 0
def test_short_string(self):
# "ab" = 2 chars, 2//4 = 0 → clamped to 1
assert estimate_tokens("ab") == 1
def test_known_length(self):
text = "a" * 400
# 400 / 4 = 100
assert estimate_tokens(text) == 100
def test_proportional(self):
short = estimate_tokens("hello world")
long = estimate_tokens("hello world " * 100)
assert long > short
class TestCheckBudget:
def test_within_budget(self):
assert check_budget(100, limit=200) is True
def test_at_budget(self):
assert check_budget(200, limit=200) is True
def test_over_budget(self):
assert check_budget(201, limit=200) is False
class TestShrinkWindow:
def test_halves(self):
assert shrink_window(16) == 8
def test_halves_again(self):
assert shrink_window(8) == 4
def test_at_minimum_raises(self):
with pytest.raises(TokenBudgetExceeded):
shrink_window(LIVE_WINDOW_MIN)
def test_below_minimum_raises(self):
with pytest.raises(TokenBudgetExceeded):
shrink_window(2)
def test_odd_number(self):
# 5 // 2 = 2, but clamped to LIVE_WINDOW_MIN (4)
assert shrink_window(5) == LIVE_WINDOW_MIN
class TestTokenBudgetExceeded:
def test_attributes(self):
exc = TokenBudgetExceeded(estimated=7000, limit=6000)
assert exc.estimated == 7000
assert exc.limit == 6000
assert "7000" in str(exc)
assert "6000" in str(exc)