feat: complete authored pull workspace access (Closes #1348)
This commit is contained in:
1 parent
050390d6f6
commit
2102b0e5da
7 files changed
+264
-44
No files matched your search
@@ -26,6 +26,7 @@ def test_authored_pull_queue_is_phone_usable_and_opens_the_existing_pull_route(v
|
||||
page.add_script_tag(path=FRONTEND / "my-work.js")
|
||||
page.add_script_tag(path=FRONTEND / "work-route.js")
|
||||
page.add_script_tag(path=FRONTEND / "mobile-queue-launcher.js")
|
||||
page.add_script_tag(path=FRONTEND / "pull-sheet.js")
|
||||
page.evaluate("document.querySelector('#mobile-queue-sheet').showModal()")
|
||||
|
||||
row = page.locator('[data-mobile-queue="authored"]')
|
||||
@@ -63,6 +64,25 @@ def test_authored_pull_queue_is_phone_usable_and_opens_the_existing_pull_route(v
|
||||
"opened": "opened",
|
||||
"count": 1,
|
||||
}
|
||||
capabilities = page.evaluate("""() => {
|
||||
const item = {key:'stackchain/dashboard#41'};
|
||||
createPullSheet.resetOwnershipControls(
|
||||
document, item, () => false, {authored:true, assigned:false}
|
||||
);
|
||||
const ownership = document.querySelector('#pull-ownership');
|
||||
return {
|
||||
hidden: ownership.hidden,
|
||||
inert: ownership.inert,
|
||||
releaseDisabled: document.querySelector('#release-pull').disabled,
|
||||
handoffDisabled: document.querySelector('#load-pull-handoff').disabled,
|
||||
};
|
||||
}""")
|
||||
assert capabilities == {
|
||||
"hidden": True,
|
||||
"inert": True,
|
||||
"releaseDisabled": True,
|
||||
"handoffDisabled": True,
|
||||
}
|
||||
assert page.evaluate(
|
||||
"document.documentElement.scrollWidth > document.documentElement.clientWidth"
|
||||
) is False
|
||||
|
||||
@@ -7642,6 +7642,39 @@ process.stdout.write(JSON.stringify({{
|
||||
assert output["released"] == "stackchain/api#7 released. Choose the next ready Today item."
|
||||
|
||||
|
||||
def test_authored_unassigned_pull_hides_assignment_ownership_controls():
|
||||
script = f"""
|
||||
const createPullSheet = require({json.dumps(str(PULL_SHEET))});
|
||||
const elements = new Map([
|
||||
['#pull-ownership', {{open:true,hidden:false,inert:false}}],
|
||||
['#pull-handoff-recipient', {{innerHTML:'stale',disabled:false}}],
|
||||
['#confirm-pull-handoff', {{disabled:false,textContent:''}}],
|
||||
['#load-pull-handoff', {{disabled:false}}],
|
||||
['#release-pull', {{disabled:false,textContent:''}}],
|
||||
['#pull-handoff-status', {{textContent:''}}],
|
||||
['#edit-pull-content', {{hidden:false}}],
|
||||
['#pull-edit-form', {{hidden:false}}],
|
||||
['#pull-edit-status', {{textContent:'stale'}}],
|
||||
]);
|
||||
const doc = {{querySelector:selector => elements.get(selector)}};
|
||||
createPullSheet.resetOwnershipControls(
|
||||
doc,
|
||||
{{key:'stackchain/api#7'}},
|
||||
() => false,
|
||||
{{authored:true,assigned:false}}
|
||||
);
|
||||
process.stdout.write(JSON.stringify(Object.fromEntries(elements)));
|
||||
"""
|
||||
output = json.loads(subprocess.run(
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
).stdout)
|
||||
|
||||
assert output["#pull-ownership"]["hidden"] is True
|
||||
assert output["#pull-ownership"]["inert"] is True
|
||||
assert output["#load-pull-handoff"]["disabled"] is True
|
||||
assert output["#release-pull"]["disabled"] is True
|
||||
|
||||
|
||||
def test_pull_sheet_single_flights_handoff_and_release_ownership_mutations():
|
||||
script = f"""
|
||||
const createPullSheet = require({json.dumps(str(PULL_SHEET))});
|
||||
|
||||
+80
-10
@@ -39,7 +39,7 @@ async def test_pull_author_can_publish_assigned_draft_at_expected_head(monkeypat
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_gitea_publishes_only_authored_assigned_draft_and_verifies_ready_head():
|
||||
async def test_gitea_publishes_authored_unassigned_draft_and_verifies_ready_head():
|
||||
requests = []
|
||||
pull = {
|
||||
"number": 7,
|
||||
@@ -48,7 +48,7 @@ async def test_gitea_publishes_only_authored_assigned_draft_and_verifies_ready_h
|
||||
"draft": True,
|
||||
"merged": False,
|
||||
"user": {"login": "alex"},
|
||||
"assignees": [{"login": "alex"}],
|
||||
"assignees": [],
|
||||
"head": {"sha": "abc1234"},
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ async def test_pull_author_can_update_assigned_open_pull_context(monkeypatch):
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_gitea_pull_context_update_requires_author_assignment_and_matching_head():
|
||||
async def test_gitea_pull_context_update_allows_author_without_assignment_at_matching_head():
|
||||
requests = []
|
||||
|
||||
async def handler(request):
|
||||
@@ -141,7 +141,7 @@ async def test_gitea_pull_context_update_requires_author_assignment_and_matching
|
||||
if request.url.path == "/api/v1/repos/stackchain/api/pulls/7" and request.method == "GET":
|
||||
return httpx.Response(200, json={
|
||||
"number": 7, "title": "Old context", "body": "Old body", "state": "open",
|
||||
"merged": False, "user": {"login": "alex"}, "assignees": [{"login": "alex"}],
|
||||
"merged": False, "user": {"login": "alex"}, "assignees": [],
|
||||
"head": {"sha": "abc1234"},
|
||||
"html_url": "https://forge.example/stackchain/api/pulls/7",
|
||||
})
|
||||
@@ -172,6 +172,69 @@ async def test_gitea_pull_context_update_requires_author_assignment_and_matching
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_gitea_authored_unassigned_pull_has_workspace_access():
|
||||
requests = []
|
||||
|
||||
async def handler(request):
|
||||
requests.append((request.method, request.url.path))
|
||||
if request.url.path == "/api/v1/user":
|
||||
return httpx.Response(200, json={"login": "alex"})
|
||||
if request.url.path == "/api/v1/repos/stackchain/api/pulls/7":
|
||||
return httpx.Response(200, json={
|
||||
"number": 7,
|
||||
"state": "open",
|
||||
"merged": False,
|
||||
"user": {"login": "alex"},
|
||||
"assignees": [],
|
||||
})
|
||||
raise AssertionError(f"unexpected request: {request.method} {request.url.path}")
|
||||
|
||||
gitea_proxy.start_client(transport=httpx.MockTransport(handler))
|
||||
try:
|
||||
capabilities = await gitea_proxy.pull_workspace_capabilities("stackchain/api", 7)
|
||||
finally:
|
||||
await gitea_proxy.stop_client()
|
||||
|
||||
assert capabilities == {"authored": True, "assigned": False}
|
||||
assert requests == [
|
||||
("GET", "/api/v1/user"),
|
||||
("GET", "/api/v1/repos/stackchain/api/pulls/7"),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_authored_unassigned_pull_detail_returns_capabilities(monkeypatch):
|
||||
async def assigned(repository, number):
|
||||
return False
|
||||
|
||||
async def capabilities(repository, number):
|
||||
assert (repository, number) == ("stackchain/api", 7)
|
||||
return {"authored": True, "assigned": False}
|
||||
|
||||
async def detail(repository, number):
|
||||
return {
|
||||
"repository": repository,
|
||||
"number": number,
|
||||
"title": "Ship mobile flow",
|
||||
"body": "Ready for review",
|
||||
"author": "alex",
|
||||
"head_sha": "abc1234",
|
||||
"state": "open",
|
||||
"conversation": {"comments": [], "page": 1, "older_page": None, "total": 0},
|
||||
}
|
||||
|
||||
monkeypatch.setattr(main.gitea_proxy, "is_assigned_pull", assigned)
|
||||
monkeypatch.setattr(main.gitea_proxy, "pull_workspace_capabilities", capabilities)
|
||||
monkeypatch.setattr(main.gitea_proxy, "pull_completion_detail", detail)
|
||||
transport = httpx.ASGITransport(app=main.app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
response = await client.get("/api/v1/repos/stackchain/api/pulls/7/detail")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["capabilities"] == {"authored": True, "assigned": False}
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_assigned_pull_detail_reports_completion_state(monkeypatch):
|
||||
async def assigned(repository, number):
|
||||
@@ -213,7 +276,11 @@ async def test_assigned_pull_detail_rejects_unassigned_pull(monkeypatch):
|
||||
async def assigned(repository, number):
|
||||
return False
|
||||
|
||||
async def capabilities(repository, number):
|
||||
return {"authored": False, "assigned": False}
|
||||
|
||||
monkeypatch.setattr(main.gitea_proxy, "is_assigned_pull", assigned, raising=False)
|
||||
monkeypatch.setattr(main.gitea_proxy, "pull_workspace_capabilities", capabilities)
|
||||
transport = httpx.ASGITransport(app=main.app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
response = await client.get("/api/v1/repos/private/secret/pulls/9/detail")
|
||||
@@ -1208,7 +1275,7 @@ async def test_pull_handoff_candidates_are_bounded_and_exclude_invalid_or_curren
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_gitea_pull_review_request_filters_candidates_and_confirms_requested_reviewer():
|
||||
async def test_gitea_authored_unassigned_pull_review_request_filters_candidates_and_confirms_reviewer():
|
||||
requests = []
|
||||
review_requested = False
|
||||
|
||||
@@ -1230,8 +1297,8 @@ async def test_gitea_pull_review_request_filters_candidates_and_confirms_request
|
||||
requested.append({"login": "casey"})
|
||||
return httpx.Response(200, json={
|
||||
"number": 7, "state": "open", "merged": False,
|
||||
"head": {"sha": "abc123"}, "user": {"login": "alex"},
|
||||
"assignees": [{"login": "timmy"}],
|
||||
"head": {"sha": "abc123"}, "user": {"login": "timmy"},
|
||||
"assignees": [],
|
||||
"requested_reviewers": requested,
|
||||
})
|
||||
if request.method == "POST" and request.url.path.endswith("/requested_reviewers"):
|
||||
@@ -1248,7 +1315,10 @@ async def test_gitea_pull_review_request_filters_candidates_and_confirms_request
|
||||
finally:
|
||||
await gitea_proxy.stop_client()
|
||||
|
||||
assert candidates == [{"login": "casey", "name": "Casey"}]
|
||||
assert candidates == [
|
||||
{"login": "alex", "name": "Alexander"},
|
||||
{"login": "casey", "name": "Casey"},
|
||||
]
|
||||
assert result == {
|
||||
"repository": "stackchain/api", "number": 7, "head_sha": "abc123",
|
||||
"requested_reviewers": ["sam", "casey"], "reviewer": "casey",
|
||||
@@ -1259,7 +1329,7 @@ async def test_gitea_pull_review_request_filters_candidates_and_confirms_request
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_gitea_cancel_pending_pull_review_confirms_authoritative_removal():
|
||||
async def test_gitea_authored_unassigned_pull_can_cancel_pending_review():
|
||||
requests = []
|
||||
cancelled = False
|
||||
|
||||
@@ -1271,7 +1341,7 @@ async def test_gitea_cancel_pending_pull_review_confirms_authoritative_removal()
|
||||
if request.url.path.endswith("/pulls/7"):
|
||||
return httpx.Response(200, json={
|
||||
"number": 7, "state": "open", "merged": False,
|
||||
"head": {"sha": "abc123"}, "assignees": [{"login": "timmy"}],
|
||||
"head": {"sha": "abc123"}, "user": {"login": "timmy"}, "assignees": [],
|
||||
"requested_reviewers": [{"login": "casey"}] if cancelled else [{"login": "sam"}, {"login": "casey"}],
|
||||
})
|
||||
if request.method == "DELETE" and request.url.path.endswith("/requested_reviewers"):
|
||||
|
||||
@@ -107,6 +107,36 @@ async def test_resolve_work_route_returns_only_a_requested_review():
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_resolve_work_route_returns_an_open_authored_unassigned_pull():
|
||||
async def upstream(request):
|
||||
if request.url.path.endswith("/user"):
|
||||
return httpx.Response(200, json={"login": "timmy"})
|
||||
return httpx.Response(200, json={
|
||||
"id": 703,
|
||||
"number": 90,
|
||||
"title": "Ship authored flow",
|
||||
"state": "open",
|
||||
"html_url": gitea_proxy.GITEA_URL + "/stackchain/dashboard/pulls/90",
|
||||
"assignees": [],
|
||||
"requested_reviewers": [],
|
||||
"user": {"login": "timmy"},
|
||||
})
|
||||
|
||||
gitea_proxy.start_client(transport=httpx.MockTransport(upstream))
|
||||
try:
|
||||
result = await gitea_proxy.resolve_work_route(
|
||||
"pull", "stackchain/dashboard", 90, None
|
||||
)
|
||||
finally:
|
||||
await gitea_proxy.stop_client()
|
||||
|
||||
assert result["kind"] == "pull"
|
||||
assert result["number"] == 90
|
||||
assert result["is_assigned"] is False
|
||||
assert result["work_reasons"] == ["authored_by_me"]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_resolve_work_route_returns_an_open_issue_filed_by_the_current_user():
|
||||
requests = []
|
||||
|
||||
Reference in new issue
Block a user