Skip to content

Commit 043dfe2

Browse files
Pin DqliteConnection.query_raw / query_raw_typed return shapes and validate-before-protocol ordering
Both methods are public API through which every dbapi cursor SELECT routes (via ``cursor.py:_call_client``), but their 2-line bodies (validate + ``_run_protocol`` await) had no direct unit tests — only integration coverage. Pin: * ``query_raw`` returns a 2-tuple ``(column_names, rows)``. * ``query_raw_typed`` returns a 4-tuple ``(column_names, column_types, row_types, rows)``. * Both validate ``params`` BEFORE entering ``_run_protocol``, so a malformed ``params`` shape (Mapping / set) raises ``DataError`` without touching the wire. Mock-driven; no live cluster. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f06c0aa commit 043dfe2

1 file changed

Lines changed: 94 additions & 0 deletions

File tree

tests/test_query_raw_unit.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"""Pin: ``DqliteConnection.query_raw`` and ``query_raw_typed``
2+
public API contracts — return shapes (2-tuple vs 4-tuple) and
3+
the validate-before-_run_protocol ordering.
4+
5+
The methods route every dbapi cursor SELECT (via
6+
``cursor.py:_call_client``) but had no direct unit tests; the
7+
two source lines (one validate + one ``_run_protocol``-await
8+
per method) were uncovered by the unit suite (only integration
9+
tests exercised them).
10+
"""
11+
12+
from __future__ import annotations
13+
14+
from typing import Any
15+
from unittest.mock import AsyncMock, MagicMock
16+
17+
import pytest
18+
19+
from dqliteclient.connection import DqliteConnection
20+
from dqliteclient.exceptions import DataError
21+
22+
23+
def _make_conn() -> DqliteConnection:
24+
"""Skeleton DqliteConnection sufficient for the validate +
25+
_run_protocol-mock paths; bypasses actual transport."""
26+
conn = DqliteConnection.__new__(DqliteConnection)
27+
conn._closed = False # type: ignore[attr-defined]
28+
conn._db_id = 1
29+
conn._protocol = MagicMock()
30+
return conn
31+
32+
33+
@pytest.mark.asyncio
34+
async def test_query_raw_returns_two_tuple(monkeypatch: pytest.MonkeyPatch) -> None:
35+
"""``query_raw`` returns ``(column_names, rows)`` —
36+
``(list[str], list[list[Any]])``."""
37+
conn = _make_conn()
38+
expected: tuple[list[str], list[list[Any]]] = (["c0", "c1"], [[1, "a"], [2, "b"]])
39+
40+
async def _fake_run_protocol(_op: Any) -> tuple[list[str], list[list[Any]]]:
41+
return expected
42+
43+
monkeypatch.setattr(conn, "_run_protocol", _fake_run_protocol)
44+
result = await conn.query_raw("SELECT 1, 'a'")
45+
assert result == expected
46+
assert len(result) == 2
47+
48+
49+
@pytest.mark.asyncio
50+
async def test_query_raw_typed_returns_four_tuple(monkeypatch: pytest.MonkeyPatch) -> None:
51+
"""``query_raw_typed`` returns
52+
``(column_names, column_types, row_types, rows)``."""
53+
conn = _make_conn()
54+
expected: tuple[list[str], list[int], list[list[int]], list[list[Any]]] = (
55+
["c0"],
56+
[1],
57+
[[1], [1]],
58+
[[1], [2]],
59+
)
60+
61+
async def _fake_run_protocol(
62+
_op: Any,
63+
) -> tuple[list[str], list[int], list[list[int]], list[list[Any]]]:
64+
return expected
65+
66+
monkeypatch.setattr(conn, "_run_protocol", _fake_run_protocol)
67+
result = await conn.query_raw_typed("SELECT 1")
68+
assert result == expected
69+
assert len(result) == 4
70+
71+
72+
@pytest.mark.asyncio
73+
async def test_query_raw_validates_params_before_run_protocol() -> None:
74+
"""``_validate_params`` must run before ``_run_protocol``: a
75+
Mapping passed as ``params`` raises ``DataError`` without
76+
touching the wire — proves the validate-first ordering."""
77+
conn = _make_conn()
78+
fake_run = AsyncMock(side_effect=AssertionError("must not be called"))
79+
conn._run_protocol = fake_run
80+
81+
with pytest.raises(DataError):
82+
await conn.query_raw("SELECT 1", {"a": 1}) # type: ignore[arg-type]
83+
fake_run.assert_not_called()
84+
85+
86+
@pytest.mark.asyncio
87+
async def test_query_raw_typed_validates_params_before_run_protocol() -> None:
88+
conn = _make_conn()
89+
fake_run = AsyncMock(side_effect=AssertionError("must not be called"))
90+
conn._run_protocol = fake_run
91+
92+
with pytest.raises(DataError):
93+
await conn.query_raw_typed("SELECT 1", {1, 2}) # type: ignore[arg-type]
94+
fake_run.assert_not_called()

0 commit comments

Comments
 (0)