test(tradein-matching): fix off-by-one in 5 match_house tests + add lock regression
Advisory lock in match_or_create_house() is now the first db.execute() call; existing tests built side_effect lists assuming the tier query was first, causing each to read off-by-one (Tier 0 cadastr lookup gets None, falls through, etc.). Prepend a None row to all 5 test_match_house_* _make_db lists. Add test_match_house_advisory_lock_called_first as a regression guard against future re-ordering of the lock call. Per PR #501 reviewer feedback.
This commit is contained in:
parent
9a6890de6a
commit
cea2af8d3d
1 changed files with 35 additions and 1 deletions
|
|
@ -148,6 +148,7 @@ def test_match_house_tier0_cadastr():
|
|||
from app.services.matching.houses import match_or_create_house
|
||||
|
||||
db = _make_db([
|
||||
None, # pg_advisory_xact_lock
|
||||
{'id': 42}, # cadastral match
|
||||
None, # _upsert_house_source (INSERT ... ON CONFLICT)
|
||||
])
|
||||
|
|
@ -171,7 +172,8 @@ def test_match_house_tier1_source_exact():
|
|||
from app.services.matching.houses import match_or_create_house
|
||||
|
||||
db = _make_db([
|
||||
{'house_id': 7}, # house_sources hit (first and only execute call)
|
||||
None, # pg_advisory_xact_lock
|
||||
{'house_id': 7}, # house_sources hit
|
||||
])
|
||||
house_id, conf, method = match_or_create_house(
|
||||
db, 'avito', 'ext-999',
|
||||
|
|
@ -186,6 +188,7 @@ def test_match_house_tier2_fingerprint():
|
|||
from app.services.matching.houses import match_or_create_house
|
||||
|
||||
db = _make_db([
|
||||
None, # pg_advisory_xact_lock
|
||||
None, # house_sources miss
|
||||
{'house_id': 15}, # fingerprint hit
|
||||
None, # _upsert_house_source
|
||||
|
|
@ -204,6 +207,7 @@ def test_match_house_tier3_geo():
|
|||
from app.services.matching.houses import match_or_create_house
|
||||
|
||||
db = _make_db([
|
||||
None, # pg_advisory_xact_lock
|
||||
None, # house_sources miss
|
||||
None, # fingerprint miss
|
||||
{'id': 22, 'dist': 15.5}, # geo hit
|
||||
|
|
@ -224,6 +228,7 @@ def test_match_house_new():
|
|||
from app.services.matching.houses import match_or_create_house
|
||||
|
||||
db = _make_db([
|
||||
None, # pg_advisory_xact_lock
|
||||
None, # house_sources miss
|
||||
None, # fingerprint miss
|
||||
None, # geo miss
|
||||
|
|
@ -240,6 +245,35 @@ def test_match_house_new():
|
|||
assert method == 'new'
|
||||
|
||||
|
||||
def test_match_house_advisory_lock_called_first():
|
||||
"""Regression: pg_advisory_xact_lock must be the first execute() call
|
||||
on match_or_create_house. Re-ordering would re-introduce the race
|
||||
(finding #1 from 2026-05-24 audit). PR #501."""
|
||||
from app.services.matching.houses import match_or_create_house
|
||||
|
||||
db = _make_db([
|
||||
None, # pg_advisory_xact_lock
|
||||
{'house_id': 7}, # house_sources Tier 1 hit (short path)
|
||||
])
|
||||
match_or_create_house(
|
||||
db, 'avito', 'regress-001',
|
||||
address='Тестовая 1', lat=56.8, lon=60.5,
|
||||
)
|
||||
|
||||
# First execute MUST be the lock acquisition, with bind {'fp': <hex>}.
|
||||
first_call = db.execute.call_args_list[0]
|
||||
sql_obj = first_call[0][0] # TextClause
|
||||
bind = first_call[0][1] # dict
|
||||
|
||||
assert 'pg_advisory_xact_lock' in str(sql_obj), (
|
||||
f'first execute must be advisory lock, got: {sql_obj}'
|
||||
)
|
||||
assert 'fp' in bind, f"lock bind must include fp, got: {bind}"
|
||||
assert isinstance(bind['fp'], str) and len(bind['fp']) == 32, (
|
||||
f"fp must be 32-char sha256 hex, got: {bind.get('fp')!r}"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# match_or_create_listing — mock DB tier routing
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue