feat: release mobile issue assignments (#183)
This commit is contained in:
1 parent
e96ff42dff
commit
87058d72e6
8 files changed
+264
-1
No files matched your search
@@ -460,6 +460,68 @@ async def test_claim_available_issue_endpoint_reports_assignment_race_as_conflic
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_gitea_release_issue_removes_only_authenticated_user_and_confirms_peers():
|
||||
requests = []
|
||||
|
||||
async def handler(request):
|
||||
requests.append(request)
|
||||
if request.method == "GET" and request.url.path.endswith("/issues/17"):
|
||||
return httpx.Response(200, json={
|
||||
"id": 81, "number": 17, "title": "Shared work", "state": "open",
|
||||
"pull_request": None,
|
||||
"assignees": [{"login": "timmy"}, {"login": "alex"}],
|
||||
"labels": [],
|
||||
})
|
||||
if request.method == "GET" and request.url.path == "/api/v1/user":
|
||||
return httpx.Response(200, json={"login": "timmy"})
|
||||
return httpx.Response(200, json={
|
||||
"id": 81, "number": 17, "title": "Shared work", "state": "open",
|
||||
"assignees": [{"login": "alex"}], "labels": [],
|
||||
})
|
||||
|
||||
gitea_proxy.start_client(transport=httpx.MockTransport(handler))
|
||||
try:
|
||||
result = await gitea_proxy.release_assigned_issue("stackchain/api", 17)
|
||||
finally:
|
||||
await gitea_proxy.stop_client()
|
||||
|
||||
assert [(request.method, request.url.path) for request in requests] == [
|
||||
("GET", "/api/v1/user"),
|
||||
("GET", "/api/v1/repos/stackchain/api/issues/17"),
|
||||
("PATCH", "/api/v1/repos/stackchain/api/issues/17"),
|
||||
]
|
||||
assert requests[2].content == b'{"assignees":["alex"]}'
|
||||
assert result["assignees"] == ["alex"]
|
||||
assert result["available"] is False
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_release_issue_endpoint_invalidates_available_work_and_returns_confirmation(monkeypatch):
|
||||
calls = []
|
||||
|
||||
async def release(repository, number):
|
||||
calls.append((repository, number))
|
||||
return {
|
||||
"number": number, "repository": repository, "state": "open",
|
||||
"assignees": [], "available": True,
|
||||
}
|
||||
|
||||
monkeypatch.setattr(main.gitea_proxy, "release_assigned_issue", release, raising=False)
|
||||
monkeypatch.setattr(main, "_available_issue_snapshot_value", [{"number": 99}])
|
||||
monkeypatch.setattr(main, "_available_issue_snapshot_created_at", 123.0)
|
||||
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/release")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["cache-control"] == "no-store"
|
||||
assert response.json()["available"] is True
|
||||
assert calls == [("stackchain/api", 17)]
|
||||
assert main._available_issue_snapshot_value is None
|
||||
assert main._available_issue_snapshot_created_at is None
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_issue_detail_endpoint_returns_assigned_issue_with_no_store(monkeypatch):
|
||||
async def assigned(repository, number):
|
||||
|
||||
@@ -110,6 +110,51 @@ process.stdout.write(JSON.stringify({{
|
||||
}
|
||||
|
||||
|
||||
def test_issue_release_is_single_flight_and_requires_confirmed_unassignment():
|
||||
script = f"""
|
||||
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
|
||||
let calls = 0;
|
||||
let finish;
|
||||
const controller = createIssueSheet({{
|
||||
fetchJson: () => {{
|
||||
calls += 1;
|
||||
return new Promise(resolve => {{ finish = resolve; }});
|
||||
}},
|
||||
storage: null,
|
||||
}});
|
||||
const item = {{repository:'stackchain/api', number:17}};
|
||||
const first = controller.release(item, 'timmy');
|
||||
const duplicate = controller.release(item, 'timmy');
|
||||
finish({{number:17, repository:'stackchain/api', assignees:[], available:true}});
|
||||
Promise.all([first, duplicate]).then(results => process.stdout.write(JSON.stringify({{
|
||||
calls, same: first === duplicate, results
|
||||
}})));
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
)
|
||||
output = json.loads(result.stdout)
|
||||
assert output["calls"] == 1
|
||||
assert output["same"] is True
|
||||
assert output["results"][0]["available"] is True
|
||||
|
||||
|
||||
def test_issue_release_rejects_response_that_still_assigns_current_user():
|
||||
script = f"""
|
||||
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
|
||||
const controller = createIssueSheet({{
|
||||
fetchJson: async () => ({{number:17, assignees:['timmy']}}), storage: null,
|
||||
}});
|
||||
controller.release({{repository:'stackchain/api', number:17}}, 'timmy')
|
||||
.then(() => process.stdout.write('unexpected'))
|
||||
.catch(error => process.stdout.write(error.message));
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
)
|
||||
assert result.stdout == "Issue release was not confirmed."
|
||||
|
||||
|
||||
def test_my_work_reviews_filter_and_summary_are_actionable():
|
||||
items = [
|
||||
{"title": "Issue", "kind": "issue", "is_review": False, "is_assigned": True},
|
||||
@@ -1221,6 +1266,40 @@ async def test_assigned_issues_open_accessible_mobile_action_sheet_with_safe_mut
|
||||
assert "e.key === 'Escape' && selectedIssue" in html
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mobile_issue_sheet_releases_assignment_with_confirmed_local_removal():
|
||||
html = await dashboard()
|
||||
|
||||
assert 'id="release-issue"' in html
|
||||
assert 'Release assignment' in html
|
||||
assert "window.confirm('Release ' + selectedIssue.key + ' from your My Work?')" in html
|
||||
assert "issueController.release(selectedIssue, lastContextSnapshot?.user?.login)" in html
|
||||
assert "buildMyWork.removeIssue(" in html
|
||||
|
||||
|
||||
def test_remove_issue_updates_snapshot_without_mutating_other_work():
|
||||
payload = {
|
||||
"issues": [
|
||||
{"repository": "stackchain/api", "number": 17},
|
||||
{"repository": "stackchain/web", "number": 18},
|
||||
],
|
||||
"pull_requests": [{"repository": "stackchain/api", "number": 17}],
|
||||
}
|
||||
script = f"""
|
||||
const buildMyWork = require({json.dumps(str(MY_WORK))});
|
||||
const original = {json.dumps(payload)};
|
||||
const updated = buildMyWork.removeIssue(original, 'stackchain/api', 17);
|
||||
process.stdout.write(JSON.stringify({{updated, original}}));
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
)
|
||||
output = json.loads(result.stdout)
|
||||
assert output["updated"]["issues"] == [{"repository": "stackchain/web", "number": 18}]
|
||||
assert output["updated"]["pull_requests"] == payload["pull_requests"]
|
||||
assert output["original"]["issues"] == payload["issues"]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mobile_issue_sheet_edits_labels_and_repaints_confirmed_priority():
|
||||
html = await dashboard()
|
||||
|
||||
Reference in new issue
Block a user