mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-03 04:49:26 +00:00
112 lines
3.7 KiB
Python
112 lines
3.7 KiB
Python
"""Event-store core: append-only doctrine, hash chain, deliveries, spaces, rebuild."""
|
|
|
|
import sqlite3
|
|
|
|
import pytest
|
|
|
|
from coworker.teams import Actor, ChainError, Role, TeamStore
|
|
|
|
USER = Actor(id="user", role=Role.USER)
|
|
LEAD = Actor(id="lead-1", role=Role.LEAD, persona="swe-lead")
|
|
|
|
|
|
@pytest.fixture
|
|
def store(tmp_path):
|
|
store = TeamStore(tmp_path / "teams.db")
|
|
yield store
|
|
store.close()
|
|
|
|
|
|
def seed(store, space="proj"):
|
|
return store.create_item(
|
|
space, LEAD, title="Review api", criteria="every route triaged"
|
|
)
|
|
|
|
|
|
def test_events_hash_chain_verifies(store):
|
|
seed(store)
|
|
store.create_item("proj", LEAD, title="Second", criteria="done means done")
|
|
assert store.verify_chain("proj") == 2
|
|
|
|
|
|
def test_out_of_band_edit_breaks_the_chain(store):
|
|
seed(store)
|
|
# The append-only property can't be enforced against someone with the sqlite
|
|
# file; the chain makes the edit visible. Tamper directly:
|
|
conn = sqlite3.connect(store.db_path)
|
|
conn.execute("UPDATE team_events SET payload = '{\"title\":\"forged\"}' WHERE seq = 1")
|
|
conn.commit()
|
|
conn.close()
|
|
with pytest.raises(ChainError):
|
|
store.verify_chain("proj")
|
|
|
|
|
|
def test_deleting_an_event_breaks_linkage(store):
|
|
seed(store)
|
|
store.create_item("proj", LEAD, title="Second", criteria="c")
|
|
conn = sqlite3.connect(store.db_path)
|
|
conn.execute("DELETE FROM team_events WHERE seq = 2")
|
|
conn.commit()
|
|
conn.close()
|
|
with pytest.raises(ChainError):
|
|
store.verify_chain("proj")
|
|
|
|
|
|
def test_chains_are_per_space(store):
|
|
seed(store, "alpha")
|
|
seed(store, "beta")
|
|
conn = sqlite3.connect(store.db_path)
|
|
conn.execute("UPDATE team_events SET taint = 1 WHERE space = 'beta'")
|
|
conn.commit()
|
|
conn.close()
|
|
assert store.verify_chain("alpha") == 1 # untouched space still verifies
|
|
with pytest.raises(ChainError):
|
|
store.verify_chain("beta")
|
|
|
|
|
|
def test_spaces_created_lazily_and_isolated(store):
|
|
assert store.spaces() == []
|
|
seed(store, "alpha")
|
|
seed(store, "beta")
|
|
assert store.spaces() == ["alpha", "beta"]
|
|
assert [i["id"] for i in store.list_items("alpha", USER)] == [1] # ids per space
|
|
assert [i["id"] for i in store.list_items("beta", USER)] == [1]
|
|
|
|
|
|
def test_assignment_lands_in_the_assignees_feed(store):
|
|
item = seed(store)
|
|
store.assign("proj", LEAD, item["id"], "worker-1")
|
|
deliveries = store.feed_for("proj", "worker-1")
|
|
assert [e["kind"] for e in deliveries] == ["item_created", "item_assigned"]
|
|
assert deliveries[-1]["payload"]["assignee"] == "worker-1"
|
|
assert store.feed_for("proj", "worker-2") == []
|
|
|
|
|
|
def test_rebuild_reproduces_the_projection(store):
|
|
item = seed(store)
|
|
worker = Actor(id="worker-1", role=Role.WORKER)
|
|
store.assign("proj", LEAD, item["id"], "worker-1")
|
|
store.transition("proj", worker, item["id"], "in_progress")
|
|
store.comment("proj", worker, item["id"], "halfway", taint=True)
|
|
before = store.list_items("proj", USER)
|
|
store.rebuild("proj")
|
|
after = store.list_items("proj", USER)
|
|
assert after == before
|
|
assert after[0]["state"] == "in_progress"
|
|
assert after[0]["assignee"] == "worker-1"
|
|
# created_ts comes from the event, so replay is deterministic
|
|
assert (
|
|
store.get_item("proj", item["id"], actor=USER)["created_ts"]
|
|
== item["created_ts"]
|
|
)
|
|
|
|
|
|
def test_taint_travels_with_the_record(store):
|
|
item = seed(store)
|
|
store.assign("proj", LEAD, item["id"], "worker-1")
|
|
worker = Actor(id="worker-1", role=Role.WORKER)
|
|
store.comment("proj", worker, item["id"], "repo says the bucket is public", taint=True)
|
|
comments = store.comments("proj", item["id"])
|
|
assert comments[-1]["taint"] == 1
|
|
assert comments[-1]["role"] == "worker"
|