Navegador

navegador / tests / test_migrations.py
Blame History Raw 127 lines
1
"""Tests for navegador.graph.migrations — schema versioning and migration."""
2
3
from unittest.mock import MagicMock, call
4
5
import pytest
6
7
from navegador.graph.migrations import (
8
CURRENT_SCHEMA_VERSION,
9
_migrations,
10
get_schema_version,
11
migrate,
12
needs_migration,
13
set_schema_version,
14
)
15
16
17
def _mock_store(version=None):
18
store = MagicMock()
19
if version is None:
20
store.query.return_value = MagicMock(result_set=[])
21
else:
22
store.query.return_value = MagicMock(result_set=[[version]])
23
return store
24
25
26
# ── get_schema_version ───────────────────────────────────────────────────────
27
28
class TestGetSchemaVersion:
29
def test_returns_zero_for_empty_graph(self):
30
store = _mock_store(version=None)
31
assert get_schema_version(store) == 0
32
33
def test_returns_zero_for_null_version(self):
34
store = MagicMock()
35
store.query.return_value = MagicMock(result_set=[[None]])
36
assert get_schema_version(store) == 0
37
38
def test_returns_stored_version(self):
39
store = _mock_store(version=2)
40
assert get_schema_version(store) == 2
41
42
43
# ── set_schema_version ──────────────────────────────────────────────────────
44
45
class TestSetSchemaVersion:
46
def test_calls_query_with_merge(self):
47
store = MagicMock()
48
set_schema_version(store, 3)
49
store.query.assert_called_once()
50
cypher = store.query.call_args[0][0]
51
assert "MERGE" in cypher
52
assert store.query.call_args[0][1]["version"] == 3
53
54
55
# ── needs_migration ──────────────────────────────────────────────────────────
56
57
class TestNeedsMigration:
58
def test_true_when_behind(self):
59
store = _mock_store(version=0)
60
assert needs_migration(store) is True
61
62
def test_false_when_current(self):
63
store = _mock_store(version=CURRENT_SCHEMA_VERSION)
64
assert needs_migration(store) is False
65
66
67
# ── migrate ──────────────────────────────────────────────────────────────────
68
69
class TestMigrate:
70
def test_applies_all_migrations_from_zero(self):
71
call_log = []
72
73
def track_query(cypher, params=None):
74
call_log.append(cypher)
75
result = MagicMock()
76
# get_schema_version query returns no rows initially
77
if "Meta" in cypher and "RETURN" in cypher:
78
result.result_set = []
79
else:
80
result.result_set = []
81
return result
82
83
store = MagicMock()
84
store.query.side_effect = track_query
85
86
applied = migrate(store)
87
assert applied == list(range(1, CURRENT_SCHEMA_VERSION + 1))
88
89
def test_no_op_when_already_current(self):
90
store = _mock_store(version=CURRENT_SCHEMA_VERSION)
91
applied = migrate(store)
92
assert applied == []
93
94
def test_raises_on_missing_migration(self):
95
# Temporarily remove a migration to trigger the RuntimeError
96
saved = _migrations.pop(0)
97
try:
98
store = _mock_store(version=None)
99
with pytest.raises(RuntimeError, match="No migration registered"):
100
migrate(store)
101
finally:
102
_migrations[0] = saved
103
104
105
# ── migrations registry ─────────────────────────────────────────────────────
106
107
class TestMigrationsRegistry:
108
def test_has_migration_for_each_version(self):
109
for v in range(CURRENT_SCHEMA_VERSION):
110
assert v in _migrations, f"Missing migration for version {v} -> {v + 1}"
111
112
def test_current_version_is_positive(self):
113
assert CURRENT_SCHEMA_VERSION > 0
114
115
def test_migration_0_to_1_runs(self):
116
store = MagicMock()
117
store.query.return_value = MagicMock(result_set=[])
118
_migrations[0](store)
119
120
def test_migration_1_to_2_sets_content_hash(self):
121
store = MagicMock()
122
store.query.return_value = MagicMock(result_set=[])
123
_migrations[1](store)
124
store.query.assert_called_once()
125
cypher = store.query.call_args[0][0]
126
assert "content_hash" in cypher
127

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button