feat: withdraw delegated issues from Filed (Closes #884)
CI / lint (pull_request) Failing after 1m55s
CI / build-release (pull_request) Has been skipped
CI / browser-journey (pull_request) Has been skipped
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-15 10:00:46 +00:00
1 parent f01dfa5f93
commit 6a03dfd7ac
8 files changed
+159 -19

No files matched your search

+54
View File
@@ -2245,6 +2245,60 @@ async def test_issue_close_endpoint_mutates_assigned_issue_only_after_confirmati
assert calls == [("stackchain/api", 7)]
@pytest.mark.anyio
async def test_issue_close_endpoint_withdraws_only_open_authored_filed_issue(monkeypatch):
checks = []
calls = []
async def authored(repository, number):
checks.append((repository, number))
return True
async def close(repository, number):
calls.append((repository, number))
return {
"number": number,
"state": "closed",
"updated_at": "2026-08-15T10:00:00Z",
}
monkeypatch.setattr(main.gitea_proxy, "is_open_authored_issue", authored)
monkeypatch.setattr(main.gitea_proxy, "close_issue", close)
transport = httpx.ASGITransport(app=main.app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.patch(
"/api/v1/repos/stackchain/api/issues/7/close?access=filed"
)
assert response.status_code == 200
assert response.json()["state"] == "closed"
assert checks == [("stackchain/api", 7)]
assert calls == [("stackchain/api", 7)]
@pytest.mark.anyio
async def test_issue_close_endpoint_does_not_withdraw_non_authored_or_closed_filed_issue(monkeypatch):
close_calls = []
async def authored(_repository, _number):
return False
async def close(repository, number):
close_calls.append((repository, number))
return {"number": number, "state": "closed"}
monkeypatch.setattr(main.gitea_proxy, "is_open_authored_issue", authored)
monkeypatch.setattr(main.gitea_proxy, "close_issue", close)
transport = httpx.ASGITransport(app=main.app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.patch(
"/api/v1/repos/stackchain/api/issues/7/close?access=filed"
)
assert response.status_code == 404
assert close_calls == []
@pytest.mark.anyio
async def test_gitea_close_issue_patches_state_and_confirms_closed_response():
requests = []
+56 -4
View File
@@ -1557,7 +1557,7 @@ async def test_dashboard_opens_delegated_filings_with_follow_up_only_capabilitie
assert "#issue-sheet.read-only #issue-planning" in html
assert "#issue-sheet.read-only #issue-handoff" in html
assert "#issue-sheet.read-only #release-issue" in html
assert "#issue-sheet.read-only #close-issue" in html
assert "qs('#close-issue').hidden = readOnly && !withdrawable" in html
assert "#issue-sheet.read-only .detail-defer" in html
assert "if (readOnly) paintIssueConversation(issueConversation.snapshot(), null);" in html
assert "if (readOnly) qs('#load-older-issue-comments').hidden = true;" not in html
@@ -1834,6 +1834,57 @@ Promise.all([
]
def test_issue_sheet_withdraws_filed_item_through_author_access_as_single_flight():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const calls = [];
let finish;
const controller = createIssueSheet({{
fetchJson: (url, options={{}}) => {{
calls.push({{url, method:options.method}});
return new Promise(resolve => {{ finish = resolve; }});
}},
storage:null,
}});
const filed = {{kind:'filed',repository:'stackchain/api',number:18,is_filed:true,is_assigned:false}};
const first = controller.close(filed);
const duplicate = controller.close(filed);
finish({{number:18,state:'closed',updated_at:'2026-08-15T10:00:00Z'}});
Promise.all([first, duplicate]).then(results => process.stdout.write(JSON.stringify({{
calls, same:first === duplicate, state:results[0].state
}})));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"calls": [{
"url": "api/v1/repos/stackchain/api/issues/18/close?access=filed",
"method": "PATCH",
}],
"same": True,
"state": "closed",
}
@pytest.mark.anyio
async def test_mobile_filed_detail_exposes_only_a_withdrawal_action_for_open_delegated_issue():
source = await dashboard()
css = (ISSUE_SHEET.parent / "dashboard.css").read_text()
assert "const withdrawable = item.is_filed && !item.is_assigned && !item.is_completed && item.state === 'open';" in source
assert "qs('#close-issue').hidden = readOnly && !withdrawable" in source
assert "withdrawable ? 'Withdraw issue'" in source
close_handler = source.split("qs('#close-issue').addEventListener('click'", 1)[1].split(
"qs('#close-pull-sheet').addEventListener", 1
)[0]
assert "Withdraw " in close_handler
assert "closes the delegated request" in close_handler
assert "is_completed:true" in close_handler
assert "#issue-sheet.read-only #issue-planning" in css
assert ".issue-sheet-actions button" in css and "min-height:44px" in css
def test_issue_sheet_uses_author_access_for_filed_conversation_and_follow_up():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
@@ -3965,7 +4016,7 @@ async def test_closing_issue_advances_active_session_once_and_exposes_close_and_
assert close_handler.index("await issueController.close(selectedIssue)") < close_handler.index(
"await runTodayTransition('complete')"
)
assert close_handler.index("lastMyWork = lastMyWork.filter") < close_handler.index(
assert close_handler.index("lastMyWork.filter") < close_handler.index(
"await runTodayTransition('complete')"
)
@@ -6905,9 +6956,10 @@ async def test_assigned_issues_open_accessible_mobile_action_sheet_with_safe_mut
assert '<script src="static/issue-sheet.js"></script>' in html
assert "issueController.load(item)" in html
assert "issueController.comment(selectedIssue" in html
assert "window.confirm('Close ' + selectedIssue.key + '?')" in html
assert "'Close ' + selectedIssue.key + '?'" in html
assert "window.confirm(confirmation)" in html
assert "issueController.close(selectedIssue)" in html
assert "lastMyWork = lastMyWork.filter" in html
assert "lastMyWork.filter" in html
assert "if (issueTrigger?.isConnected) issueTrigger.focus()" in html
assert "e.key === 'Escape' && selectedIssue" in html