feat: apply search release plans atomically (Closes #817)
CI / lint (pull_request) Successful in 1m43s
CI / build-release (pull_request) Successful in 6s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-14 09:57:15 +00:00
1 parent 9a24aff537
commit 53539e0cdf
5 files changed
+184 -25

No files matched your search

+75
View File
@@ -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 = []
+8 -12
View File
@@ -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",