feat: operationalise project bus for Sandbox
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from project_bus.db import Database
|
||||
from project_bus.operations import OperationsError, backup_database, bootstrap_project, load_manifest
|
||||
|
||||
|
||||
class RecordingClient:
|
||||
def __init__(self):
|
||||
self.calls = []
|
||||
|
||||
def call(self, name, arguments):
|
||||
self.calls.append((name, arguments))
|
||||
return {"name": name, "idempotency_key": arguments["idempotency_key"]}
|
||||
|
||||
|
||||
class OperationsTests(unittest.TestCase):
|
||||
def manifest(self):
|
||||
return {
|
||||
"project": {
|
||||
"project_id": "sandbox",
|
||||
"name": "Sandbox",
|
||||
"description": "test",
|
||||
},
|
||||
"actors": [
|
||||
{
|
||||
"actor_id": "po",
|
||||
"auth_subject": "po",
|
||||
"display_name": "PO",
|
||||
"role": "PO",
|
||||
},
|
||||
{
|
||||
"actor_id": "reviewer",
|
||||
"auth_subject": "reviewer",
|
||||
"display_name": "Reviewer",
|
||||
"role": "REVIEWER",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
def test_bootstrap_uses_stable_idempotency_keys(self):
|
||||
first = RecordingClient()
|
||||
second = RecordingClient()
|
||||
bootstrap_project(first, self.manifest())
|
||||
bootstrap_project(second, self.manifest())
|
||||
self.assertEqual(first.calls, second.calls)
|
||||
self.assertEqual(first.calls[0][0], "create_project")
|
||||
self.assertEqual([call[0] for call in first.calls[1:]], ["register_actor", "register_actor"])
|
||||
|
||||
def test_manifest_rejects_system_and_duplicate_subjects(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = Path(directory) / "manifest.json"
|
||||
invalid = self.manifest()
|
||||
invalid["actors"][0]["role"] = "SYSTEM"
|
||||
path.write_text(json.dumps(invalid), encoding="utf-8")
|
||||
with self.assertRaises(OperationsError):
|
||||
load_manifest(path)
|
||||
invalid = self.manifest()
|
||||
invalid["actors"][1]["auth_subject"] = "po"
|
||||
path.write_text(json.dumps(invalid), encoding="utf-8")
|
||||
with self.assertRaises(OperationsError):
|
||||
load_manifest(path)
|
||||
|
||||
def test_backup_is_consistent_and_integrity_checked(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
source = Path(directory) / "source.db"
|
||||
destination = Path(directory) / "backup.db"
|
||||
Database(source).migrate()
|
||||
with sqlite3.connect(source) as connection:
|
||||
connection.execute(
|
||||
"""
|
||||
INSERT INTO projects(project_id, name, description, created_at)
|
||||
VALUES ('sandbox', 'Sandbox', '', '2026-07-30T00:00:00Z')
|
||||
"""
|
||||
)
|
||||
connection.execute(
|
||||
"""
|
||||
INSERT INTO actors(actor_id, auth_subject, display_name, created_at)
|
||||
VALUES ('system', 'system', 'System', '2026-07-30T00:00:00Z')
|
||||
"""
|
||||
)
|
||||
connection.execute(
|
||||
"""
|
||||
INSERT INTO event_log(
|
||||
event_id, project_id, event_type, actor_id, actor_role,
|
||||
aggregate_type, aggregate_id, payload_json, created_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
"evt_test",
|
||||
"sandbox",
|
||||
"test.created",
|
||||
"system",
|
||||
"SYSTEM",
|
||||
"test",
|
||||
"test-1",
|
||||
"{}",
|
||||
"2026-07-30T00:00:00Z",
|
||||
),
|
||||
)
|
||||
receipt = backup_database(source, destination)
|
||||
self.assertEqual(receipt["integrity"], "ok")
|
||||
self.assertEqual(receipt["event_cursor"], 1)
|
||||
with sqlite3.connect(destination) as connection:
|
||||
self.assertEqual(
|
||||
connection.execute("SELECT COUNT(*) FROM event_log").fetchone()[0],
|
||||
1,
|
||||
)
|
||||
|
||||
def test_backup_refuses_in_place_destination(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
source = Path(directory) / "source.db"
|
||||
source.touch()
|
||||
with self.assertRaises(OperationsError):
|
||||
backup_database(source, source)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user