1c4a4b9259
Cinco funciones nuevas para soportar DuckDB como fuente de verdad del project osint:
Grupo duckdb (escritura, complementan a duckdb_query_readonly):
- duckdb_execute_py_infra (impure): ejecuta INSERT/UPDATE/DELETE/DDL en read-write, commit, {status,rowcount}. 6 tests.
- duckdb_upsert_py_infra (impure): UPSERT ON CONFLICT actualizando solo update_cols → ownership selectivo (un re-upsert no pisa columnas excluidas). 7 tests.
Grupo dav (libretas de contactos + vCard multi-valor):
- dav_make_addressbook_py_infra (impure): crea una libreta CardDAV nueva via extended MKCOL (RFC 5689). Idempotente. 12 tests.
- dav_list_addressbooks_py_infra (impure): lista las libretas del contacts-home (PROPFIND Depth:1). 7 tests.
- build_vcard_py_core (pure): serializa un contacto a vCard 3.0 multi-valor (N TEL/EMAIL/ADR + X-OSINT-*). 5 tests.
Paginas de capacidad duckdb.md y dav.md actualizadas.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
86 lines
2.8 KiB
Python
86 lines
2.8 KiB
Python
"""Tests para duckdb_execute."""
|
|
|
|
import duckdb
|
|
import pytest
|
|
|
|
from .duckdb_execute import duckdb_execute
|
|
|
|
|
|
@pytest.fixture
|
|
def db(tmp_path):
|
|
"""Crea una base DuckDB temporal con una tabla vacia y devuelve su path."""
|
|
path = str(tmp_path / "test.duckdb")
|
|
con = duckdb.connect(path)
|
|
con.execute("CREATE TABLE t (id INTEGER, name VARCHAR)")
|
|
con.close()
|
|
return path
|
|
|
|
|
|
def _read_rows(path: str) -> list:
|
|
"""Relee la tabla t en una conexion read_only y devuelve las filas."""
|
|
con = duckdb.connect(path, read_only=True)
|
|
try:
|
|
return con.execute("SELECT id, name FROM t ORDER BY id").fetchall()
|
|
finally:
|
|
con.close()
|
|
|
|
|
|
def test_insert_devuelve_status_ok_y_persiste(db):
|
|
res = duckdb_execute(
|
|
db,
|
|
"INSERT INTO t VALUES (?, ?), (?, ?), (?, ?)",
|
|
params=[1, "a", 2, "b", 3, "c"],
|
|
)
|
|
assert res["status"] == "ok"
|
|
assert res["rowcount"] == 3
|
|
# Releemos para confirmar el efecto en disco.
|
|
assert _read_rows(db) == [(1, "a"), (2, "b"), (3, "c")]
|
|
|
|
|
|
def test_update_afecta_filas_y_persiste(db):
|
|
duckdb_execute(db, "INSERT INTO t VALUES (1,'a'),(2,'b'),(3,'c')")
|
|
res = duckdb_execute(db, "UPDATE t SET name = ? WHERE id <= ?", params=["x", 2])
|
|
assert res["status"] == "ok"
|
|
assert res["rowcount"] == 2
|
|
assert _read_rows(db) == [(1, "x"), (2, "x"), (3, "c")]
|
|
|
|
|
|
def test_delete_afecta_filas_y_persiste(db):
|
|
duckdb_execute(db, "INSERT INTO t VALUES (1,'a'),(2,'b'),(3,'c')")
|
|
res = duckdb_execute(db, "DELETE FROM t WHERE id = ?", params=[3])
|
|
assert res["status"] == "ok"
|
|
assert res["rowcount"] == 1
|
|
assert _read_rows(db) == [(1, "a"), (2, "b")]
|
|
|
|
|
|
def test_ddl_create_table_status_ok(db):
|
|
res = duckdb_execute(db, "CREATE TABLE u (x INTEGER)")
|
|
assert res["status"] == "ok"
|
|
# DDL no reporta filas: rowcount queda en -1, no falla.
|
|
assert res["rowcount"] == -1
|
|
# Confirmamos que la tabla existe insertando en ella.
|
|
res2 = duckdb_execute(db, "INSERT INTO u VALUES (42)")
|
|
assert res2["status"] == "ok"
|
|
con = duckdb.connect(db, read_only=True)
|
|
try:
|
|
assert con.execute("SELECT x FROM u").fetchall() == [(42,)]
|
|
finally:
|
|
con.close()
|
|
|
|
|
|
def test_crea_la_base_si_no_existe(tmp_path):
|
|
path = str(tmp_path / "nueva.duckdb")
|
|
res = duckdb_execute(path, "CREATE TABLE nueva (a INTEGER)")
|
|
assert res["status"] == "ok"
|
|
res2 = duckdb_execute(path, "INSERT INTO nueva VALUES (7)")
|
|
assert res2["status"] == "ok"
|
|
assert res2["rowcount"] == 1
|
|
|
|
|
|
def test_sql_invalido_devuelve_status_error(db):
|
|
res = duckdb_execute(db, "INSERT INTO tabla_que_no_existe VALUES (1)")
|
|
assert res["status"] == "error"
|
|
assert "error" in res
|
|
assert isinstance(res["error"], str) and res["error"]
|
|
# La funcion no lanza: el flujo del test llega hasta aqui sin excepcion.
|