fix: update test fixtures for SQLite storage migration
Some checks failed
Lint & Test / test (push) Failing after 1m33s
Some checks failed
Lint & Test / test (push) Failing after 1m33s
All store tests were passing file paths instead of Database objects after the JSON-to-SQLite migration. Updated fixtures to create temp Database instances, rewrote backup e2e tests for binary .db format, and fixed config tests for the simplified StorageConfig.
This commit is contained in:
@@ -30,13 +30,21 @@ def _make_app():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def device_store(tmp_path):
|
def _route_db(tmp_path):
|
||||||
return DeviceStore(tmp_path / "devices.json")
|
from wled_controller.storage.database import Database
|
||||||
|
db = Database(tmp_path / "test.db")
|
||||||
|
yield db
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def output_target_store(tmp_path):
|
def device_store(_route_db):
|
||||||
return OutputTargetStore(str(tmp_path / "output_targets.json"))
|
return DeviceStore(_route_db)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def output_target_store(_route_db):
|
||||||
|
return OutputTargetStore(_route_db)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from datetime import datetime, timezone
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from wled_controller.config import Config, StorageConfig, ServerConfig, AuthConfig
|
from wled_controller.config import Config, StorageConfig, ServerConfig, AuthConfig
|
||||||
|
from wled_controller.storage.database import Database
|
||||||
from wled_controller.storage.device_store import Device, DeviceStore
|
from wled_controller.storage.device_store import Device, DeviceStore
|
||||||
from wled_controller.storage.sync_clock import SyncClock
|
from wled_controller.storage.sync_clock import SyncClock
|
||||||
from wled_controller.storage.sync_clock_store import SyncClockStore
|
from wled_controller.storage.sync_clock_store import SyncClockStore
|
||||||
@@ -37,12 +38,25 @@ def test_config_dir(tmp_path):
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def temp_store_dir(tmp_path):
|
def temp_store_dir(tmp_path):
|
||||||
"""Provide a temp directory for JSON store files, cleaned up after tests."""
|
"""Provide a temp directory for store files, cleaned up after tests."""
|
||||||
d = tmp_path / "stores"
|
d = tmp_path / "stores"
|
||||||
d.mkdir(parents=True, exist_ok=True)
|
d.mkdir(parents=True, exist_ok=True)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Database fixture
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def tmp_db(tmp_path):
|
||||||
|
"""Provide a temporary SQLite Database instance."""
|
||||||
|
db = Database(tmp_path / "test.db")
|
||||||
|
yield db
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Config fixtures
|
# Config fixtures
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -55,20 +69,7 @@ def test_config(tmp_path):
|
|||||||
data_dir.mkdir(parents=True, exist_ok=True)
|
data_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
storage = StorageConfig(
|
storage = StorageConfig(
|
||||||
devices_file=str(data_dir / "devices.json"),
|
database_file=str(data_dir / "test.db"),
|
||||||
templates_file=str(data_dir / "capture_templates.json"),
|
|
||||||
postprocessing_templates_file=str(data_dir / "postprocessing_templates.json"),
|
|
||||||
picture_sources_file=str(data_dir / "picture_sources.json"),
|
|
||||||
output_targets_file=str(data_dir / "output_targets.json"),
|
|
||||||
pattern_templates_file=str(data_dir / "pattern_templates.json"),
|
|
||||||
color_strip_sources_file=str(data_dir / "color_strip_sources.json"),
|
|
||||||
audio_sources_file=str(data_dir / "audio_sources.json"),
|
|
||||||
audio_templates_file=str(data_dir / "audio_templates.json"),
|
|
||||||
value_sources_file=str(data_dir / "value_sources.json"),
|
|
||||||
automations_file=str(data_dir / "automations.json"),
|
|
||||||
scene_presets_file=str(data_dir / "scene_presets.json"),
|
|
||||||
color_strip_processing_templates_file=str(data_dir / "color_strip_processing_templates.json"),
|
|
||||||
sync_clocks_file=str(data_dir / "sync_clocks.json"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return Config(
|
return Config(
|
||||||
@@ -84,33 +85,33 @@ def test_config(tmp_path):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def device_store(temp_store_dir):
|
def device_store(tmp_db):
|
||||||
"""Provide a DeviceStore backed by a temp file."""
|
"""Provide a DeviceStore backed by a temp database."""
|
||||||
return DeviceStore(temp_store_dir / "devices.json")
|
return DeviceStore(tmp_db)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def sync_clock_store(temp_store_dir):
|
def sync_clock_store(tmp_db):
|
||||||
"""Provide a SyncClockStore backed by a temp file."""
|
"""Provide a SyncClockStore backed by a temp database."""
|
||||||
return SyncClockStore(str(temp_store_dir / "sync_clocks.json"))
|
return SyncClockStore(tmp_db)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def output_target_store(temp_store_dir):
|
def output_target_store(tmp_db):
|
||||||
"""Provide an OutputTargetStore backed by a temp file."""
|
"""Provide an OutputTargetStore backed by a temp database."""
|
||||||
return OutputTargetStore(str(temp_store_dir / "output_targets.json"))
|
return OutputTargetStore(tmp_db)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def automation_store(temp_store_dir):
|
def automation_store(tmp_db):
|
||||||
"""Provide an AutomationStore backed by a temp file."""
|
"""Provide an AutomationStore backed by a temp database."""
|
||||||
return AutomationStore(str(temp_store_dir / "automations.json"))
|
return AutomationStore(tmp_db)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def value_source_store(temp_store_dir):
|
def value_source_store(tmp_db):
|
||||||
"""Provide a ValueSourceStore backed by a temp file."""
|
"""Provide a ValueSourceStore backed by a temp database."""
|
||||||
return ValueSourceStore(str(temp_store_dir / "value_sources.json"))
|
return ValueSourceStore(tmp_db)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ from wled_controller.storage.automation_store import AutomationStore
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_store(tmp_path) -> AutomationStore:
|
def mock_store(tmp_db) -> AutomationStore:
|
||||||
return AutomationStore(str(tmp_path / "auto.json"))
|
return AutomationStore(tmp_db)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|||||||
@@ -46,10 +46,12 @@ def client(_test_client):
|
|||||||
|
|
||||||
def _clear_stores():
|
def _clear_stores():
|
||||||
"""Remove all entities from all stores for test isolation."""
|
"""Remove all entities from all stores for test isolation."""
|
||||||
# Reset the saves-frozen flag that freeze_saves() sets during restore flows.
|
# Reset frozen-writes flags that restore flows set.
|
||||||
# Without this, subsequent tests can't persist data because _save() is a no-op.
|
# Without this, subsequent tests can't persist data.
|
||||||
from wled_controller.storage.base_store import unfreeze_saves
|
from wled_controller.storage.base_store import unfreeze_saves
|
||||||
unfreeze_saves()
|
unfreeze_saves()
|
||||||
|
import wled_controller.storage.database as _db_mod
|
||||||
|
_db_mod._writes_frozen = False
|
||||||
|
|
||||||
from wled_controller.api import dependencies as deps
|
from wled_controller.api import dependencies as deps
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
"""E2E: Backup and restore flow.
|
"""E2E: Backup and restore flow.
|
||||||
|
|
||||||
Tests creating entities, backing up, deleting, then restoring from backup.
|
Tests creating entities, backing up (SQLite .db file), deleting, then restoring.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TestBackupRestoreFlow:
|
class TestBackupRestoreFlow:
|
||||||
@@ -42,20 +40,12 @@ class TestBackupRestoreFlow:
|
|||||||
resp = client.get("/api/v1/color-strip-sources")
|
resp = client.get("/api/v1/color-strip-sources")
|
||||||
assert resp.json()["count"] == 1
|
assert resp.json()["count"] == 1
|
||||||
|
|
||||||
# 2. Create a backup (GET returns a JSON file)
|
# 2. Create a backup (GET returns a SQLite .db file)
|
||||||
resp = client.get("/api/v1/system/backup")
|
resp = client.get("/api/v1/system/backup")
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
backup_data = resp.json()
|
backup_bytes = resp.content
|
||||||
assert backup_data["meta"]["format"] == "ledgrab-backup"
|
# SQLite files start with this magic header
|
||||||
assert "stores" in backup_data
|
assert backup_bytes[:16].startswith(b"SQLite format 3")
|
||||||
assert "devices" in backup_data["stores"]
|
|
||||||
assert "color_strip_sources" in backup_data["stores"]
|
|
||||||
|
|
||||||
# Verify device is in the backup.
|
|
||||||
# Store files have structure: {"version": "...", "devices": {id: {...}}}
|
|
||||||
devices_store = backup_data["stores"]["devices"]
|
|
||||||
assert "devices" in devices_store
|
|
||||||
assert len(devices_store["devices"]) == 1
|
|
||||||
|
|
||||||
# 3. Delete all created entities
|
# 3. Delete all created entities
|
||||||
resp = client.delete(f"/api/v1/color-strip-sources/{css_id}")
|
resp = client.delete(f"/api/v1/color-strip-sources/{css_id}")
|
||||||
@@ -69,52 +59,37 @@ class TestBackupRestoreFlow:
|
|||||||
resp = client.get("/api/v1/color-strip-sources")
|
resp = client.get("/api/v1/color-strip-sources")
|
||||||
assert resp.json()["count"] == 0
|
assert resp.json()["count"] == 0
|
||||||
|
|
||||||
# 4. Restore from backup (POST with the backup JSON as a file upload)
|
# 4. Restore from backup (POST with the .db file upload)
|
||||||
backup_bytes = json.dumps(backup_data).encode("utf-8")
|
|
||||||
resp = client.post(
|
resp = client.post(
|
||||||
"/api/v1/system/restore",
|
"/api/v1/system/restore",
|
||||||
files={"file": ("backup.json", io.BytesIO(backup_bytes), "application/json")},
|
files={"file": ("backup.db", io.BytesIO(backup_bytes), "application/octet-stream")},
|
||||||
)
|
)
|
||||||
assert resp.status_code == 200, f"Restore failed: {resp.text}"
|
assert resp.status_code == 200, f"Restore failed: {resp.text}"
|
||||||
restore_result = resp.json()
|
restore_result = resp.json()
|
||||||
assert restore_result["status"] == "restored"
|
assert restore_result["status"] == "restored"
|
||||||
assert restore_result["stores_written"] > 0
|
|
||||||
|
|
||||||
# 5. After restore, stores are written to disk but the in-memory
|
|
||||||
# stores haven't been re-loaded (normally a server restart does that).
|
|
||||||
# Verify the backup file was written correctly by reading it back.
|
|
||||||
# The restore endpoint writes JSON files; we check the response confirms success.
|
|
||||||
assert restore_result["restart_scheduled"] is True
|
assert restore_result["restart_scheduled"] is True
|
||||||
|
|
||||||
def test_backup_contains_all_store_keys(self, client):
|
def test_backup_is_valid_sqlite(self, client):
|
||||||
"""Backup response includes entries for all known store types."""
|
"""Backup response is a valid SQLite database file."""
|
||||||
resp = client.get("/api/v1/system/backup")
|
resp = client.get("/api/v1/system/backup")
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
stores = resp.json()["stores"]
|
assert resp.content[:16].startswith(b"SQLite format 3")
|
||||||
# At minimum, these critical stores should be present
|
# Should have Content-Disposition header for download
|
||||||
expected_keys = {
|
assert "attachment" in resp.headers.get("content-disposition", "")
|
||||||
"devices", "output_targets", "color_strip_sources",
|
|
||||||
"capture_templates", "value_sources",
|
|
||||||
}
|
|
||||||
assert expected_keys.issubset(set(stores.keys()))
|
|
||||||
|
|
||||||
def test_restore_rejects_invalid_format(self, client):
|
def test_restore_rejects_invalid_format(self, client):
|
||||||
"""Uploading a non-backup JSON file should fail validation."""
|
"""Uploading a non-SQLite file should fail validation."""
|
||||||
bad_data = json.dumps({"not": "a backup"}).encode("utf-8")
|
bad_data = b"not a database file at all, just random text content"
|
||||||
resp = client.post(
|
resp = client.post(
|
||||||
"/api/v1/system/restore",
|
"/api/v1/system/restore",
|
||||||
files={"file": ("bad.json", io.BytesIO(bad_data), "application/json")},
|
files={"file": ("bad.db", io.BytesIO(bad_data), "application/octet-stream")},
|
||||||
)
|
)
|
||||||
assert resp.status_code == 400
|
assert resp.status_code == 400
|
||||||
|
|
||||||
def test_restore_rejects_empty_stores(self, client):
|
def test_restore_rejects_empty_file(self, client):
|
||||||
"""A backup with no recognized stores should fail."""
|
"""A tiny file should fail validation."""
|
||||||
bad_backup = {
|
|
||||||
"meta": {"format": "ledgrab-backup", "format_version": 1},
|
|
||||||
"stores": {"unknown_store": {}},
|
|
||||||
}
|
|
||||||
resp = client.post(
|
resp = client.post(
|
||||||
"/api/v1/system/restore",
|
"/api/v1/system/restore",
|
||||||
files={"file": ("bad.json", io.BytesIO(json.dumps(bad_backup).encode()), "application/json")},
|
files={"file": ("tiny.db", io.BytesIO(b"x" * 50), "application/octet-stream")},
|
||||||
)
|
)
|
||||||
assert resp.status_code == 400
|
assert resp.status_code == 400
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ from wled_controller.storage.automation_store import AutomationStore
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def store(tmp_path) -> AutomationStore:
|
def store(tmp_db) -> AutomationStore:
|
||||||
return AutomationStore(str(tmp_path / "automations.json"))
|
return AutomationStore(tmp_db)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -240,16 +240,18 @@ class TestAutomationNameUniqueness:
|
|||||||
|
|
||||||
class TestAutomationPersistence:
|
class TestAutomationPersistence:
|
||||||
def test_persist_and_reload(self, tmp_path):
|
def test_persist_and_reload(self, tmp_path):
|
||||||
path = str(tmp_path / "auto_persist.json")
|
from wled_controller.storage.database import Database
|
||||||
s1 = AutomationStore(path)
|
db = Database(tmp_path / "auto_persist.db")
|
||||||
|
s1 = AutomationStore(db)
|
||||||
a = s1.create_automation(
|
a = s1.create_automation(
|
||||||
name="Persist",
|
name="Persist",
|
||||||
conditions=[WebhookCondition(token="t1")],
|
conditions=[WebhookCondition(token="t1")],
|
||||||
)
|
)
|
||||||
aid = a.id
|
aid = a.id
|
||||||
|
|
||||||
s2 = AutomationStore(path)
|
s2 = AutomationStore(db)
|
||||||
loaded = s2.get_automation(aid)
|
loaded = s2.get_automation(aid)
|
||||||
assert loaded.name == "Persist"
|
assert loaded.name == "Persist"
|
||||||
assert len(loaded.conditions) == 1
|
assert len(loaded.conditions) == 1
|
||||||
assert isinstance(loaded.conditions[0], WebhookCondition)
|
assert isinstance(loaded.conditions[0], WebhookCondition)
|
||||||
|
db.close()
|
||||||
|
|||||||
@@ -14,13 +14,16 @@ from wled_controller.storage.device_store import Device, DeviceStore
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def temp_storage(tmp_path) -> Path:
|
def tmp_db(tmp_path):
|
||||||
return tmp_path / "devices.json"
|
from wled_controller.storage.database import Database
|
||||||
|
db = Database(tmp_path / "test.db")
|
||||||
|
yield db
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def store(temp_storage) -> DeviceStore:
|
def store(tmp_db) -> DeviceStore:
|
||||||
return DeviceStore(temp_storage)
|
return DeviceStore(tmp_db)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -240,23 +243,29 @@ class TestDeviceNameUniqueness:
|
|||||||
|
|
||||||
|
|
||||||
class TestDevicePersistence:
|
class TestDevicePersistence:
|
||||||
def test_persistence_across_instances(self, temp_storage):
|
def test_persistence_across_instances(self, tmp_path):
|
||||||
s1 = DeviceStore(temp_storage)
|
from wled_controller.storage.database import Database
|
||||||
|
db = Database(tmp_path / "persist.db")
|
||||||
|
s1 = DeviceStore(db)
|
||||||
d = s1.create_device(name="Persist", url="http://p", led_count=77)
|
d = s1.create_device(name="Persist", url="http://p", led_count=77)
|
||||||
did = d.id
|
did = d.id
|
||||||
|
|
||||||
s2 = DeviceStore(temp_storage)
|
s2 = DeviceStore(db)
|
||||||
loaded = s2.get_device(did)
|
loaded = s2.get_device(did)
|
||||||
assert loaded.name == "Persist"
|
assert loaded.name == "Persist"
|
||||||
assert loaded.led_count == 77
|
assert loaded.led_count == 77
|
||||||
|
db.close()
|
||||||
|
|
||||||
def test_update_persists(self, temp_storage):
|
def test_update_persists(self, tmp_path):
|
||||||
s1 = DeviceStore(temp_storage)
|
from wled_controller.storage.database import Database
|
||||||
|
db = Database(tmp_path / "persist2.db")
|
||||||
|
s1 = DeviceStore(db)
|
||||||
d = s1.create_device(name="Before", url="http://x", led_count=10)
|
d = s1.create_device(name="Before", url="http://x", led_count=10)
|
||||||
s1.update_device(d.id, name="After")
|
s1.update_device(d.id, name="After")
|
||||||
|
|
||||||
s2 = DeviceStore(temp_storage)
|
s2 = DeviceStore(db)
|
||||||
assert s2.get_device(d.id).name == "After"
|
assert s2.get_device(d.id).name == "After"
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -266,7 +275,9 @@ class TestDevicePersistence:
|
|||||||
|
|
||||||
class TestDeviceThreadSafety:
|
class TestDeviceThreadSafety:
|
||||||
def test_concurrent_creates(self, tmp_path):
|
def test_concurrent_creates(self, tmp_path):
|
||||||
s = DeviceStore(tmp_path / "conc.json")
|
from wled_controller.storage.database import Database
|
||||||
|
db = Database(tmp_path / "conc.db")
|
||||||
|
s = DeviceStore(db)
|
||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
def _create(i):
|
def _create(i):
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ from wled_controller.storage.key_colors_output_target import (
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def store(tmp_path) -> OutputTargetStore:
|
def store(tmp_db) -> OutputTargetStore:
|
||||||
return OutputTargetStore(str(tmp_path / "output_targets.json"))
|
return OutputTargetStore(tmp_db)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -193,8 +193,10 @@ class TestOutputTargetQueries:
|
|||||||
|
|
||||||
class TestOutputTargetPersistence:
|
class TestOutputTargetPersistence:
|
||||||
def test_persist_and_reload(self, tmp_path):
|
def test_persist_and_reload(self, tmp_path):
|
||||||
path = str(tmp_path / "ot_persist.json")
|
from wled_controller.storage.database import Database
|
||||||
s1 = OutputTargetStore(path)
|
db_path = str(tmp_path / "ot_persist.db")
|
||||||
|
db = Database(db_path)
|
||||||
|
s1 = OutputTargetStore(db)
|
||||||
t = s1.create_target(
|
t = s1.create_target(
|
||||||
"Persist", "led",
|
"Persist", "led",
|
||||||
device_id="dev_1",
|
device_id="dev_1",
|
||||||
@@ -203,8 +205,9 @@ class TestOutputTargetPersistence:
|
|||||||
)
|
)
|
||||||
tid = t.id
|
tid = t.id
|
||||||
|
|
||||||
s2 = OutputTargetStore(path)
|
s2 = OutputTargetStore(db)
|
||||||
loaded = s2.get_target(tid)
|
loaded = s2.get_target(tid)
|
||||||
assert loaded.name == "Persist"
|
assert loaded.name == "Persist"
|
||||||
assert isinstance(loaded, WledOutputTarget)
|
assert isinstance(loaded, WledOutputTarget)
|
||||||
assert loaded.tags == ["tv"]
|
assert loaded.tags == ["tv"]
|
||||||
|
db.close()
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ from wled_controller.storage.sync_clock_store import SyncClockStore
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def store(tmp_path) -> SyncClockStore:
|
def store(tmp_db) -> SyncClockStore:
|
||||||
return SyncClockStore(str(tmp_path / "sync_clocks.json"))
|
return SyncClockStore(tmp_db)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -149,12 +149,14 @@ class TestSyncClockNameUniqueness:
|
|||||||
|
|
||||||
class TestSyncClockPersistence:
|
class TestSyncClockPersistence:
|
||||||
def test_persist_and_reload(self, tmp_path):
|
def test_persist_and_reload(self, tmp_path):
|
||||||
path = str(tmp_path / "sc_persist.json")
|
from wled_controller.storage.database import Database
|
||||||
s1 = SyncClockStore(path)
|
db = Database(tmp_path / "sc_persist.db")
|
||||||
|
s1 = SyncClockStore(db)
|
||||||
c = s1.create_clock(name="Persist", speed=2.5)
|
c = s1.create_clock(name="Persist", speed=2.5)
|
||||||
cid = c.id
|
cid = c.id
|
||||||
|
|
||||||
s2 = SyncClockStore(path)
|
s2 = SyncClockStore(db)
|
||||||
loaded = s2.get_clock(cid)
|
loaded = s2.get_clock(cid)
|
||||||
assert loaded.name == "Persist"
|
assert loaded.name == "Persist"
|
||||||
assert loaded.speed == 2.5
|
assert loaded.speed == 2.5
|
||||||
|
db.close()
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ from wled_controller.storage.value_source_store import ValueSourceStore
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def store(tmp_path) -> ValueSourceStore:
|
def store(tmp_db) -> ValueSourceStore:
|
||||||
return ValueSourceStore(str(tmp_path / "value_sources.json"))
|
return ValueSourceStore(tmp_db)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -247,13 +247,15 @@ class TestValueSourceNameUniqueness:
|
|||||||
|
|
||||||
class TestValueSourcePersistence:
|
class TestValueSourcePersistence:
|
||||||
def test_persist_and_reload(self, tmp_path):
|
def test_persist_and_reload(self, tmp_path):
|
||||||
path = str(tmp_path / "vs_persist.json")
|
from wled_controller.storage.database import Database
|
||||||
s1 = ValueSourceStore(path)
|
db = Database(tmp_path / "vs_persist.db")
|
||||||
|
s1 = ValueSourceStore(db)
|
||||||
src = s1.create_source("Persist", "static", value=0.42)
|
src = s1.create_source("Persist", "static", value=0.42)
|
||||||
sid = src.id
|
sid = src.id
|
||||||
|
|
||||||
s2 = ValueSourceStore(path)
|
s2 = ValueSourceStore(db)
|
||||||
loaded = s2.get_source(sid)
|
loaded = s2.get_source(sid)
|
||||||
assert loaded.name == "Persist"
|
assert loaded.name == "Persist"
|
||||||
assert isinstance(loaded, StaticValueSource)
|
assert isinstance(loaded, StaticValueSource)
|
||||||
assert loaded.value == 0.42
|
assert loaded.value == 0.42
|
||||||
|
db.close()
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ class TestDefaultConfig:
|
|||||||
|
|
||||||
def test_default_storage_paths(self):
|
def test_default_storage_paths(self):
|
||||||
config = Config()
|
config = Config()
|
||||||
assert config.storage.devices_file == "data/devices.json"
|
assert config.storage.database_file == "data/ledgrab.db"
|
||||||
assert config.storage.sync_clocks_file == "data/sync_clocks.json"
|
|
||||||
|
|
||||||
def test_default_mqtt_disabled(self):
|
def test_default_mqtt_disabled(self):
|
||||||
config = Config()
|
config = Config()
|
||||||
@@ -73,12 +72,11 @@ class TestServerConfig:
|
|||||||
class TestDemoMode:
|
class TestDemoMode:
|
||||||
def test_demo_rewrites_storage_paths(self):
|
def test_demo_rewrites_storage_paths(self):
|
||||||
config = Config(demo=True)
|
config = Config(demo=True)
|
||||||
assert config.storage.devices_file.startswith("data/demo/")
|
assert config.storage.database_file.startswith("data/demo/")
|
||||||
assert config.storage.sync_clocks_file.startswith("data/demo/")
|
|
||||||
|
|
||||||
def test_non_demo_keeps_original_paths(self):
|
def test_non_demo_keeps_original_paths(self):
|
||||||
config = Config(demo=False)
|
config = Config(demo=False)
|
||||||
assert config.storage.devices_file == "data/devices.json"
|
assert config.storage.database_file == "data/ledgrab.db"
|
||||||
|
|
||||||
|
|
||||||
class TestGlobalConfig:
|
class TestGlobalConfig:
|
||||||
|
|||||||
@@ -2,19 +2,22 @@
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from wled_controller.storage.database import Database
|
||||||
from wled_controller.storage.device_store import Device, DeviceStore
|
from wled_controller.storage.device_store import Device, DeviceStore
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def temp_storage(tmp_path):
|
def tmp_db(tmp_path):
|
||||||
"""Provide temporary storage file."""
|
"""Provide a temporary SQLite Database instance."""
|
||||||
return tmp_path / "devices.json"
|
db = Database(tmp_path / "test.db")
|
||||||
|
yield db
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def device_store(temp_storage):
|
def device_store(tmp_db):
|
||||||
"""Provide device store instance."""
|
"""Provide device store instance."""
|
||||||
return DeviceStore(temp_storage)
|
return DeviceStore(tmp_db)
|
||||||
|
|
||||||
|
|
||||||
def test_device_creation():
|
def test_device_creation():
|
||||||
@@ -207,10 +210,11 @@ def test_device_exists(device_store):
|
|||||||
assert device_store.device_exists("nonexistent") is False
|
assert device_store.device_exists("nonexistent") is False
|
||||||
|
|
||||||
|
|
||||||
def test_persistence(temp_storage):
|
def test_persistence(tmp_path):
|
||||||
"""Test device persistence across store instances."""
|
"""Test device persistence across store instances."""
|
||||||
|
db = Database(tmp_path / "persist.db")
|
||||||
# Create store and add device
|
# Create store and add device
|
||||||
store1 = DeviceStore(temp_storage)
|
store1 = DeviceStore(db)
|
||||||
device = store1.create_device(
|
device = store1.create_device(
|
||||||
name="Test WLED",
|
name="Test WLED",
|
||||||
url="http://192.168.1.100",
|
url="http://192.168.1.100",
|
||||||
@@ -218,14 +222,15 @@ def test_persistence(temp_storage):
|
|||||||
)
|
)
|
||||||
device_id = device.id
|
device_id = device.id
|
||||||
|
|
||||||
# Create new store instance (loads from file)
|
# Create new store instance (loads from database)
|
||||||
store2 = DeviceStore(temp_storage)
|
store2 = DeviceStore(db)
|
||||||
|
|
||||||
# Verify device persisted
|
# Verify device persisted
|
||||||
loaded_device = store2.get_device(device_id)
|
loaded_device = store2.get_device(device_id)
|
||||||
assert loaded_device is not None
|
assert loaded_device is not None
|
||||||
assert loaded_device.name == "Test WLED"
|
assert loaded_device.name == "Test WLED"
|
||||||
assert loaded_device.led_count == 150
|
assert loaded_device.led_count == 150
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
def test_clear(device_store):
|
def test_clear(device_store):
|
||||||
|
|||||||
Reference in New Issue
Block a user