Tests unitarios: 51 tests para compactor, key_data, fingerprint y costes

- tests/test_compactor.py: 24 tests (estimate_tokens, extract_facts,
  build_summary, summarize_tool_output, compact_sections)
- tests/test_key_data_extraction.py: 11 tests (extracción de tables,
  records, sections, modules, pages desde tool executions)
- tests/test_fingerprint.py: 8 tests (deduplicación MD5, sort_keys,
  nested args)
- tests/test_cost_calculation.py: 8 tests (pricing formula, custom
  pricing, rounding)
- README.md: sección Tests con instrucciones de ejecución

Todos offline, sin Docker/Redis/LLM. Ejecutar: python3 -m pytest tests/ -v

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jordan Diaz
2026-04-03 14:28:32 +00:00
parent 7c891cf023
commit 6978764540
7 changed files with 670 additions and 0 deletions

61
tests/test_fingerprint.py Normal file
View File

@@ -0,0 +1,61 @@
"""Tests para la logica de fingerprinting/deduplicacion de tool calls.
Replica la logica de BaseAgent.execute() (lineas con hashlib.md5) sin
necesidad de instanciar BaseAgent ni sus dependencias.
"""
import hashlib
import json
import pytest
def compute_fingerprint(tool_name: str, args: dict) -> str:
"""Replica exacta de la logica de fingerprint en BaseAgent.execute()."""
fp_raw = f"{tool_name}:{json.dumps(args, sort_keys=True)}"
return hashlib.md5(fp_raw.encode()).hexdigest()
class TestFingerprint:
def test_same_tool_same_args_same_fingerprint(self):
fp1 = compute_fingerprint("read_file", {"path": "/index.html"})
fp2 = compute_fingerprint("read_file", {"path": "/index.html"})
assert fp1 == fp2
def test_same_tool_different_args_different_fingerprint(self):
fp1 = compute_fingerprint("read_file", {"path": "/index.html"})
fp2 = compute_fingerprint("read_file", {"path": "/style.css"})
assert fp1 != fp2
def test_different_tool_same_args_different_fingerprint(self):
fp1 = compute_fingerprint("read_file", {"path": "/index.html"})
fp2 = compute_fingerprint("write_file", {"path": "/index.html"})
assert fp1 != fp2
def test_fingerprint_is_md5_hex_32_chars(self):
fp = compute_fingerprint("any_tool", {"key": "value"})
assert len(fp) == 32
assert all(c in "0123456789abcdef" for c in fp)
def test_arg_order_does_not_matter(self):
"""json.dumps con sort_keys=True normaliza el orden."""
fp1 = compute_fingerprint("tool", {"b": 2, "a": 1})
fp2 = compute_fingerprint("tool", {"a": 1, "b": 2})
assert fp1 == fp2
def test_empty_args(self):
fp = compute_fingerprint("tool", {})
assert len(fp) == 32
# Debe ser determinista
assert fp == compute_fingerprint("tool", {})
def test_nested_args(self):
args = {"filter": {"table": "pages", "status": "active"}, "limit": 10}
fp1 = compute_fingerprint("search", args)
fp2 = compute_fingerprint("search", args)
assert fp1 == fp2
def test_different_nested_values(self):
fp1 = compute_fingerprint("search", {"filter": {"status": "active"}})
fp2 = compute_fingerprint("search", {"filter": {"status": "draft"}})
assert fp1 != fp2