Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions conserver/links/tag/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ def run(

vcon_redis = VconRedis()
vCon = vcon_redis.get_vcon(vcon_uuid)
if vCon is None:
# get_vcon returns None when the vCon is missing from Redis and every
# storage backend (evicted/expired under chain latency). None is the
# documented "halt the chain" contract, so stop rather than crash.
logger.warning(f"tag: vCon {vcon_uuid} not found, halting chain")
return None
Comment on lines +19 to +24
for tag in opts.get("tags", []):
vCon.add_tag(tag_name=tag, tag_value=tag)
vcon_redis.store_vcon(vCon)
Expand Down
13 changes: 13 additions & 0 deletions conserver/links/tag/test_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ def test_run_respects_custom_tags(mock_vcon_redis):
mock_instance.store_vcon.assert_called_once_with(vcon)


@patch("links.tag.VconRedis")
def test_run_halts_chain_when_vcon_missing(mock_vcon_redis):
# Regression for CON-617: get_vcon returns None on a Redis/storage miss.
# The link must halt the chain, not crash with 'NoneType' has no add_tag.
mock_instance = mock_vcon_redis.return_value
mock_instance.get_vcon.return_value = None

result = run("missing-uuid", "tag")

assert result is None
mock_instance.store_vcon.assert_not_called()


@patch("links.tag.VconRedis")
def test_run_handles_empty_tag_list(mock_vcon_redis):
vcon = Vcon.build_new()
Expand Down