feat: apply search release plans atomically (Closes #817)
This commit is contained in:
1 parent
9a24aff537
commit
53539e0cdf
5 files changed
+184
-25
No files matched your search
@@ -215,6 +215,81 @@ async def test_gitea_milestone_update_revalidates_assignment_and_open_repository
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.parametrize("due_date", ["2026-08-31T23:59:59Z", None])
|
||||
async def test_release_plan_applies_milestone_and_optional_deadline_in_one_patch(due_date):
|
||||
requests = []
|
||||
existing_due_date = "2026-08-20T23:59:59Z"
|
||||
|
||||
async def handler(request):
|
||||
requests.append(request)
|
||||
if request.url.path == "/api/v1/user":
|
||||
return httpx.Response(200, json={"login": "timmy"})
|
||||
if request.method == "GET" and request.url.path.endswith("/issues/17"):
|
||||
return httpx.Response(200, json={
|
||||
"number": 17, "state": "open", "pull_request": None,
|
||||
"assignees": [{"login": "timmy"}], "due_date": existing_due_date,
|
||||
})
|
||||
if request.method == "GET" and request.url.path.endswith("/milestones"):
|
||||
return httpx.Response(200, json=[
|
||||
{"id": 9, "title": "August RC", "state": "open"},
|
||||
])
|
||||
return httpx.Response(200, json={
|
||||
"number": 17, "state": "open",
|
||||
"milestone": {"id": 9, "title": "August RC"},
|
||||
"due_date": due_date or existing_due_date,
|
||||
})
|
||||
|
||||
gitea_proxy.start_client(transport=httpx.MockTransport(handler))
|
||||
try:
|
||||
result = await gitea_proxy.update_assigned_issue_release_plan(
|
||||
"stackchain/api", 17, 9, due_date
|
||||
)
|
||||
finally:
|
||||
await gitea_proxy.stop_client()
|
||||
|
||||
patch_requests = [request for request in requests if request.method == "PATCH"]
|
||||
assert len(patch_requests) == 1
|
||||
expected_payload = {"milestone": 9}
|
||||
if due_date:
|
||||
expected_payload["due_date"] = due_date
|
||||
assert json.loads(patch_requests[0].content) == expected_payload
|
||||
assert result == {
|
||||
"repository": "stackchain/api", "number": 17, "state": "open",
|
||||
"milestone": {"id": 9, "title": "August RC"},
|
||||
"due_date": due_date or existing_due_date,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_release_plan_endpoint_is_bounded_and_returns_confirmed_plan(monkeypatch):
|
||||
calls = []
|
||||
|
||||
async def update(repository, number, milestone_id, due_date):
|
||||
calls.append((repository, number, milestone_id, due_date))
|
||||
return {
|
||||
"repository": repository, "number": number, "state": "open",
|
||||
"milestone": {"id": milestone_id, "title": "August RC"},
|
||||
"due_date": due_date,
|
||||
}
|
||||
|
||||
monkeypatch.setattr(
|
||||
main.gitea_proxy, "update_assigned_issue_release_plan", 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/release-plan",
|
||||
json={"milestone_id": 9, "due_date": "2026-08-31T23:59:59Z"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["cache-control"] == "no-store"
|
||||
assert response.json()["milestone"] == {"id": 9, "title": "August RC"}
|
||||
assert response.json()["due_date"] == "2026-08-31T23:59:59Z"
|
||||
assert calls == [("stackchain/api", 17, 9, "2026-08-31T23:59:59Z")]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_milestone_routes_are_repository_bounded_and_no_store(monkeypatch):
|
||||
calls = []
|
||||
|
||||
@@ -293,8 +293,9 @@ const fetchJson=(url,options={{}})=>{{
|
||||
calls.push({{url,method:options.method || 'GET',body:options.body ? JSON.parse(options.body) : null}});
|
||||
if (url.endsWith('/milestones')) return Promise.resolve([{{id:9,title:'August RC'}},{{id:7,title:'Old',state:'closed'}}]);
|
||||
const number=Number(url.split('/issues/')[1].split('/')[0]);
|
||||
if (url.endsWith('/milestone')) return Promise.resolve({{number,milestone:{{id:9,title:'August RC'}}}});
|
||||
if (url.endsWith('/due-date')) return Promise.resolve({{number,due_date:'2026-08-31T23:59:59Z'}});
|
||||
if (url.endsWith('/release-plan')) return Promise.resolve({{
|
||||
number,milestone:{{id:9,title:'August RC'}},due_date:'2026-08-31T23:59:59Z'
|
||||
}});
|
||||
return Promise.resolve();
|
||||
}};
|
||||
const processorOptions=[];
|
||||
@@ -346,16 +347,11 @@ listeners['plan-selected-search-results:click']().then(async()=>{{
|
||||
"due_date": "2026-08-31T23:59:59Z",
|
||||
},
|
||||
}
|
||||
assert result["calls"][2:] == [
|
||||
{
|
||||
"url": "api/v1/repos/stackchain/dashboard/issues/815/milestone",
|
||||
"method": "PATCH", "body": {"milestone_id": 9},
|
||||
},
|
||||
{
|
||||
"url": "api/v1/repos/stackchain/dashboard/issues/815/due-date",
|
||||
"method": "PATCH", "body": {"due_date": "2026-08-31T23:59:59Z"},
|
||||
},
|
||||
]
|
||||
assert result["calls"][2:] == [{
|
||||
"url": "api/v1/repos/stackchain/dashboard/issues/815/release-plan",
|
||||
"method": "PATCH",
|
||||
"body": {"milestone_id": 9, "due_date": "2026-08-31T23:59:59Z"},
|
||||
}]
|
||||
assert result["queued"] == "queued"
|
||||
assert result["journals"] == [
|
||||
"search-today-batch", "search-later-batch", "search-release-batch",
|
||||
|
||||
Reference in new issue
Block a user