Coverage for app/backend/src/tests/test_query_log.py: 100%
54 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-10 04:35 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-10 04:35 +0000
1"""Tests for the SQL query recorder.
3The fingerprint is the key the CI report groups and diffs on, so anything that makes it vary between runs turns the
4whole report into noise. These pin the normalisations it relies on.
5"""
7from tests.fixtures import query_log
10def test_fingerprint_replaces_bound_parameters():
11 sql = "SELECT users.id FROM users WHERE users.id = %(id_1)s AND users.username = %(username_1)s"
12 assert query_log._fingerprint(sql) == "SELECT users.id FROM users WHERE users.id = ? AND users.username = ?"
15def test_fingerprint_collapses_expanded_in_lists():
16 """An IN list's length tracks the test data, so two runs must not produce different shapes for one query."""
17 two = query_log._fingerprint("SELECT 1 FROM users WHERE users.id IN (%(id_1)s, %(id_2)s)")
18 five = query_log._fingerprint(
19 "SELECT 1 FROM users WHERE users.id IN (%(id_1)s, %(id_2)s, %(id_3)s, %(id_4)s, %(id_5)s)"
20 )
21 assert two == five == "SELECT 1 FROM users WHERE users.id IN (?)"
24def test_fingerprint_normalises_whitespace_and_strips_comments():
25 sql = "SELECT 1\n FROM users\n WHERE id = %(id_1)s /* traceparent='00-abc' */"
26 assert query_log._fingerprint(sql) == "SELECT 1 FROM users WHERE id = ?"
29def test_fingerprint_collapses_repeated_values_tuples():
30 """A multi-row insert's tuple count tracks the batch size, which varies with the test data."""
31 two = query_log._fingerprint("INSERT INTO users (a, b) VALUES (%(a)s, %(b)s), (%(a_1)s, %(b_1)s)")
32 four = query_log._fingerprint(
33 "INSERT INTO users (a, b) VALUES (%(a)s, %(b)s), (%(a_1)s, %(b_1)s), (%(a_2)s, %(b_2)s), (%(a_3)s, %(b_3)s)"
34 )
35 assert two == four == "INSERT INTO users (a, b) VALUES (?)"
38def test_fingerprint_collapses_bulk_inlined_literals():
39 """The real timezone_areas.sql inlines WKB hex as literals, so each row would otherwise be its own shape."""
40 a = query_log._fingerprint(
41 "INSERT INTO timezone_areas (tzid, geom) VALUES ('Etc/UTC', '0106000020E6" + "A" * 300 + "')"
42 )
43 b = query_log._fingerprint(
44 "INSERT INTO timezone_areas (tzid, geom) VALUES ('Etc/UTC', '0106000020E6" + "B" * 900 + "')"
45 )
46 assert a == b
47 assert len(a) < 100
50def test_fingerprint_is_capped():
51 """The cap is what bounds the artifact: uncapped, the timezone_areas load took one CI node's dump to 495 MB."""
52 huge = query_log._fingerprint("SELECT " + "x" * 100_000)
53 assert len(huge) == query_log._MAX_SQL_CHARS + len(query_log._TRUNCATION_MARKER)
54 assert huge.endswith(query_log._TRUNCATION_MARKER)
57def test_fingerprint_keeps_distinct_queries_distinct():
58 a = query_log._fingerprint("SELECT users.id FROM users WHERE users.id = %(id_1)s")
59 b = query_log._fingerprint("SELECT users.id FROM users WHERE users.username = %(username_1)s")
60 assert a != b
63def test_shape_id_is_content_addressed():
64 """Ids must depend only on the fingerprint: the pytest-split nodes assign them independently."""
65 sql = "SELECT 1 FROM users WHERE id = ?"
66 assert query_log._shape_id(sql) == query_log._shape_id(sql)
67 assert query_log._shape_id(sql) != query_log._shape_id("SELECT 2 FROM users WHERE id = ?")
70def test_callsite_falls_back_to_test_frames():
71 """Fixture setup often issues queries with no application frame on the stack, and should still be attributed."""
72 site_id = query_log._callsite()
73 assert query_log._sites[site_id].startswith("tests/test_query_log.py:")
74 assert "test_callsite_falls_back_to_test_frames" in query_log._sites[site_id]
77def test_callsite_ids_are_stable_and_content_addressed():
78 """Node dumps are merged by site id, so the same chain must produce the same id everywhere."""
79 first = query_log._callsite()
80 second = query_log._callsite()
81 # Different lines, so different sites, but each id is the hash of its own rendering.
82 assert first != second
83 assert first == query_log._shape_id(query_log._sites[first])
86def test_frame_kind_separates_application_from_test_and_plumbing():
87 assert query_log._frame_kind(_dummy_code("/app/backend/src/couchers/servicers/api.py")) == query_log._APP
88 assert query_log._frame_kind(_dummy_code("/app/backend/src/tests/fixtures/db.py")) == query_log._TEST
89 assert query_log._frame_kind(_dummy_code("/venv/lib/sqlalchemy/orm/session.py")) == query_log._SKIP
90 assert query_log._frame_kind(_dummy_code("/app/backend/src/tests/fixtures/query_log.py")) == query_log._SKIP
93def _dummy_code(filename: str):
94 """A code object with a chosen co_filename, to exercise the frame classifier without building real frames."""
95 return compile("pass", filename, "exec")
98def test_span_is_inert_when_recording_is_off(monkeypatch):
99 """Ordinary runs go through the same span() calls, so being disabled must record nothing and raise nothing.
101 Forced off rather than asserted off, so this holds whether or not the suite itself was given --query-log.
102 """
103 monkeypatch.setattr(query_log, "_enabled", False)
104 before = {test: list(spans) for test, spans in query_log._tests.items()}
105 with query_log.span("rpc", "/org.couchers.api.core.API/GetUser"):
106 pass
107 assert query_log._tests == before