Add gated changeset application and graph controls
This commit is contained in:
parent
3c15e26283
commit
78335c8973
20 changed files with 1813 additions and 453 deletions
|
|
@ -10,6 +10,7 @@ from pathlib import Path
|
|||
from typing import Any
|
||||
from unittest import mock
|
||||
|
||||
from docforge.application import CanonicalApplicationService, GenericCanonicalApplier
|
||||
from docforge.changesets import ChangesetStore
|
||||
from docforge.errors import DocForgeError
|
||||
from docforge.project import Project
|
||||
|
|
@ -182,6 +183,91 @@ class DocForgeChangesetTests(unittest.TestCase):
|
|||
self.assertEqual("delete", delete_store.diff("delete-node")["changes"][0]["operation"])
|
||||
self.assertEqual(delete_before, self.canonical_digest(delete_root))
|
||||
|
||||
def test_hash_bound_application_writes_projection_and_refreshes_derived_state(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = self.copy_fixture(Path(directory))
|
||||
project = Project.open(root)
|
||||
store = ChangesetStore(project, "alpha-editor")
|
||||
hashes = {node.node_id: node.content_hash for node in project.load().nodes}
|
||||
created = store.create("apply-all")
|
||||
updated = store.propose_update(
|
||||
changeset_id="apply-all",
|
||||
expected_changeset_hash=created["changeset_hash"],
|
||||
node_id="guide.workflow",
|
||||
expected_content_hash=hashes["guide.workflow"],
|
||||
metadata={"summary": "Applied through the canonical application service."},
|
||||
content="The applied workflow is now canonical.",
|
||||
relationship_changes=[],
|
||||
rationale="Exercise a canonical update.",
|
||||
)
|
||||
added = store.propose_create(
|
||||
changeset_id="apply-all",
|
||||
expected_changeset_hash=updated["changeset_hash"],
|
||||
node_id="guide.applied",
|
||||
target_source="docs/content/applied.md",
|
||||
metadata=self.new_metadata(),
|
||||
content="This node was created by an approved changeset.",
|
||||
relationship_changes=[],
|
||||
rationale="Exercise a canonical creation.",
|
||||
)
|
||||
moved = store.propose_move(
|
||||
changeset_id="apply-all",
|
||||
expected_changeset_hash=added["changeset_hash"],
|
||||
node_id="guide.foundation",
|
||||
expected_content_hash=hashes["guide.foundation"],
|
||||
target_source="docs/content/foundation-moved.md",
|
||||
rationale="Exercise a canonical move.",
|
||||
)
|
||||
final = store.propose_delete(
|
||||
changeset_id="apply-all",
|
||||
expected_changeset_hash=moved["changeset_hash"],
|
||||
node_id="proof.validation",
|
||||
expected_content_hash=hashes["proof.validation"],
|
||||
relationship_changes=[
|
||||
{
|
||||
"action": "remove",
|
||||
"source_id": "proof.validation",
|
||||
"relation": "proves",
|
||||
"target_id": "guide.workflow",
|
||||
}
|
||||
],
|
||||
rationale="Exercise a canonical deletion.",
|
||||
)
|
||||
service = CanonicalApplicationService(
|
||||
project,
|
||||
applier_id="alpha-editor",
|
||||
applier=GenericCanonicalApplier(project),
|
||||
)
|
||||
result = service.apply("apply-all", str(final["changeset_hash"]))
|
||||
|
||||
snapshot = project.load()
|
||||
node_ids = {node.node_id for node in snapshot.nodes}
|
||||
workflow = next(node for node in snapshot.nodes if node.node_id == "guide.workflow")
|
||||
self.assertTrue(result["applied"])
|
||||
self.assertEqual(
|
||||
[
|
||||
"docs/content/applied.md",
|
||||
"docs/content/foundation-moved.md",
|
||||
"docs/content/foundation.md",
|
||||
"docs/content/proof.toml",
|
||||
"docs/content/workflow.md",
|
||||
],
|
||||
result["applied_sources"],
|
||||
)
|
||||
self.assertEqual({"guide.applied", "guide.foundation", "guide.workflow"}, node_ids)
|
||||
self.assertEqual(
|
||||
"Applied through the canonical application service.",
|
||||
workflow.summary,
|
||||
)
|
||||
self.assertFalse((root / "docs/content/foundation.md").exists())
|
||||
self.assertFalse((root / "docs/content/proof.toml").exists())
|
||||
self.assertTrue((root / "docs/content/foundation-moved.md").is_file())
|
||||
self.assertTrue((root / ".docforge/cache/index.sqlite3").is_file())
|
||||
self.assertTrue((root / ".docforge/rendered/manual.html").is_file())
|
||||
|
||||
with self.assertRaisesRegex(DocForgeError, "Canonical project changed"):
|
||||
service.apply("apply-all", str(final["changeset_hash"]))
|
||||
|
||||
def test_optimistic_and_cross_changeset_conflicts_preserve_both_proposals(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = self.copy_fixture(Path(directory))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue