Coverage for app/backend/src/tests/test_query_log.py: 100%

58 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-24 08:45 +0000

1"""Tests for the SQL query recorder. 

2 

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""" 

6 

7from tests.fixtures import query_log 

8 

9 

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 = ?" 

13 

14 

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 (?)" 

22 

23 

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 = ?" 

27 

28 

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 (?)" 

36 

37 

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 

48 

49 

50def test_fingerprint_keeps_the_text_between_two_literals(): 

51 """The gap between two literals is not a literal, however long it is: collapsing it merges distinct shapes.""" 

52 touch_user = ( 

53 "WITH touch_user AS (UPDATE users SET last_active=now() WHERE users.id = %(id_1)s " 

54 "AND users.last_active < now() - interval '5 minutes' RETURNING users.id) " 

55 "INSERT INTO user_activity (user_id, period) SELECT id, interval '1 hour' FROM touch_user" 

56 ) 

57 assert "'...'" not in query_log._fingerprint(touch_user) 

58 assert query_log._fingerprint(touch_user) != query_log._fingerprint( 

59 touch_user.replace("RETURNING users.id", "RETURNING users.id, users.last_active") 

60 ) 

61 

62 

63def test_fingerprint_is_capped(): 

64 """The cap is what bounds the artifact: uncapped, the timezone_areas load took one CI node's dump to 495 MB.""" 

65 huge = query_log._fingerprint("SELECT " + "x" * 100_000) 

66 assert len(huge) == query_log._MAX_SQL_CHARS + len(query_log._TRUNCATION_MARKER) 

67 assert huge.endswith(query_log._TRUNCATION_MARKER) 

68 

69 

70def test_fingerprint_keeps_distinct_queries_distinct(): 

71 a = query_log._fingerprint("SELECT users.id FROM users WHERE users.id = %(id_1)s") 

72 b = query_log._fingerprint("SELECT users.id FROM users WHERE users.username = %(username_1)s") 

73 assert a != b 

74 

75 

76def test_shape_id_is_content_addressed(): 

77 """Ids must depend only on the fingerprint: the pytest-split nodes assign them independently.""" 

78 sql = "SELECT 1 FROM users WHERE id = ?" 

79 assert query_log._shape_id(sql) == query_log._shape_id(sql) 

80 assert query_log._shape_id(sql) != query_log._shape_id("SELECT 2 FROM users WHERE id = ?") 

81 

82 

83def test_callsite_falls_back_to_test_frames(): 

84 """Fixture setup often issues queries with no application frame on the stack, and should still be attributed.""" 

85 site_id = query_log._callsite() 

86 assert query_log._sites[site_id].startswith("tests/test_query_log.py:") 

87 assert "test_callsite_falls_back_to_test_frames" in query_log._sites[site_id] 

88 

89 

90def test_callsite_ids_are_stable_and_content_addressed(): 

91 """Node dumps are merged by site id, so the same chain must produce the same id everywhere.""" 

92 first = query_log._callsite() 

93 second = query_log._callsite() 

94 # Different lines, so different sites, but each id is the hash of its own rendering. 

95 assert first != second 

96 assert first == query_log._shape_id(query_log._sites[first]) 

97 

98 

99def test_frame_kind_separates_application_from_test_and_plumbing(): 

100 assert query_log._frame_kind(_dummy_code("/app/backend/src/couchers/servicers/api.py")) == query_log._APP 

101 assert query_log._frame_kind(_dummy_code("/app/backend/src/tests/fixtures/db.py")) == query_log._TEST 

102 assert query_log._frame_kind(_dummy_code("/venv/lib/sqlalchemy/orm/session.py")) == query_log._SKIP 

103 assert query_log._frame_kind(_dummy_code("/app/backend/src/tests/fixtures/query_log.py")) == query_log._SKIP 

104 

105 

106def _dummy_code(filename: str): 

107 """A code object with a chosen co_filename, to exercise the frame classifier without building real frames.""" 

108 return compile("pass", filename, "exec") 

109 

110 

111def test_span_is_inert_when_recording_is_off(monkeypatch): 

112 """Ordinary runs go through the same span() calls, so being disabled must record nothing and raise nothing. 

113 

114 Forced off rather than asserted off, so this holds whether or not the suite itself was given --query-log. 

115 """ 

116 monkeypatch.setattr(query_log, "_enabled", False) 

117 before = {test: list(spans) for test, spans in query_log._tests.items()} 

118 with query_log.span("rpc", "/org.couchers.api.core.API/GetUser"): 

119 pass 

120 assert query_log._tests == before