1212
1313import pytest
1414
15+ import osw .core as core_mod
1516import osw .model .entity as model
1617from osw .core import OSW
1718from osw .utils .wiki import get_namespace , get_title
@@ -23,21 +24,33 @@ def _title(entity):
2324
2425
2526class _FakeMwSite :
26- """Records the API queries and answers them from a set of missing titles."""
27+ """Records the API queries and answers them from a set of missing titles.
2728
28- def __init__ (self , missing_titles = (), normalized = None ):
29+ missing_once holds titles that are reported as missing by the first query
30+ only, which is what a read from a lagging database replica looks like.
31+ """
32+
33+ def __init__ (
34+ self , missing_titles = (), normalized = None , missing_once = (), fails = False
35+ ):
2936 self .missing_titles = set (missing_titles )
37+ self .missing_once = set (missing_once )
3038 self .normalized = normalized or {}
39+ self .fails = fails
3140 self .queries = []
3241
3342 def api (self , action , ** kwargs ):
3443 assert action == "query"
44+ if self .fails :
45+ raise RuntimeError ("the API is not reachable" )
3546 titles = kwargs ["titles" ].split ("|" )
3647 self .queries .append (titles )
48+ missing_now = self .missing_titles | self .missing_once
49+ self .missing_once = set ()
3750 pages = {}
3851 for i , title in enumerate (titles ):
3952 reported = self .normalized .get (title , title )
40- if title in self . missing_titles :
53+ if title in missing_now :
4154 pages [str (- (i + 1 ))] = {"title" : reported , "missing" : "" }
4255 else :
4356 pages [str (i + 1 )] = {"title" : reported , "pageid" : i + 1 }
@@ -62,9 +75,11 @@ def offline_osw(monkeypatch):
6275 OSW , "_apply_overwrite_policy" , staticmethod (lambda param : param .page )
6376 )
6477 monkeypatch .setattr (WtPage , "edit" , lambda self , * a , ** kw : None )
78+ # the delay before the confirmation query, not worth waiting for in a test
79+ monkeypatch .setattr (core_mod , "sleep" , lambda * a , ** kw : None )
6580
66- def _make (missing_titles = (), normalized = None ):
67- mw_site = _FakeMwSite (missing_titles , normalized )
81+ def _make (missing_titles = (), normalized = None , missing_once = (), fails = False ):
82+ mw_site = _FakeMwSite (missing_titles , normalized , missing_once , fails )
6883 return OSW .construct (site = _FakeSite (mw_site )), mw_site
6984
7085 return _make
@@ -137,6 +152,42 @@ def test_verification_is_skipped_offline(offline_osw):
137152 assert set (result .pages .keys ()) == {title }
138153
139154
155+ def test_a_missing_page_is_confirmed_by_a_second_query (offline_osw ):
156+ item = model .Item (label = [model .Label (text = "Ghost" )])
157+ title = _title (item )
158+ osw_obj , mw_site = offline_osw (missing_titles = [title ])
159+
160+ with pytest .raises (OSW .StoreEntityPartialError ):
161+ osw_obj .store_entity (OSW .StoreEntityParam (entities = [item ], parallel = False ))
162+
163+ assert mw_site .queries == [[title ], [title ]]
164+
165+
166+ def test_a_page_that_appears_on_the_second_query_is_not_reported (offline_osw ):
167+ """A read answered by a lagging database replica must not fail the store."""
168+ items = [model .Item (label = [model .Label (text = f"Lag{ i } " )]) for i in range (2 )]
169+ titles = [_title (it ) for it in items ]
170+ osw_obj , mw_site = offline_osw (missing_once = [titles [0 ]])
171+
172+ result = osw_obj .store_entity (OSW .StoreEntityParam (entities = items , parallel = False ))
173+
174+ assert set (result .pages .keys ()) == set (titles )
175+ assert result .failed == {}
176+ # the second query asks only for the title the first one reported as missing
177+ assert mw_site .queries [1 ] == [titles [0 ]]
178+
179+
180+ def test_a_failing_query_keeps_the_pages_and_does_not_raise (offline_osw ):
181+ items = [model .Item (label = [model .Label (text = f"Unverified{ i } " )]) for i in range (2 )]
182+ titles = [_title (it ) for it in items ]
183+ osw_obj , _mw_site = offline_osw (fails = True )
184+
185+ result = osw_obj .store_entity (OSW .StoreEntityParam (entities = items , parallel = False ))
186+
187+ assert set (result .pages .keys ()) == set (titles )
188+ assert result .failed == {}
189+
190+
140191def test_titles_are_queried_in_batches_of_fifty (offline_osw ):
141192 osw_obj , mw_site = offline_osw ()
142193 titles = [f"Item:OSW{ i :04d} " for i in range (120 )]
0 commit comments