fix: close Human Gates review races
This commit is contained in:
1 parent
dbb8ff4d4e
commit
9d84d5c5c4
5 files changed
+89
-18
No files matched your search
@@ -115,6 +115,25 @@ def test_same_hash_update_coalesces_one_card_and_preserves_history(tmp_path):
|
||||
assert store.list("timmy")["pending_count"] == 1
|
||||
|
||||
|
||||
def test_updated_checks_reopen_a_released_hash_for_review(tmp_path):
|
||||
store = HumanGateStore(tmp_path / "gates.sqlite3", clock=iter([100, 101, 102]).__next__)
|
||||
gate = store.intake("timmy", candidate(), idempotency_key="run-1")
|
||||
store.decide(
|
||||
"timmy", gate["id"], expected_revision=1, decision="release",
|
||||
reason="", override_reason="", checklist=checklist(), idempotency_key="decision-1",
|
||||
)
|
||||
|
||||
reopened = store.intake(
|
||||
"timmy", candidate(check_state="failure"), idempotency_key="run-2",
|
||||
)
|
||||
|
||||
assert reopened["state"] == "pending"
|
||||
assert reopened["revision"] == 3
|
||||
assert reopened["checks"][0]["state"] == "failure"
|
||||
assert reopened["history"][-1]["action"] == "reopened"
|
||||
assert store.list("timmy")["pending_count"] == 1
|
||||
|
||||
|
||||
def test_same_hash_identity_facts_cannot_be_redefined(tmp_path):
|
||||
store = HumanGateStore(tmp_path / "gates.sqlite3", clock=lambda: 100)
|
||||
store.intake("timmy", candidate(), idempotency_key="run-1")
|
||||
|
||||
@@ -82,6 +82,18 @@ const gates=createHumanGates({storage,getLogin:()=> 'timmy',getAccountKey:()=>ac
|
||||
assert output["restored"] is None
|
||||
|
||||
|
||||
def test_account_switch_clears_in_memory_gate_data_before_failed_load():
|
||||
output = run_node(r"""
|
||||
const values=new Map(); const storage={getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)};
|
||||
let account='1:timmy', fail=false; const nodes={count:{},list:{innerHTML:''},status:{},panel:{}};
|
||||
const gates=createHumanGates({storage,getLogin:()=> 'timmy',getAccountKey:()=>account,isOnline:()=>true,nodes,location:{hash:''},fetchJson:async()=>{if(fail)throw new Error('offline');return {pending_count:1,items:[{id:'private-1',title:'Principal 1 private',candidate_hash:'secret'}]}}});
|
||||
(async()=>{await gates.load();account='2:timmy';fail=true;try{await gates.load()}catch(_){}process.stdout.write(JSON.stringify({snapshot:gates.snapshot(),html:nodes.list.innerHTML}));})();
|
||||
""")
|
||||
assert output["snapshot"] == {"pending_count": 0, "items": []}
|
||||
assert "Principal 1 private" not in output["html"]
|
||||
assert "secret" not in output["html"]
|
||||
|
||||
|
||||
def test_decision_retry_reuses_the_same_idempotency_key():
|
||||
output = run_node(r"""
|
||||
let attempts=0; const keys=[];
|
||||
@@ -93,6 +105,19 @@ const gates=createHumanGates({storage:{getItem:()=>null,setItem(){}},getLogin:()
|
||||
assert output["keys"][0] == output["keys"][1]
|
||||
|
||||
|
||||
def test_concurrent_decision_taps_submit_once_and_advance_once():
|
||||
output = run_node(r"""
|
||||
let posts=0, releasePost; const posted=new Promise(resolve=>releasePost=resolve);
|
||||
const items=[{id:'g1',title:'One',candidate_hash:'a1',revision:1,checks:[]},{id:'g2',title:'Two',candidate_hash:'b2',revision:1,checks:[]}];
|
||||
const gates=createHumanGates({storage:{getItem:()=>null,setItem(){}},getLogin:()=> 'timmy',isOnline:()=>true,nodes:{count:{},list:{},status:{},panel:{},detail:{}},location:{hash:''},fetchJson:async(path,options={})=>{if(options.method==='POST'){posts++;await posted;return {receipt_id:'r1'}};return {pending_count:2,items};}});
|
||||
(async()=>{await gates.load();gates.reviewNext();const values={reason:'wait',checklist:{}};const first=gates.decideAndNext('hold',values);const second=gates.decideAndNext('hold',values);releasePost();await Promise.all([first,second]);process.stdout.write(JSON.stringify({posts,current:gates.current()?.id,snapshot:gates.snapshot()}));})();
|
||||
""")
|
||||
assert output["posts"] == 1
|
||||
assert output["current"] == "g2"
|
||||
assert output["snapshot"]["pending_count"] == 1
|
||||
assert [item["id"] for item in output["snapshot"]["items"]] == ["g2"]
|
||||
|
||||
|
||||
def test_selecting_a_queue_card_opens_that_exact_gate():
|
||||
output = run_node(r"""
|
||||
const details={
|
||||
@@ -117,3 +142,4 @@ def test_human_gate_mobile_shell_and_deep_route_are_wired():
|
||||
assert 'static/human-gates.js' in index
|
||||
assert "#/my-work/human-gates" in dashboard
|
||||
assert "createHumanGates" in dashboard
|
||||
assert "planningOwnerAccountKey = confirmedOwnerLogin && saved.user?.id" in dashboard
|
||||
Reference in new issue
Block a user