Files
ThothII/harness/tests/test_sql_preview_json.py
T
marcopanandClaude Opus 4.8 85e2fdc00a fix(harness): correct truncated signalling for sql preview offset>0
When offset>0 the wrapper added an outer LIMIT N, so run_controlled's
_inject_limit bailed (a LIMIT IS present) and truncated was always False —
AGGrid could never detect more rows. Fix: for offset>0 probe with LIMIT (N+1)
OFFSET M, then compute truncated = len(rows) > N in do_run and slice back to N.
offset==0 path unchanged (delegates to extracted _run_transport helper). JSON
still reports the user's requested limit N and correct truncated. Adds 3 tests
exercising the real do_run offset>0 path (N+1 -> truncated True, N -> False,
offset==0 verbatim). 222/222 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 20:18:58 +02:00

135 lines
4.9 KiB
Python

"""Task 6: `tht sql preview --json` + `--offset` paging.
Pure-logic tests (no DB needed):
1. inject_limit_offset — subquery wrapping, non-destructive.
2. preview_cmd JSON-purity — monkeypatched do_run, verifies stdout is valid JSON.
"""
import json
import pytest
from tht.execute.limit import inject_limit_offset
def test_inject_limit_offset_wraps_query():
sql = "SELECT a FROM t ORDER BY a"
out = inject_limit_offset(sql, limit=10, offset=20)
assert "LIMIT 10" in out and "OFFSET 20" in out
# original query stays as a subquery (no clobber of an existing user LIMIT)
assert "SELECT a FROM t ORDER BY a" in out
def test_inject_limit_offset_zero_offset_no_offset_clause():
out = inject_limit_offset("SELECT 1", limit=5, offset=0)
assert "LIMIT 5" in out
assert "OFFSET" not in out
def test_inject_limit_offset_strips_trailing_semicolon():
out = inject_limit_offset("SELECT 1;", limit=3, offset=0)
assert "LIMIT 3" in out
# semicolon should not appear in the inner subquery
assert ";" not in out
def test_inject_limit_offset_default_offset_zero():
out = inject_limit_offset("SELECT x FROM y", limit=7)
assert "LIMIT 7" in out
assert "OFFSET" not in out
def test_do_run_offset_truncated_when_extra_row(monkeypatch):
"""Real do_run offset>0 path: runner returns N+1 rows → truncated True, exactly N returned.
Stubs the transport layer (_run_transport) so do_run's own offset logic
(probe_limit=N+1, compute truncated, slice to N) is exercised for real.
"""
from types import SimpleNamespace
from tht.cli import sql_cmd
from tht.execute import ExecResult
captured = {}
def fake_transport(cfg, sql, *, limit):
captured["sql"] = sql
captured["limit"] = limit
# runner sliced to probe_limit (N+1=3): it found the sentinel extra row.
return ExecResult(columns=["a"], rows=[(1,), (2,), (3,)], execution_ms=5, truncated=False)
monkeypatch.setattr(sql_cmd, "_run_transport", fake_transport)
result = sql_cmd.do_run(SimpleNamespace(), "SELECT a FROM t", limit=2, offset=10)
# do_run must have probed for N+1 rows and wrapped with OFFSET.
assert captured["limit"] == 3
assert "OFFSET 10" in captured["sql"] and "LIMIT 3" in captured["sql"]
# extra row detected → truncated True, sliced back to exactly N=2.
assert result.truncated is True
assert result.rows == [(1,), (2,)]
def test_do_run_offset_not_truncated_when_exactly_n(monkeypatch):
"""Real do_run offset>0 path: runner returns exactly N rows → truncated False."""
from types import SimpleNamespace
from tht.cli import sql_cmd
from tht.execute import ExecResult
def fake_transport(cfg, sql, *, limit):
return ExecResult(columns=["a"], rows=[(1,), (2,)], execution_ms=5, truncated=False)
monkeypatch.setattr(sql_cmd, "_run_transport", fake_transport)
result = sql_cmd.do_run(SimpleNamespace(), "SELECT a FROM t", limit=2, offset=10)
assert result.truncated is False
assert result.rows == [(1,), (2,)]
def test_do_run_offset_zero_path_unchanged(monkeypatch):
"""offset == 0 bypasses the wrapper entirely: SQL and limit reach the runner verbatim."""
from types import SimpleNamespace
from tht.cli import sql_cmd
from tht.execute import ExecResult
captured = {}
def fake_transport(cfg, sql, *, limit):
captured["sql"] = sql
captured["limit"] = limit
return ExecResult(columns=["a"], rows=[(1,)], execution_ms=5, truncated=False)
monkeypatch.setattr(sql_cmd, "_run_transport", fake_transport)
sql_cmd.do_run(SimpleNamespace(), "SELECT a FROM t", limit=2, offset=0)
# no wrapper applied: original SQL and original limit passed straight through.
assert captured["sql"] == "SELECT a FROM t"
assert captured["limit"] == 2
def test_preview_json_pure_stdout(monkeypatch, tmp_path, capsys):
from types import SimpleNamespace
from tht.cli import sql_cmd
fake = SimpleNamespace(columns=["a"], rows=[[1], [2]], execution_ms=3, truncated=False)
monkeypatch.setattr(sql_cmd, "do_run", lambda *a, **k: fake)
monkeypatch.setattr(sql_cmd, "validate_or_exit", lambda *a, **k: SimpleNamespace(ast=None))
monkeypatch.setattr(sql_cmd, "require_action", lambda *a, **k: None)
monkeypatch.setattr(
sql_cmd,
"_load_config_or_exit",
lambda *a, **k: SimpleNamespace(execution=SimpleNamespace(max_preview_rows=100)),
)
f = tmp_path / "q.sql"
f.write_text("SELECT 1")
sql_cmd.preview_cmd(file=f, limit=None, offset=0, session=None, json_out=True, config=None)
out = capsys.readouterr().out.strip()
data = json.loads(out) # must parse: stdout pristine
assert data["columns"] == ["a"]
assert data["rows"] == [[1], [2]]
assert data["execution_ms"] == 3
assert data["truncated"] is False
assert "limit" in data
assert data["offset"] == 0