feat(infra): grupo claude-fleet — FleetView TUI + orquestacion de Claudes
Sistema FleetView para centralizar la flota de procesos Claude Code vivos en una sola ventana kitty + tmux (socket aislado -L fleet) con un panel TUI: - list_claude_fleet (+ tipo claude_fleet): escanea ~/.claude/sessions + goals + runtime, valida procesos vivos (anti-PID-reciclado), join por sessionId. - list_resumable_claudes (+ tipo resumable_claude): sesiones cerradas reanudables. - wrappers tmux: tmux_new_claude_window (con --resume), tmux_swap_window_into_console (preserva ancho del sidebar), tmux_map_claude_panes. - launch_kittyclaude: comando entrypoint; instala atajos alt+flechas/enter/n/0/k/r, mouse on, remain-on-exit off; fija el ancho del sidebar con hooks. - docs/capabilities/claude-fleet.md + entrada en el INDEX. Incluye ademas funciones datascience en progreso (excel/duckdb/postgres) y ajustes varios de docs e infra de otra sesion, agrupados aqui para no perderlos. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
"""Tests para pg_query.
|
||||
|
||||
Requieren un PostgreSQL real. Si la variable de entorno PG_TEST_DSN no esta
|
||||
definida, todos los tests se saltan con skip elegante (no fallan). Cada test crea
|
||||
y limpia su propia tabla temporal con un nombre aleatorio para no depender de un
|
||||
schema concreto ni interferir entre ejecuciones.
|
||||
|
||||
PG_TEST_DSN="postgresql://user:pass@localhost:5433/trends" \
|
||||
python/.venv/bin/python3 -m pytest python/functions/infra/pg_query_test.py
|
||||
"""
|
||||
|
||||
import base64
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.join(os.path.dirname(__file__), "..")
|
||||
) # python/functions -> permite `from infra...`
|
||||
from infra.pg_query import _to_serializable, pg_query # noqa: E402
|
||||
|
||||
PG_TEST_DSN = os.environ.get("PG_TEST_DSN")
|
||||
requires_pg = pytest.mark.skipif(
|
||||
not PG_TEST_DSN, reason="PG_TEST_DSN no definido: se omiten los tests de Postgres"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_table():
|
||||
"""Crea una tabla temporal con datos y la elimina al terminar."""
|
||||
import psycopg2
|
||||
|
||||
name = "pg_query_t_" + uuid.uuid4().hex[:12]
|
||||
conn = psycopg2.connect(PG_TEST_DSN)
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
f"CREATE TABLE {name} (id INTEGER, region TEXT, total NUMERIC(10,2))"
|
||||
)
|
||||
cur.execute(
|
||||
f"INSERT INTO {name} VALUES (1,'norte',120.50),(2,'sur',80.00),"
|
||||
f"(3,'norte',45.25)"
|
||||
)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
yield name
|
||||
|
||||
conn = psycopg2.connect(PG_TEST_DSN)
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(f"DROP TABLE IF EXISTS {name}")
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_skip_sin_pg_test_dsn():
|
||||
"""skip sin PG_TEST_DSN: el resto de tests no corre sin Postgres."""
|
||||
if not PG_TEST_DSN:
|
||||
pytest.skip("PG_TEST_DSN no definido")
|
||||
# Si hay DSN, el placeholder se cumple trivialmente.
|
||||
assert PG_TEST_DSN
|
||||
|
||||
|
||||
def test_normaliza_tipos_no_serializables():
|
||||
"""normaliza tipos no serializables: _to_serializable es pura, sin DB."""
|
||||
assert _to_serializable(date(2026, 6, 16)) == "2026-06-16"
|
||||
assert _to_serializable(uuid.UUID(int=0)) == str(uuid.UUID(int=0))
|
||||
assert _to_serializable(b"\x00\x01") == base64.b64encode(b"\x00\x01").decode("ascii")
|
||||
import decimal
|
||||
|
||||
assert _to_serializable(decimal.Decimal("1.50")) == 1.5
|
||||
assert _to_serializable(None) is None
|
||||
assert _to_serializable("x") == "x"
|
||||
|
||||
|
||||
@requires_pg
|
||||
def test_select_con_parametros_posicionales(temp_table):
|
||||
"""select con parametros posicionales: filtra por %s, agrega y serializa."""
|
||||
res = pg_query(
|
||||
PG_TEST_DSN,
|
||||
f"SELECT region, SUM(total) AS total FROM {temp_table} "
|
||||
f"WHERE region = %s GROUP BY region",
|
||||
params=["norte"],
|
||||
)
|
||||
assert res["status"] == "ok"
|
||||
assert res["columns"] == ["region", "total"]
|
||||
assert res["row_count"] == 1
|
||||
assert res["rows"][0]["region"] == "norte"
|
||||
# NUMERIC se normaliza a float.
|
||||
assert abs(res["rows"][0]["total"] - 165.75) < 1e-9
|
||||
assert res["truncated"] is False
|
||||
|
||||
|
||||
@requires_pg
|
||||
def test_trunca_a_max_rows(temp_table):
|
||||
"""trunca a max_rows: pide menos filas de las que hay y marca truncated."""
|
||||
res = pg_query(PG_TEST_DSN, f"SELECT id FROM {temp_table} ORDER BY id", max_rows=2)
|
||||
assert res["status"] == "ok"
|
||||
assert res["row_count"] == 2
|
||||
assert res["truncated"] is True
|
||||
|
||||
|
||||
@requires_pg
|
||||
def test_dsn_invalido_devuelve_status_error():
|
||||
"""dsn invalido devuelve status error sin lanzar."""
|
||||
res = pg_query(
|
||||
"postgresql://nouser:nopass@127.0.0.1:1/nodb",
|
||||
"SELECT 1",
|
||||
)
|
||||
assert res["status"] == "error"
|
||||
assert "error" in res
|
||||
Reference in New Issue
Block a user