Skip to content

Commit 6425bab

Browse files
Add 3 BOOKMARK unit tests: RV advance, raw-dict handler, multiple bookmarks
Co-authored-by: brendandburns <5751682+brendandburns@users.noreply.github.com> Agent-Logs-Url: https://github.com/kubernetes-client/python/sessions/d5256bd7-1cab-4399-97a0-9c115a2f97cb
1 parent 1d10150 commit 6425bab

File tree

1 file changed

+107
-0
lines changed

1 file changed

+107
-0
lines changed

kubernetes/test/test_informer.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,113 @@ def fake_stream(func, **kw):
367367
self.assertEqual(len(cached), 1)
368368
self.assertIs(cached[0], pod)
369369

370+
def test_bookmark_advances_resource_version(self):
371+
"""A BOOKMARK event causes the informer's _resource_version to advance.
372+
373+
PR #2505 added BOOKMARK-aware handling to Watch.unmarshal_event: it
374+
extracts resourceVersion from the raw BOOKMARK dict and stores it on
375+
self.resource_version *without* deserialising the object (because
376+
BOOKMARK events may be incomplete). The informer must read that value
377+
back so that the next watch reconnect starts from the BOOKMARK's RV
378+
rather than the initial-list RV.
379+
"""
380+
bookmark_obj = {"metadata": {"resourceVersion": "100"}}
381+
382+
list_func = MagicMock()
383+
list_resp = MagicMock()
384+
list_resp.items = []
385+
list_resp.metadata = MagicMock(resource_version="5")
386+
list_func.return_value = list_resp
387+
388+
informer = SharedInformer(list_func=list_func)
389+
390+
with patch("kubernetes.informer.informer.Watch") as MockWatch:
391+
mock_w = MagicMock()
392+
# Simulate Watch.unmarshal_event updating resource_version on BOOKMARK.
393+
mock_w.resource_version = "100"
394+
395+
def fake_stream(func, **kw):
396+
yield {"type": "BOOKMARK", "object": bookmark_obj, "raw_object": bookmark_obj}
397+
informer._stop_event.set()
398+
399+
mock_w.stream.side_effect = fake_stream
400+
MockWatch.return_value = mock_w
401+
402+
informer.start()
403+
informer._thread.join(timeout=3)
404+
405+
# The informer must have synced the RV from the BOOKMARK.
406+
self.assertEqual(informer._resource_version, "100")
407+
408+
def test_bookmark_handler_receives_raw_dict(self):
409+
"""BOOKMARK handlers receive the raw dict, not a deserialized model.
410+
411+
Watch intentionally skips deserialization for BOOKMARK events (PR #2505)
412+
because BOOKMARK objects may be incomplete. The informer passes
413+
``event.get('raw_object', obj)`` to the BOOKMARK handler, so it must
414+
always be a dict rather than a typed Kubernetes model object.
415+
"""
416+
bookmark_obj = {"metadata": {"resourceVersion": "77"}}
417+
received = []
418+
419+
list_func = MagicMock()
420+
list_resp = MagicMock()
421+
list_resp.items = []
422+
list_resp.metadata = MagicMock(resource_version="1")
423+
list_func.return_value = list_resp
424+
425+
informer = SharedInformer(list_func=list_func)
426+
informer.add_event_handler(BOOKMARK, received.append)
427+
428+
with patch("kubernetes.informer.informer.Watch") as MockWatch:
429+
mock_w = MagicMock()
430+
mock_w.resource_version = "77"
431+
432+
def fake_stream(func, **kw):
433+
yield {"type": "BOOKMARK", "object": bookmark_obj, "raw_object": bookmark_obj}
434+
informer._stop_event.set()
435+
436+
mock_w.stream.side_effect = fake_stream
437+
MockWatch.return_value = mock_w
438+
439+
informer.start()
440+
informer._thread.join(timeout=3)
441+
442+
self.assertEqual(len(received), 1)
443+
# Must be the raw dict, not a deserialized model.
444+
self.assertIsInstance(received[0], dict)
445+
self.assertEqual(received[0]["metadata"]["resourceVersion"], "77")
446+
447+
def test_multiple_bookmarks_advance_resource_version_to_latest(self):
448+
"""Multiple BOOKMARK events each update _resource_version to the latest value."""
449+
list_func = MagicMock()
450+
list_resp = MagicMock()
451+
list_resp.items = []
452+
list_resp.metadata = MagicMock(resource_version="1")
453+
list_func.return_value = list_resp
454+
455+
informer = SharedInformer(list_func=list_func)
456+
457+
rv_sequence = iter(["10", "20", "30"])
458+
459+
with patch("kubernetes.informer.informer.Watch") as MockWatch:
460+
mock_w = MagicMock()
461+
462+
def fake_stream(func, **kw):
463+
for rv in ["10", "20", "30"]:
464+
bk = {"metadata": {"resourceVersion": rv}}
465+
mock_w.resource_version = rv
466+
yield {"type": "BOOKMARK", "object": bk, "raw_object": bk}
467+
informer._stop_event.set()
468+
469+
mock_w.stream.side_effect = fake_stream
470+
MockWatch.return_value = mock_w
471+
472+
informer.start()
473+
informer._thread.join(timeout=3)
474+
475+
self.assertEqual(informer._resource_version, "30")
476+
370477
def test_resync_period_triggers_full_list(self):
371478
"""A full List call must be made to the API server on every resync_period.
372479

0 commit comments

Comments
 (0)