feat: revise delegated filings from mobile Filed (Closes #887)
CI / lint (pull_request) Successful in 1m50s
CI / build-release (pull_request) Successful in 5s
CI / browser-journey (pull_request) Successful in 55s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-15 11:00:18 +00:00
1 parent 5201b18cae
commit 64f549848d
8 files changed
+194 -11

No files matched your search

+60
View File
@@ -570,6 +570,66 @@ async def test_edit_assigned_issue_updates_title_and_body_at_expected_revision(m
)]
@pytest.mark.anyio
async def test_edit_filed_issue_uses_open_author_authorization(monkeypatch):
calls = []
async def update(repository, number, title, body, expected_updated_at):
calls.append((repository, number, title, body, expected_updated_at))
return {
"repository": repository, "number": number, "title": title, "body": body,
"state": "open", "updated_at": "2026-08-07T10:01:00Z",
}
monkeypatch.setattr(main.gitea_proxy, "update_authored_issue", update, raising=False)
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/17/content?access=filed",
json={
"title": "Clarified filing", "body": "Canonical criteria",
"expected_updated_at": "2026-08-07T10:00:00Z",
},
)
assert response.status_code == 200
assert calls == [(
"stackchain/api", 17, "Clarified filing", "Canonical criteria",
"2026-08-07T10:00:00Z",
)]
@pytest.mark.anyio
@pytest.mark.parametrize("target", [
{"state": "closed", "user": {"login": "timmy"}, "pull_request": None},
{"state": "open", "user": {"login": "alex"}, "pull_request": None},
{"state": "open", "user": {"login": "timmy"}, "pull_request": {}},
])
async def test_gitea_edit_filed_issue_rejects_ineligible_target_without_patch(target):
requests = []
async def handler(request):
requests.append(request)
if request.url.path == "/api/v1/user":
return httpx.Response(200, json={"login": "timmy"})
return httpx.Response(200, json={
"number": 17, "title": "Old", "body": "Old body",
"updated_at": "2026-08-07T10:00:00Z", **target,
})
gitea_proxy.start_client(transport=httpx.MockTransport(handler))
try:
with pytest.raises(gitea_proxy.IssueNotAvailableError):
await gitea_proxy.update_authored_issue(
"stackchain/api", 17, "Clarified", "New body",
"2026-08-07T10:00:00Z",
)
finally:
await gitea_proxy.stop_client()
assert [request.method for request in requests] == ["GET", "GET"]
@pytest.mark.anyio
async def test_gitea_edit_issue_rejects_stale_revision_without_patch():
requests = []
+54
View File
@@ -2184,6 +2184,43 @@ Promise.all([first, duplicate]).then(results => process.stdout.write(JSON.string
assert output["results"][0]["updated_at"] == "2026-08-07T10:01:00Z"
def test_filed_issue_content_edit_uses_author_scoped_endpoint_and_keeps_draft_on_failure():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const values = new Map();
const storage = {{
getItem:key => values.get(key) || null,
setItem:(key,value) => values.set(key,value),
removeItem:key => values.delete(key),
}};
let call;
const controller = createIssueSheet({{
storage,
fetchJson:(url, options) => {{ call={{url,options}}; return Promise.reject(new Error('conflict')); }},
}});
const item = {{repository:'stackchain/api', number:17, is_filed:true, is_assigned:false, state:'open'}};
const draft = {{title:'Clarified filing', body:'Canonical body', expectedUpdatedAt:'2026-08-07T10:00:00Z'}};
controller.updateContent(item, draft).catch(() => process.stdout.write(JSON.stringify({{
url:call.url, body:JSON.parse(call.options.body), draft:controller.loadEditDraft(item)
}})));
"""
output = json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
assert output == {
"url": "api/v1/repos/stackchain/api/issues/17/content?access=filed",
"body": {
"title": "Clarified filing", "body": "Canonical body",
"expected_updated_at": "2026-08-07T10:00:00Z",
},
"draft": {
"title": "Clarified filing", "body": "Canonical body",
"expectedUpdatedAt": "2026-08-07T10:00:00Z",
},
}
def test_issue_due_date_update_is_single_flight_and_keeps_draft_until_confirmed():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
@@ -4761,6 +4798,23 @@ async def test_mobile_assigned_issue_sheet_exposes_touch_sized_content_editor():
assert 'buildMyWork.replaceIssueContent(' in html
@pytest.mark.anyio
async def test_mobile_filed_issue_exposes_only_open_author_revision_controls():
html = await dashboard()
assert 'class="issue-content-editor"' in html
assert "const revisableFiling = item.is_filed && !item.is_completed && detail.state === 'open';" in html
assert "qs('#edit-issue-content').textContent = revisableFiling ? 'Revise filing' : 'Edit issue';" in html
assert "qs('#edit-issue-content').disabled = readOnly && !revisableFiling;" in html
assert '#issue-sheet.read-only .issue-content-editor' not in html
assert '.issue-content-editor { max-width:100%; overflow-x:hidden;' in html
assert '.issue-edit-form textarea { min-height:132px;' in html
assert '.issue-edit-form input, .issue-edit-form textarea, .issue-edit-form button { min-height:44px;' in html
assert "history.pushState({ ...history.state, stackchainIssueEdit:true }, '', window.location.href);" in html
assert "window.addEventListener('popstate', closeIssueEditorFromHistory);" in html
assert "if (!qs('#issue-edit-form').hidden)" in html
@pytest.mark.anyio
async def test_mobile_find_work_sheet_is_accessible_touch_sized_and_subpath_safe():
html = await dashboard()