154 lines
4.2 KiB
Python
154 lines
4.2 KiB
Python
"""Tests for Kanta read-only mode."""
|
|
|
|
import pytest
|
|
|
|
from kanta.exceptions import DataIntegrityError, FileLockError
|
|
from kanta.serialization import struct_to_dict
|
|
|
|
from .support import (
|
|
Data,
|
|
EvolvableDataV2,
|
|
fixed_change,
|
|
make_kanta,
|
|
make_migrations_module,
|
|
seed_single_change,
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_readonly_opens_existing_database(tmp_path, format_config):
|
|
path = tmp_path / "test.db"
|
|
seed_single_change(path, fixed_change("seed", {"counter": 7}), format_config)
|
|
|
|
kanta = make_kanta(path, Data, format_config)
|
|
await kanta.open(readonly=True)
|
|
|
|
assert isinstance(kanta.data, Data)
|
|
assert kanta.data.counter == 7
|
|
assert kanta._impl.readonly is True
|
|
assert kanta._impl.background_task is None
|
|
|
|
await kanta.close()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_readonly_missing_file_fails(tmp_path, format_config):
|
|
path = tmp_path / "missing.db"
|
|
kanta = make_kanta(path, Data, format_config)
|
|
|
|
with pytest.raises(FileLockError):
|
|
await kanta.open(readonly=True)
|
|
|
|
assert not path.exists()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_readonly_empty_file_fails(tmp_path, format_config):
|
|
path = tmp_path / "empty.db"
|
|
path.touch()
|
|
kanta = make_kanta(path, Data, format_config)
|
|
|
|
with pytest.raises(DataIntegrityError, match="empty"):
|
|
await kanta.open(readonly=True)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_readonly_transaction_fails(tmp_path, format_config):
|
|
path = tmp_path / "test.db"
|
|
seed_single_change(path, fixed_change("seed", {"counter": 1}), format_config)
|
|
|
|
kanta = make_kanta(path, Data, format_config)
|
|
await kanta.open(readonly=True)
|
|
|
|
with pytest.raises(DataIntegrityError, match="read-only"):
|
|
with kanta.transaction(action="inc") as data:
|
|
data.counter = 2
|
|
|
|
# In-memory state must remain unchanged.
|
|
assert kanta.data.counter == 1
|
|
await kanta.close()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_readonly_flush_fails(tmp_path, format_config):
|
|
path = tmp_path / "test.db"
|
|
seed_single_change(path, fixed_change("seed", {"counter": 1}), format_config)
|
|
|
|
kanta = make_kanta(path, Data, format_config)
|
|
await kanta.open(readonly=True)
|
|
|
|
with pytest.raises(DataIntegrityError, match="read-only"):
|
|
await kanta.flush()
|
|
|
|
await kanta.close()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_readonly_create_true_does_not_create_file(tmp_path, format_config):
|
|
path = tmp_path / "test.db"
|
|
kanta = make_kanta(path, Data, format_config)
|
|
|
|
with pytest.raises(FileLockError):
|
|
await kanta.open(create=True, readonly=True)
|
|
|
|
assert not path.exists()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_readonly_does_not_persist_changes(tmp_path, format_config):
|
|
path = tmp_path / "test.db"
|
|
seed_single_change(path, fixed_change("seed", {"counter": 1}), format_config)
|
|
original_content = path.read_bytes()
|
|
|
|
kanta = make_kanta(path, Data, format_config)
|
|
await kanta.open(readonly=True)
|
|
await kanta.close()
|
|
|
|
assert path.read_bytes() == original_content
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_readonly_runs_migrations(tmp_path, format_config):
|
|
path = tmp_path / "test.db"
|
|
seed_single_change(
|
|
path,
|
|
fixed_change("seed", {"counter": 1}, version=0),
|
|
format_config,
|
|
)
|
|
|
|
def migrate_v1(data, ctx):
|
|
data.setdefault("enabled", True)
|
|
|
|
migrations = make_migrations_module("readonly_migrations", "migrate_v1", migrate_v1)
|
|
|
|
kanta = make_kanta(path, EvolvableDataV2, format_config, migrations=migrations)
|
|
await kanta.open(readonly=True)
|
|
|
|
assert kanta.data.counter == 1
|
|
# Migration ran in memory even though no change was persisted.
|
|
assert struct_to_dict(kanta.data, serializer=kanta._impl.serializer) == {
|
|
"counter": 1,
|
|
"enabled": True,
|
|
}
|
|
assert not kanta._impl.pending_changes
|
|
|
|
await kanta.close()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_readwrite_and_readonly_can_open_together(tmp_path, format_config):
|
|
path = tmp_path / "test.db"
|
|
seed_single_change(path, fixed_change("seed", {"counter": 1}), format_config)
|
|
|
|
rw = make_kanta(path, Data, format_config)
|
|
await rw.open()
|
|
|
|
ro = make_kanta(path, Data, format_config)
|
|
await ro.open(readonly=True)
|
|
|
|
assert rw.data.counter == 1
|
|
assert ro.data.counter == 1
|
|
|
|
await ro.close()
|
|
await rw.close()
|