"""Tests para extract_timeseries_raw. No usa DuckDB real: inyecta un query_fn FAKE (closure) que devuelve filas predefinidas y, opcionalmente, captura el SQL recibido para verificar la query generada (ORDER BY por la columna temporal + LIMIT). Asi el test es autocontenido y no depende de ningun backend. """ import os import sys sys.path.insert(0, os.path.dirname(__file__)) from extract_timeseries_raw import extract_timeseries_raw def _fake_query(rows, captured=None, status="ok", error=None): """Crea un query_fn FAKE. `captured` (lista opcional) recibe el SQL ejecutado para poder inspeccionarlo. `status`/`error` permiten simular un fallo del backend. """ def _q(sql): if captured is not None: captured.append(sql) if status != "ok": return {"status": "error", "error": error or "boom"} return {"status": "ok", "rows": rows} return _q def test_golden_t_y_series_alineadas(): """Golden: t y series alineadas, floats convertidos, n correcto.""" rows = [ {"fecha": "2024-01-01", "ventas": "10", "stock": 5}, {"fecha": "2024-01-02", "ventas": "20.5", "stock": 7}, {"fecha": "2024-01-03", "ventas": 30, "stock": 9}, ] res = extract_timeseries_raw(_fake_query(rows), "t", "fecha", ["ventas", "stock"]) assert res["status"] == "ok" assert res["n"] == 3 assert res["time_col"] == "fecha" assert res["t"] == ["2024-01-01", "2024-01-02", "2024-01-03"] assert res["series"]["ventas"] == [10.0, 20.5, 30.0] assert res["series"]["stock"] == [5.0, 7.0, 9.0] def test_valor_no_convertible_da_none(): """Valor no convertible a float -> None en la serie (alineacion preservada).""" rows = [ {"fecha": "2024-01-01", "ventas": "abc"}, {"fecha": "2024-01-02", "ventas": None}, {"fecha": "2024-01-03", "ventas": "12.5"}, ] res = extract_timeseries_raw(_fake_query(rows), "t", "fecha", ["ventas"]) assert res["status"] == "ok" assert res["series"]["ventas"] == [None, None, 12.5] assert res["n"] == 3 def test_value_cols_vacia_status_error(): """value_cols vacia -> status error con t/series/n vacios.""" res = extract_timeseries_raw(_fake_query([]), "t", "fecha", []) assert res["status"] == "error" assert "value_cols" in res["error"] assert res["t"] == [] assert res["series"] == {} assert res["n"] == 0 def test_query_fn_status_error_propaga(): """query_fn que devuelve status != ok -> se propaga como error.""" res = extract_timeseries_raw( _fake_query([], status="error", error="db locked"), "t", "fecha", ["ventas"], ) assert res["status"] == "error" assert "db locked" in res["error"] assert res["n"] == 0 def test_query_fn_none_da_error_sin_reventar(): """query_fn None -> error degradado, sin excepcion.""" res = extract_timeseries_raw(None, "t", "fecha", ["ventas"]) assert res["status"] == "error" assert res["t"] == [] assert res["n"] == 0 def test_sql_contiene_order_by_y_limit(): """La query generada ordena por time_col y aplica el LIMIT sobre la tabla.""" captured = [] rows = [{"fecha": "2024-01-01", "ventas": 1}] extract_timeseries_raw( _fake_query(rows, captured), "ventas_tbl", "fecha", ["ventas"], max_rows=123, ) assert len(captured) == 1 sql = captured[0] assert 'ORDER BY "fecha"' in sql assert "LIMIT 123" in sql assert 'FROM "ventas_tbl"' in sql