This commit is contained in:
1 parent
4db3f45164
commit
d25c04cdd2
175 files changed
+56243
-15
No files matched your search
@@ -0,0 +1,92 @@
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from pathlib import Path
|
||||
from threading import Barrier
|
||||
|
||||
import pytest
|
||||
|
||||
from gewu.adapters.git_repository import (
|
||||
GitCanonicalRepository,
|
||||
GitConflictError,
|
||||
GitRepositoryError,
|
||||
)
|
||||
|
||||
|
||||
def test_bare_repository_commits_only_explicit_files_and_checks_expected_head(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
repository = GitCanonicalRepository(tmp_path / "private" / "knowledge.git")
|
||||
base = repository.head()
|
||||
record_path = "projects/project-a/nodes/node-a.yaml"
|
||||
commit = repository.publish_files(
|
||||
expected_head=base,
|
||||
files={record_path: b"title: accepted\n"},
|
||||
message="Accept fixture",
|
||||
index_directory=tmp_path / "staging" / "publishing",
|
||||
)
|
||||
assert repository.read_file(commit, record_path) == b"title: accepted\n"
|
||||
assert repository.list_files(commit, "projects") == (record_path,)
|
||||
assert repository.list_files(commit, "sources") == ()
|
||||
assert repository.read_file(commit, "unrelated/user-file.txt") is None
|
||||
assert repository.introducing_commit(commit, record_path) == commit
|
||||
|
||||
with pytest.raises(GitConflictError, match="Canonical branch changed"):
|
||||
repository.publish_files(
|
||||
expected_head=base,
|
||||
files={"projects/project-b/nodes/node-b.yaml": b"title: stale\n"},
|
||||
message="Stale writer",
|
||||
index_directory=tmp_path / "staging" / "publishing",
|
||||
)
|
||||
|
||||
|
||||
def test_record_paths_reject_traversal_and_noncanonical_separators(tmp_path: Path) -> None:
|
||||
repository = GitCanonicalRepository(tmp_path / "knowledge.git")
|
||||
for unsafe in ("../escape", "/absolute", "projects//node", "projects/./node", "a\\b"):
|
||||
with pytest.raises(GitRepositoryError, match="record path is invalid"):
|
||||
repository.read_file(repository.head(), unsafe)
|
||||
|
||||
|
||||
def test_independent_repository_adapters_allow_one_compare_and_swap_winner(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
repository_path = tmp_path / "knowledge.git"
|
||||
first_adapter = GitCanonicalRepository(repository_path)
|
||||
second_adapter = GitCanonicalRepository(repository_path)
|
||||
base = first_adapter.head()
|
||||
start = Barrier(2)
|
||||
index_directory = tmp_path / "staging" / "publishing"
|
||||
|
||||
def publish(repository: GitCanonicalRepository, name: str) -> tuple[str, str | None]:
|
||||
path = f"projects/{name}/nodes/node.yaml"
|
||||
start.wait(timeout=5)
|
||||
try:
|
||||
revision = repository.publish_files(
|
||||
expected_head=base,
|
||||
files={path: f"title: {name}\n".encode()},
|
||||
message=f"Publish {name}",
|
||||
index_directory=index_directory,
|
||||
)
|
||||
except GitConflictError:
|
||||
return name, None
|
||||
return name, revision
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as workers:
|
||||
results = list(
|
||||
workers.map(
|
||||
lambda args: publish(*args),
|
||||
((first_adapter, "project-a"), (second_adapter, "project-b")),
|
||||
)
|
||||
)
|
||||
|
||||
winners = [(name, revision) for name, revision in results if revision is not None]
|
||||
losers = [name for name, revision in results if revision is None]
|
||||
assert len(winners) == len(losers) == 1
|
||||
winner, winning_revision = winners[0]
|
||||
assert winning_revision is not None
|
||||
assert first_adapter.head() == winning_revision
|
||||
assert second_adapter.head() == winning_revision
|
||||
assert (
|
||||
first_adapter.read_file(winning_revision, f"projects/{winner}/nodes/node.yaml")
|
||||
== f"title: {winner}\n".encode()
|
||||
)
|
||||
loser = losers[0]
|
||||
assert first_adapter.read_file(winning_revision, f"projects/{loser}/nodes/node.yaml") is None
|
||||
Reference in new issue
Block a user