feat: funciones Python infra y tipos Python (core, datascience, infra)
Infra: cache_to_file, cache_to_sqlite, http_download_file, http_get_json, http_post_json, read_file_with_encoding, safe_extract_zip, scan_directory, setup_logger, normalize_zip_filenames. Tipos: 30+ tipos core (agent_action, context, task, message, parse_result...), 6 tipos datascience (entity_candidate, extraction_result...), 2 tipos infra. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
"""Tests para cache_to_sqlite."""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
from .cache_to_sqlite import cache_to_sqlite
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def store(tmp_path):
|
||||
db = str(tmp_path / "test.db")
|
||||
return cache_to_sqlite(db)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def store2(tmp_path):
|
||||
"""Segundo namespace en la misma BD."""
|
||||
db = str(tmp_path / "test.db")
|
||||
return cache_to_sqlite(db, namespace="other")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def store_and_other(tmp_path):
|
||||
db = str(tmp_path / "test.db")
|
||||
s1 = cache_to_sqlite(db, namespace="ns1")
|
||||
s2 = cache_to_sqlite(db, namespace="ns2")
|
||||
return s1, s2
|
||||
|
||||
|
||||
def test_set_y_get_basico(store):
|
||||
store.set("foo", {"x": 1})
|
||||
assert store.get("foo") == {"x": 1}
|
||||
|
||||
|
||||
def test_ttl_expirado_retorna_none(store):
|
||||
store.set("expiring", "hello", ttl=0.05)
|
||||
time.sleep(0.1)
|
||||
assert store.get("expiring") is None
|
||||
|
||||
|
||||
def test_ttl_cero_nunca_expira(store):
|
||||
store.set("forever", 42, ttl=0)
|
||||
time.sleep(0.05)
|
||||
assert store.get("forever") == 42
|
||||
|
||||
|
||||
def test_get_or_set_factory_solo_se_llama_en_miss(store):
|
||||
calls = []
|
||||
|
||||
def factory():
|
||||
calls.append(1)
|
||||
return "computed"
|
||||
|
||||
result1 = store.get_or_set("key", factory, ttl=10)
|
||||
result2 = store.get_or_set("key", factory, ttl=10)
|
||||
assert result1 == "computed"
|
||||
assert result2 == "computed"
|
||||
assert len(calls) == 1
|
||||
|
||||
|
||||
def test_namespaces_independientes(store_and_other):
|
||||
s1, s2 = store_and_other
|
||||
s1.set("k", "from_ns1")
|
||||
assert s2.get("k") is None
|
||||
s2.set("k", "from_ns2")
|
||||
assert s1.get("k") == "from_ns1"
|
||||
assert s2.get("k") == "from_ns2"
|
||||
|
||||
|
||||
def test_clear_elimina_solo_el_namespace(store_and_other):
|
||||
s1, s2 = store_and_other
|
||||
s1.set("a", 1)
|
||||
s2.set("b", 2)
|
||||
removed = s1.clear()
|
||||
assert removed == 1
|
||||
assert s1.get("a") is None
|
||||
assert s2.get("b") == 2
|
||||
|
||||
|
||||
def test_stats_contadores_correctos(store):
|
||||
store.set("x", 10)
|
||||
store.get("x") # hit
|
||||
store.get("x") # hit
|
||||
store.get("z") # miss
|
||||
s = store.stats()
|
||||
assert s["hits"] == 2
|
||||
assert s["misses"] == 1
|
||||
assert s["size"] == 1
|
||||
|
||||
|
||||
def test_concurrencia(tmp_path):
|
||||
db = str(tmp_path / "concurrent.db")
|
||||
s = cache_to_sqlite(db, "parallel")
|
||||
errors = []
|
||||
|
||||
def worker(i):
|
||||
try:
|
||||
s.set(f"key_{i}", i)
|
||||
val = s.get(f"key_{i}")
|
||||
assert val == i
|
||||
except Exception as e:
|
||||
errors.append(e)
|
||||
|
||||
threads = [threading.Thread(target=worker, args=(i,)) for i in range(20)]
|
||||
for t in threads:
|
||||
t.start()
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
assert errors == [], f"Errors in threads: {errors}"
|
||||
Reference in New Issue
Block a user