feat: mute future updates during triage (Closes #751)
This commit is contained in:
1 parent
b93e63ab70
commit
8dc3880167
8 files changed
+190
-3
No files matched your search
@@ -254,6 +254,8 @@ async def test_notification_detail_loads_subject_and_latest_comment_for_inbox_re
|
||||
"number": 7, "state": "open", "assignees": [],
|
||||
"body": "Deploy fails after **three** retries.",
|
||||
})
|
||||
if request.url.path.endswith("/issues/7/subscriptions/check"):
|
||||
return httpx.Response(200, json={"subscribed": True, "ignored": False})
|
||||
if request.url.path.endswith("/issues/comments/9"):
|
||||
return httpx.Response(200, json={
|
||||
"id": 9,
|
||||
@@ -283,6 +285,7 @@ async def test_notification_detail_loads_subject_and_latest_comment_for_inbox_re
|
||||
"http://127.0.0.1:3000/api/v1/repos/stackchain/api/issues/7",
|
||||
"http://127.0.0.1:3000/api/v1/repos/stackchain/api/issues/comments/9",
|
||||
"http://127.0.0.1:3000/api/v1/repos/stackchain/api/issues/7/comments?limit=20&page=1",
|
||||
"http://127.0.0.1:3000/api/v1/repos/stackchain/api/issues/7/subscriptions/check",
|
||||
]
|
||||
assert result == {
|
||||
"id": 42,
|
||||
@@ -300,6 +303,7 @@ async def test_notification_detail_loads_subject_and_latest_comment_for_inbox_re
|
||||
},
|
||||
"issue": {"number": 7, "assignees": [], "claimable": True},
|
||||
"acknowledge_supported": True,
|
||||
"mute_supported": True,
|
||||
"conversation": {
|
||||
"comments": [{
|
||||
"id": 9,
|
||||
@@ -398,3 +402,37 @@ async def test_reply_to_notification_posts_to_its_issue_conversation(subject_kin
|
||||
"created_at": "2026-08-07T19:00:00Z",
|
||||
"url": "https://forge.example/stackchain/api/issues/7#issuecomment-91",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mute_notification_unsubscribes_current_user_from_trusted_thread_target():
|
||||
requests = []
|
||||
|
||||
def upstream(request):
|
||||
requests.append((request.method, request.url.path))
|
||||
if request.url.path.endswith("/notifications/threads/42"):
|
||||
return httpx.Response(200, json={
|
||||
"repository": {"full_name": "stackchain/api"},
|
||||
"subject": {
|
||||
"type": "Pull",
|
||||
"url": "http://127.0.0.1:3000/api/v1/repos/stackchain/api/pulls/7",
|
||||
},
|
||||
})
|
||||
if request.url.path.endswith("/user"):
|
||||
return httpx.Response(200, json={"login": "timmy"})
|
||||
if request.method == "DELETE":
|
||||
return httpx.Response(204)
|
||||
return httpx.Response(404)
|
||||
|
||||
gitea_proxy.start_client(transport=httpx.MockTransport(upstream))
|
||||
try:
|
||||
result = await gitea_proxy.mute_notification(42)
|
||||
finally:
|
||||
await gitea_proxy.stop_client()
|
||||
|
||||
assert result == {"id": 42, "repository": "stackchain/api", "number": 7, "muted": True}
|
||||
assert requests == [
|
||||
("GET", "/api/v1/notifications/threads/42"),
|
||||
("GET", "/api/v1/user"),
|
||||
("DELETE", "/api/v1/repos/stackchain/api/issues/7/subscriptions/timmy"),
|
||||
]
|
||||
@@ -4958,6 +4958,21 @@ async def test_mobile_update_sheet_wires_touch_safe_online_acknowledge_and_next(
|
||||
assert '.update-sheet-actions #acknowledge-update-next { min-height:44px;' in html
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mobile_update_sheet_mutes_future_updates_and_advances_only_on_read_success():
|
||||
html = await dashboard()
|
||||
|
||||
assert 'id="mute-update-next" type="button" hidden' in html
|
||||
assert ">Mute future updates & next</button>" in html
|
||||
assert "qs('#mute-update-next').hidden = !detail.mute_supported;" in html
|
||||
assert "async function muteNotification(notificationId)" in html
|
||||
assert "'/mute', { method: 'POST'" in html
|
||||
assert "notificationReader.acceptReadAndNext(lastMyWork, item)" in html
|
||||
assert "Future updates are muted; current item is still unread" in html
|
||||
assert "qs('#mute-update-next').disabled = offline;" in html
|
||||
assert ".update-sheet-actions #mute-update-next" in html
|
||||
|
||||
|
||||
def test_notification_reader_keeps_current_update_retryable_when_detail_load_fails():
|
||||
script = f"""
|
||||
const buildMyWork = require({json.dumps(str(MY_WORK))});
|
||||
|
||||
@@ -215,6 +215,34 @@ async def test_mark_notification_unread_api_is_bounded_and_never_cacheable(monke
|
||||
assert restored == [42]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mute_notification_then_marks_read_and_reports_partial_success(monkeypatch):
|
||||
calls = []
|
||||
|
||||
async def mute(thread_id):
|
||||
calls.append(("mute", thread_id))
|
||||
return {"id": thread_id, "muted": True}
|
||||
|
||||
async def fail_read(thread_id):
|
||||
calls.append(("read", thread_id))
|
||||
raise httpx.HTTPError("unavailable")
|
||||
|
||||
monkeypatch.setattr(main.gitea_proxy, "mute_notification", mute, raising=False)
|
||||
monkeypatch.setattr(main, "mark_notification_read", fail_read)
|
||||
transport = httpx.ASGITransport(app=main.app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
response = await client.post("/api/v1/notifications/42/mute")
|
||||
|
||||
assert response.status_code == 409
|
||||
assert response.json() == {
|
||||
"id": 42,
|
||||
"muted": True,
|
||||
"status": "unread",
|
||||
"error": "Future updates are muted; current item is still unread. Retry mark read & next.",
|
||||
}
|
||||
assert calls == [("mute", 42), ("read", 42)]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_acknowledge_notification_api_confirms_reaction_and_removes_snapshot_item(monkeypatch):
|
||||
calls = []
|
||||
|
||||
Reference in new issue
Block a user