feat: keep global search useful during partial failures (#199)
CI / lint (pull_request) Successful in 15s
CI / build-frontend (pull_request) Successful in 4s

This commit is contained in:
timmy committed 2026-08-07 14:18:38 +00:00
1 parent 2243b9453c
commit f1448e6b58
6 files changed
+195 -29

No files matched your search

+60
View File
@@ -82,6 +82,63 @@ if (ready[0].query !== 'release' || ready[0].items[0].title !== 'Current result'
)
def test_remote_command_search_aborts_superseded_and_cleared_queries():
script = f"""
const filterCommands = require({json.dumps(str(COMMANDS))});
(async () => {{
const signals = [];
const states = [];
const controller = filterCommands.createGlobalSearchController({{
delay: 0,
search: (query, signal) => {{
signals.push(signal);
return new Promise((resolve, reject) => signal.addEventListener('abort', () => {{
const error = new Error('aborted');
error.name = 'AbortError';
reject(error);
}}));
}},
onState: state => states.push(state),
}});
controller.setQuery('mobile');
await new Promise(resolve => setTimeout(resolve, 0));
controller.setQuery('release');
if (!signals[0].aborted) throw new Error('superseded request was not aborted');
await new Promise(resolve => setTimeout(resolve, 0));
controller.setQuery('');
if (!signals[1].aborted) throw new Error('cleared query request was not aborted');
await new Promise(resolve => setTimeout(resolve, 0));
if (states.some(state => state.status === 'error')) throw new Error('abort rendered an error');
if (states.at(-1).status !== 'idle') throw new Error('clear did not restore idle state');
}})().catch(error => {{ console.error(error); process.exit(1); }});
"""
subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
def test_remote_command_search_preserves_partial_result_status():
script = f"""
const filterCommands = require({json.dumps(str(COMMANDS))});
(async () => {{
const states = [];
const controller = filterCommands.createGlobalSearchController({{
delay: 0,
search: () => Promise.resolve({{ items:[{{ title:'Useful result' }}], partial:true }}),
onState: state => states.push(state),
}});
controller.setQuery('mobile');
await new Promise(resolve => setTimeout(resolve, 0));
await new Promise(resolve => setTimeout(resolve, 0));
const ready = states.find(state => state.status === 'ready');
if (!ready || ready.items.length !== 1 || ready.partial !== true) {{
throw new Error('partial result metadata was lost: ' + JSON.stringify(states));
}}
}})().catch(error => {{ console.error(error); process.exit(1); }});
"""
subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
def test_command_selection_wraps_for_arrow_keys():
script = f"""
const commands = require({json.dumps(str(COMMANDS))});
@@ -101,6 +158,9 @@ def test_palette_exposes_accessible_global_work_search_under_dashboard_subpath()
assert 'aria-controls="cmd-results"' in html
assert 'role="listbox"' in html
assert "fetch('api/v1/search?q='" in html
assert "async function searchGlobalWork(query, signal)" in html
assert "signal," in html
assert "Some results are temporarily unavailable." in html
assert urljoin(
"https://forge.alexanderwhitestone.com/dashboard/",
"api/v1/search?q=mobile",
+96 -18
View File
@@ -1,3 +1,5 @@
import asyncio
import httpx
import pytest
@@ -10,14 +12,17 @@ async def test_global_search_endpoint_returns_bounded_normalized_results(monkeyp
async def search(query, limit):
requested.append((query, limit))
return [{
"kind": "issue",
"repository": "stackchain/api",
"number": 42,
"title": "Repair mobile queue",
"state": "open",
"url": "https://forge.example/stackchain/api/issues/42",
}]
return {
"items": [{
"kind": "issue",
"repository": "stackchain/api",
"number": 42,
"title": "Repair mobile queue",
"state": "open",
"url": "https://forge.example/stackchain/api/issues/42",
}],
"partial": False,
}
monkeypatch.setattr(main.gitea_proxy, "global_search", search, raising=False)
transport = httpx.ASGITransport(app=main.app)
@@ -34,7 +39,7 @@ async def test_global_search_endpoint_returns_bounded_normalized_results(monkeyp
"title": "Repair mobile queue",
"state": "open",
"url": "https://forge.example/stackchain/api/issues/42",
}]}
}], "partial": False}
@pytest.mark.anyio
@@ -106,15 +111,88 @@ async def test_global_search_queries_issues_and_pulls_and_skips_unsafe_results()
assert {request["type"] for request in requests} == {"issues", "pulls"}
assert all(request["q"] == "mobile queue" and request["limit"] == "7" for request in requests)
assert results == [{
"kind": "issue", "repository": "stackchain/api", "number": 42,
"title": "Repair queue", "state": "open",
"url": "https://forge.example/stackchain/api/issues/42",
}, {
"kind": "pull", "repository": "stackchain/web", "number": 9,
"title": "Improve search", "state": "closed",
"url": "https://forge.example/stackchain/web/pulls/9",
}]
assert results == {
"items": [{
"kind": "issue", "repository": "stackchain/api", "number": 42,
"title": "Repair queue", "state": "open",
"url": "https://forge.example/stackchain/api/issues/42",
}, {
"kind": "pull", "repository": "stackchain/web", "number": 9,
"title": "Improve search", "state": "closed",
"url": "https://forge.example/stackchain/web/pulls/9",
}],
"partial": False,
}
@pytest.mark.anyio
async def test_global_search_returns_healthy_stream_when_other_stream_fails():
async def handler(request):
if request.url.params["type"] == "issues":
return httpx.Response(503, json={"message": "upstream details must stay private"})
return httpx.Response(200, json=[{
"number": 9,
"title": "Improve search",
"state": "open",
"repository": {"full_name": "stackchain/web"},
"html_url": "https://forge.example/stackchain/web/pulls/9",
}])
gitea_proxy.start_client(transport=httpx.MockTransport(handler))
try:
result = await gitea_proxy.global_search("search", 10)
finally:
await gitea_proxy.stop_client()
assert result == {
"items": [{
"kind": "pull",
"repository": "stackchain/web",
"number": 9,
"title": "Improve search",
"state": "open",
"url": "https://forge.example/stackchain/web/pulls/9",
}],
"partial": True,
}
@pytest.mark.anyio
async def test_global_search_caps_combined_results_to_requested_limit():
async def handler(request):
kind = request.url.params["type"]
items = [{
"number": number,
"title": f"{kind} {number}",
"state": "open",
"repository": {"full_name": "stackchain/web"},
"html_url": f"https://forge.example/stackchain/web/{kind}/{number}",
} for number in (1, 2)]
return httpx.Response(200, json=items)
gitea_proxy.start_client(transport=httpx.MockTransport(handler))
try:
result = await gitea_proxy.global_search("search", 2)
finally:
await gitea_proxy.stop_client()
assert len(result["items"]) == 2
assert result["partial"] is False
@pytest.mark.anyio
async def test_global_search_propagates_cancellation_to_stop_obsolete_work():
async def handler(request):
if request.url.params["type"] == "issues":
raise asyncio.CancelledError()
return httpx.Response(200, json=[])
gitea_proxy.start_client(transport=httpx.MockTransport(handler))
try:
with pytest.raises(asyncio.CancelledError):
await gitea_proxy.global_search("obsolete", 10)
finally:
await gitea_proxy.stop_client()
@pytest.mark.anyio