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,167 @@
|
||||
"""Tests para pg_upsert.
|
||||
|
||||
Requieren un PostgreSQL real. Si PG_TEST_DSN no esta definida, los tests que tocan
|
||||
la DB se saltan con skip elegante. Cada test crea y limpia su propia tabla con un
|
||||
nombre aleatorio.
|
||||
|
||||
PG_TEST_DSN="postgresql://user:pass@localhost:5433/trends" \
|
||||
python/.venv/bin/python3 -m pytest python/functions/infra/pg_upsert_test.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
from infra.pg_upsert import pg_upsert # 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 leads(email PRIMARY KEY, name TEXT, score INT) y la elimina al final."""
|
||||
import psycopg2
|
||||
|
||||
name = "pg_upsert_t_" + uuid.uuid4().hex[:12]
|
||||
conn = psycopg2.connect(PG_TEST_DSN)
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
f"CREATE TABLE {name} "
|
||||
f"(email TEXT PRIMARY KEY, name TEXT, score INTEGER)"
|
||||
)
|
||||
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 _read(table, email):
|
||||
import psycopg2
|
||||
|
||||
conn = psycopg2.connect(PG_TEST_DSN)
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
f"SELECT name, score FROM {table} WHERE email = %s", (email,)
|
||||
)
|
||||
return cur.fetchone()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_skip_sin_pg_test_dsn():
|
||||
"""skip sin PG_TEST_DSN."""
|
||||
if not PG_TEST_DSN:
|
||||
pytest.skip("PG_TEST_DSN no definido")
|
||||
assert PG_TEST_DSN
|
||||
|
||||
|
||||
def test_identificador_invalido_devuelve_status_error():
|
||||
"""identificador invalido devuelve status error sin tocar DB."""
|
||||
res = pg_upsert(
|
||||
"postgresql://x/y",
|
||||
"tabla mala; DROP TABLE foo",
|
||||
[{"email": "a@x.com"}],
|
||||
key_cols=["email"],
|
||||
)
|
||||
assert res["status"] == "error"
|
||||
|
||||
|
||||
@requires_pg
|
||||
def test_inserta_filas_nuevas_cuenta_inserted(temp_table):
|
||||
"""inserta filas nuevas cuenta inserted."""
|
||||
res = pg_upsert(
|
||||
PG_TEST_DSN,
|
||||
temp_table,
|
||||
[
|
||||
{"email": "ana@x.com", "name": "Ana", "score": 0},
|
||||
{"email": "bob@x.com", "name": "Bob", "score": 5},
|
||||
],
|
||||
key_cols=["email"],
|
||||
)
|
||||
assert res["status"] == "ok"
|
||||
assert res["inserted"] == 2
|
||||
assert res["updated"] == 0
|
||||
|
||||
|
||||
@requires_pg
|
||||
def test_conflicto_actualiza_y_cuenta_updated(temp_table):
|
||||
"""conflicto actualiza columnas y cuenta updated."""
|
||||
pg_upsert(
|
||||
PG_TEST_DSN, temp_table,
|
||||
[{"email": "ana@x.com", "name": "Ana", "score": 0}], key_cols=["email"],
|
||||
)
|
||||
res = pg_upsert(
|
||||
PG_TEST_DSN, temp_table,
|
||||
[{"email": "ana@x.com", "name": "Ana Lopez", "score": 9}], key_cols=["email"],
|
||||
)
|
||||
assert res["status"] == "ok"
|
||||
assert res["inserted"] == 0
|
||||
assert res["updated"] == 1
|
||||
assert _read(temp_table, "ana@x.com") == ("Ana Lopez", 9)
|
||||
|
||||
|
||||
@requires_pg
|
||||
def test_ownership_selectivo_no_pisa_columna_excluida(temp_table):
|
||||
"""ownership selectivo no pisa columna excluida."""
|
||||
pg_upsert(
|
||||
PG_TEST_DSN, temp_table,
|
||||
[{"email": "ana@x.com", "name": "Ana", "score": 0}], key_cols=["email"],
|
||||
)
|
||||
# La DB es duena de score (otro proceso lo subio a 87).
|
||||
import psycopg2
|
||||
|
||||
conn = psycopg2.connect(PG_TEST_DSN)
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(f"UPDATE {temp_table} SET score = 87 WHERE email = 'ana@x.com'")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
# El feed trae score=0 pero solo autorizamos actualizar name.
|
||||
res = pg_upsert(
|
||||
PG_TEST_DSN, temp_table,
|
||||
[{"email": "ana@x.com", "name": "Ana Lopez", "score": 0}],
|
||||
key_cols=["email"], update_cols=["name"],
|
||||
)
|
||||
assert res["status"] == "ok"
|
||||
assert res["updated"] == 1
|
||||
assert _read(temp_table, "ana@x.com") == ("Ana Lopez", 87)
|
||||
|
||||
|
||||
@requires_pg
|
||||
def test_do_nothing_no_actualiza(temp_table):
|
||||
"""do nothing no actualiza: update_cols=[] inserta solo nuevas."""
|
||||
pg_upsert(
|
||||
PG_TEST_DSN, temp_table,
|
||||
[{"email": "ana@x.com", "name": "Ana", "score": 1}], key_cols=["email"],
|
||||
)
|
||||
res = pg_upsert(
|
||||
PG_TEST_DSN, temp_table,
|
||||
[
|
||||
{"email": "ana@x.com", "name": "PISADO", "score": 99}, # conflicto
|
||||
{"email": "new@x.com", "name": "Nuevo", "score": 2}, # nuevo
|
||||
],
|
||||
key_cols=["email"], update_cols=[],
|
||||
)
|
||||
assert res["status"] == "ok"
|
||||
# La fila en conflicto no se devuelve por RETURNING (DO NOTHING).
|
||||
assert res["inserted"] == 1
|
||||
assert res["updated"] == 0
|
||||
# La existente NO se pisa.
|
||||
assert _read(temp_table, "ana@x.com") == ("Ana", 1)
|
||||
Reference in New Issue
Block a user