from __future__ import annotations import json from datetime import UTC, datetime from pathlib import Path from uuid import uuid4 import pytest from app.storage import DATA_FORMAT_VERSION, StoredTask from app.storage.repository import JsonFileTaskRepository def make_task(title: str = "Task") -> StoredTask: return StoredTask( id=uuid4(), title=title, created_at=datetime.now(UTC), started_at=None, done_at=None, ) def test_interrupted_save_before_rename_keeps_original_data_intact(tmp_path, monkeypatch): file_path = tmp_path / "tasks.json" repo = JsonFileTaskRepository(file_path) original_task = make_task("Original") repo.create_task(original_task) replacement_called = False real_replace = Path.replace def failing_replace(self: Path, target: Path): nonlocal replacement_called replacement_called = True raise RuntimeError("simulated crash before rename") monkeypatch.setattr(Path, "replace", failing_replace) with pytest.raises(RuntimeError, match="simulated crash before rename"): repo.create_task(make_task("New task")) assert replacement_called is True reloaded_repo = JsonFileTaskRepository(file_path) assert reloaded_repo.list_tasks() == [original_task] tmp_file = tmp_path / "tasks.json.tmp" assert tmp_file.exists() monkeypatch.setattr(Path, "replace", real_replace) def test_normal_save_replaces_file_with_complete_payload(tmp_path): file_path = tmp_path / "tasks.json" repo = JsonFileTaskRepository(file_path) task_1 = make_task("One") task_2 = make_task("Two") repo.create_task(task_1) repo.create_task(task_2) payload = json.loads(file_path.read_text(encoding="utf-8")) assert payload["version"] == DATA_FORMAT_VERSION assert len(payload["tasks"]) == 2 assert {item["id"] for item in payload["tasks"]} == {str(task_1.id), str(task_2.id)} def test_stale_tmp_file_does_not_break_startup(tmp_path): file_path = tmp_path / "tasks.json" repo_1 = JsonFileTaskRepository(file_path) task = make_task("Persisted") repo_1.create_task(task) stale_tmp = tmp_path / "tasks.json.tmp" stale_tmp.write_text('{"version": 999, "tasks": "broken"}', encoding="utf-8") repo_2 = JsonFileTaskRepository(file_path) assert repo_2.list_tasks() == [task] assert stale_tmp.exists()