927437a8d8
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>
93 lines
2.8 KiB
Python
93 lines
2.8 KiB
Python
"""Tests para excel_to_duckdb."""
|
|
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(__file__))
|
|
|
|
import duckdb # noqa: E402
|
|
import openpyxl # noqa: E402
|
|
|
|
from excel_to_duckdb import excel_to_duckdb # noqa: E402
|
|
|
|
|
|
def _make_xlsx(path: str) -> None:
|
|
wb = openpyxl.Workbook()
|
|
ws = wb.active
|
|
ws.title = "Hoja1"
|
|
ws.append(["id", "nombre", "total"])
|
|
ws.append([1, "ana", 10.5])
|
|
ws.append([2, "luis", 20.0])
|
|
ws.append([3, "eva", 5.25])
|
|
ws2 = wb.create_sheet("Segunda")
|
|
ws2.append(["x"])
|
|
ws2.append([99])
|
|
wb.save(path)
|
|
|
|
|
|
def test_replace_ingesta_primera_hoja(tmp_path):
|
|
xlsx = tmp_path / "datos.xlsx"
|
|
_make_xlsx(str(xlsx))
|
|
db = tmp_path / "out.duckdb"
|
|
res = excel_to_duckdb(str(xlsx), str(db), "ventas")
|
|
assert res["status"] == "ok", res
|
|
assert res["table"] == "ventas"
|
|
assert res["row_count"] == 3
|
|
# Verificar que la tabla existe con las filas esperadas.
|
|
con = duckdb.connect(str(db), read_only=True)
|
|
assert con.execute("SELECT COUNT(*) FROM ventas").fetchone()[0] == 3
|
|
con.close()
|
|
|
|
|
|
def test_seleccion_de_hoja_por_nombre(tmp_path):
|
|
xlsx = tmp_path / "datos.xlsx"
|
|
_make_xlsx(str(xlsx))
|
|
db = tmp_path / "out.duckdb"
|
|
res = excel_to_duckdb(str(xlsx), str(db), "otra", sheet="Segunda")
|
|
assert res["status"] == "ok", res
|
|
assert res["row_count"] == 1
|
|
|
|
|
|
def test_append_acumula_filas(tmp_path):
|
|
xlsx = tmp_path / "datos.xlsx"
|
|
_make_xlsx(str(xlsx))
|
|
db = tmp_path / "out.duckdb"
|
|
r1 = excel_to_duckdb(str(xlsx), str(db), "acum", mode="replace")
|
|
assert r1["row_count"] == 3
|
|
r2 = excel_to_duckdb(str(xlsx), str(db), "acum", mode="append")
|
|
assert r2["status"] == "ok", r2
|
|
assert r2["row_count"] == 6
|
|
|
|
|
|
def test_replace_reemplaza_no_acumula(tmp_path):
|
|
xlsx = tmp_path / "datos.xlsx"
|
|
_make_xlsx(str(xlsx))
|
|
db = tmp_path / "out.duckdb"
|
|
excel_to_duckdb(str(xlsx), str(db), "rep", mode="replace")
|
|
res = excel_to_duckdb(str(xlsx), str(db), "rep", mode="replace")
|
|
assert res["row_count"] == 3
|
|
|
|
|
|
def test_identificador_invalido_devuelve_status_error(tmp_path):
|
|
xlsx = tmp_path / "datos.xlsx"
|
|
_make_xlsx(str(xlsx))
|
|
db = tmp_path / "out.duckdb"
|
|
res = excel_to_duckdb(str(xlsx), str(db), "t; DROP TABLE x")
|
|
assert res["status"] == "error"
|
|
assert "invalid table identifier" in res["error"]
|
|
|
|
|
|
def test_mode_invalido_devuelve_status_error(tmp_path):
|
|
xlsx = tmp_path / "datos.xlsx"
|
|
_make_xlsx(str(xlsx))
|
|
db = tmp_path / "out.duckdb"
|
|
res = excel_to_duckdb(str(xlsx), str(db), "t", mode="upsert")
|
|
assert res["status"] == "error"
|
|
assert "invalid mode" in res["error"]
|
|
|
|
|
|
def test_xlsx_inexistente_devuelve_status_error(tmp_path):
|
|
db = tmp_path / "out.duckdb"
|
|
res = excel_to_duckdb(str(tmp_path / "noexiste.xlsx"), str(db), "t")
|
|
assert res["status"] == "error"
|