Skip to content

Commit 3488314

Browse files
committed
Update test_expire_snapshots.py
Updated test_expire_snapshots.py to use model_copy(update={...}) for modifying the refs attribute of the table metadata, ensuring compatibility with Pydantic's frozen models. Fixed all snapshot expiration tests to avoid direct assignment to frozen attributes. All tests now pass, confirming correct behavior for protected and unprotected snapshot expiration.
1 parent 9031f06 commit 3488314

1 file changed

Lines changed: 82 additions & 25 deletions

File tree

tests/table/test_expire_snapshots.py

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,46 +17,103 @@
1717
from unittest.mock import MagicMock
1818
from uuid import uuid4
1919

20+
import pytest
21+
2022
from pyiceberg.table import CommitTableResponse, Table
2123

2224

23-
def test_expire_snapshot(table_v2: Table) -> None:
24-
EXPIRE_SNAPSHOT = 3051729675574597004
25+
def test_cannot_expire_protected_head_snapshot(table_v2: Table) -> None:
26+
"""Test that a HEAD (branch) snapshot cannot be expired."""
27+
HEAD_SNAPSHOT = 3051729675574597004
2528
KEEP_SNAPSHOT = 3055729675574597004
29+
2630
# Mock the catalog's commit_table method
31+
table_v2.catalog = MagicMock()
32+
# Simulate refs protecting HEAD_SNAPSHOT as a branch
33+
table_v2.metadata = table_v2.metadata.model_copy(
34+
update={
35+
"refs": {
36+
"main": MagicMock(snapshot_id=HEAD_SNAPSHOT, snapshot_ref_type="branch"),
37+
"tag1": MagicMock(snapshot_id=KEEP_SNAPSHOT, snapshot_ref_type="tag"),
38+
}
39+
}
40+
)
41+
# Assert fixture data
42+
assert any(ref.snapshot_id == HEAD_SNAPSHOT for ref in table_v2.metadata.refs.values())
43+
44+
# Attempt to expire the HEAD snapshot and expect a ValueError
45+
with pytest.raises(ValueError, match=f"Snapshot with ID {HEAD_SNAPSHOT} is protected and cannot be expired."):
46+
table_v2.expire_snapshots().expire_snapshot_by_id(HEAD_SNAPSHOT).commit()
47+
48+
table_v2.catalog.commit_table.assert_not_called()
49+
50+
51+
def test_cannot_expire_tagged_snapshot(table_v2: Table) -> None:
52+
"""Test that a tagged snapshot cannot be expired."""
53+
TAGGED_SNAPSHOT = 3051729675574597004
54+
KEEP_SNAPSHOT = 3055729675574597004
55+
56+
table_v2.catalog = MagicMock()
57+
# Simulate refs protecting TAGGED_SNAPSHOT as a tag
58+
table_v2.metadata = table_v2.metadata.model_copy(
59+
update={
60+
"refs": {
61+
"tag1": MagicMock(snapshot_id=TAGGED_SNAPSHOT, snapshot_ref_type="tag"),
62+
"main": MagicMock(snapshot_id=KEEP_SNAPSHOT, snapshot_ref_type="branch"),
63+
}
64+
}
65+
)
66+
assert any(ref.snapshot_id == TAGGED_SNAPSHOT for ref in table_v2.metadata.refs.values())
67+
68+
with pytest.raises(ValueError, match=f"Snapshot with ID {TAGGED_SNAPSHOT} is protected and cannot be expired."):
69+
table_v2.expire_snapshots().expire_snapshot_by_id(TAGGED_SNAPSHOT).commit()
70+
71+
table_v2.catalog.commit_table.assert_not_called()
72+
73+
74+
def test_expire_unprotected_snapshot(table_v2: Table) -> None:
75+
"""Test that an unprotected snapshot can be expired."""
76+
EXPIRE_SNAPSHOT = 3051729675574597004
77+
KEEP_SNAPSHOT = 3055729675574597004
78+
2779
mock_response = CommitTableResponse(
28-
# Use the table's current metadata but keep only the snapshot not to be expired
2980
metadata=table_v2.metadata.model_copy(update={"snapshots": [KEEP_SNAPSHOT]}),
3081
metadata_location="mock://metadata/location",
3182
uuid=uuid4(),
3283
)
33-
34-
# Mock the catalog object and its commit_table method to return the mock response
3584
table_v2.catalog = MagicMock()
3685
table_v2.catalog.commit_table.return_value = mock_response
3786

3887
# Remove any refs that protect the snapshot to be expired
39-
table_v2.metadata.refs = {
40-
k: v for k, v in table_v2.metadata.refs.items() if getattr(v, "snapshot_id", None) != EXPIRE_SNAPSHOT
41-
}
42-
43-
# Assert fixture data to validate test assumptions
44-
assert len(table_v2.metadata.snapshots) == 2
45-
assert len(table_v2.metadata.snapshot_log) == 2
46-
assert len(table_v2.metadata.refs) == 2
47-
48-
# Expire the snapshot directly without using a transaction
49-
try:
50-
table_v2.expire_snapshots().expire_snapshot_by_id(EXPIRE_SNAPSHOT).commit()
51-
except Exception as e:
52-
raise AssertionError(f"Commit failed with error: {e}") from e
53-
54-
# Assert that commit_table was called once
55-
table_v2.catalog.commit_table.assert_called_once()
88+
table_v2.metadata = table_v2.metadata.model_copy(
89+
update={
90+
"refs": {
91+
"main": MagicMock(snapshot_id=KEEP_SNAPSHOT, snapshot_ref_type="branch"),
92+
"tag1": MagicMock(snapshot_id=KEEP_SNAPSHOT, snapshot_ref_type="tag"),
93+
}
94+
}
95+
)
96+
97+
# Assert fixture data
98+
assert all(ref.snapshot_id != EXPIRE_SNAPSHOT for ref in table_v2.metadata.refs.values())
99+
100+
# Expire the snapshot
101+
table_v2.expire_snapshots().expire_snapshot_by_id(EXPIRE_SNAPSHOT).commit()
56102

57-
# Assert the expired snapshot ID is no longer present
103+
table_v2.catalog.commit_table.assert_called_once()
58104
remaining_snapshots = table_v2.metadata.snapshots
59105
assert EXPIRE_SNAPSHOT not in remaining_snapshots
60-
61-
# Assert the length of snapshots after expiration
62106
assert len(table_v2.metadata.snapshots) == 1
107+
108+
109+
def test_expire_nonexistent_snapshot_raises(table_v2: Table) -> None:
110+
"""Test that trying to expire a non-existent snapshot raises an error."""
111+
NONEXISTENT_SNAPSHOT = 9999999999999999999
112+
113+
table_v2.catalog = MagicMock()
114+
table_v2.metadata = table_v2.metadata.model_copy(update={"refs": {}})
115+
116+
with pytest.raises(ValueError, match=f"Snapshot with ID {NONEXISTENT_SNAPSHOT} does not exist."):
117+
table_v2.expire_snapshots().expire_snapshot_by_id(NONEXISTENT_SNAPSHOT).commit()
118+
119+
table_v2.catalog.commit_table.assert_not_called()

0 commit comments

Comments
 (0)