feat(client): add durable HTTP task state
This commit is contained in:
@@ -2,16 +2,18 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
|
||||
CLIENT_ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(CLIENT_ROOT / "src"))
|
||||
|
||||
from cmbuyer_client.runtime import RuntimePaths
|
||||
from cmbuyer_client.runtime import LocalStateRuntime, RuntimePaths
|
||||
|
||||
|
||||
class RuntimePathsTests(unittest.TestCase):
|
||||
@@ -23,3 +25,58 @@ class RuntimePathsTests(unittest.TestCase):
|
||||
|
||||
self.assertTrue(paths.logs.is_dir())
|
||||
self.assertTrue(paths.artifacts.is_dir())
|
||||
self.assertTrue(paths.state.is_dir())
|
||||
self.assertEqual(paths.database, paths.state / "client-state.sqlite3")
|
||||
|
||||
def test_localstate_runtime_acquires_mutex_before_protector_and_store(self) -> None:
|
||||
events: list[str] = []
|
||||
|
||||
class Mutex:
|
||||
def __init__(self, path: Path) -> None:
|
||||
events.append("mutex")
|
||||
|
||||
def close(self) -> None:
|
||||
events.append("close")
|
||||
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
runtime = LocalStateRuntime.open(
|
||||
RuntimePaths.from_root(Path(directory)),
|
||||
mutex_factory=Mutex,
|
||||
protector_factory=lambda: events.append("protector") or object(),
|
||||
store_factory=lambda path, protector: events.append("store") or object(),
|
||||
)
|
||||
runtime.close()
|
||||
self.assertEqual(events, ["mutex", "protector", "store", "close"])
|
||||
|
||||
def test_localstate_runtime_releases_mutex_if_open_fails(self) -> None:
|
||||
events: list[str] = []
|
||||
|
||||
class Mutex:
|
||||
def __init__(self, path: Path) -> None:
|
||||
events.append("mutex")
|
||||
|
||||
def close(self) -> None:
|
||||
events.append("close")
|
||||
|
||||
def fail() -> object:
|
||||
raise RuntimeError("failed")
|
||||
|
||||
with tempfile.TemporaryDirectory() as directory, self.assertRaises(RuntimeError):
|
||||
LocalStateRuntime.open(
|
||||
RuntimePaths.from_root(Path(directory)),
|
||||
mutex_factory=Mutex,
|
||||
protector_factory=fail,
|
||||
)
|
||||
self.assertEqual(events, ["mutex", "close"])
|
||||
|
||||
def test_windows_without_localappdata_fails_instead_of_creating_second_database(self) -> None:
|
||||
with mock.patch("cmbuyer_client.runtime.os.name", "nt"), mock.patch.dict(os.environ, {}, clear=True):
|
||||
with self.assertRaisesRegex(RuntimeError, "local_app_data_required"):
|
||||
RuntimePaths.default()
|
||||
|
||||
def test_runtime_root_is_frozen_absolute_and_relative_localappdata_is_rejected(self) -> None:
|
||||
paths = RuntimePaths.from_root(Path("relative-runtime"))
|
||||
self.assertTrue(paths.root.is_absolute())
|
||||
with mock.patch.dict(os.environ, {"LOCALAPPDATA": "relative-local-app-data"}, clear=True):
|
||||
with self.assertRaisesRegex(RuntimeError, "local_app_data_must_be_absolute"):
|
||||
RuntimePaths.default()
|
||||
|
||||
Reference in New Issue
Block a user