refactor(vector-sync): address round-1 review nits
- ProvisionSignal.wait() re-arms in a finally so a cancelled wait (shutdown racing the doorbell) leaves a fresh unset event, not a stale set-but-consumed one; preserves the no-await-before-swap lost-wakeup guarantee. - Hoist user_manager_task's _wake_on helper out of the while loop (one object, not one per iteration). - Test: assert ProvisionSignal via its public wait() contract instead of the private _event attribute. - Add test_provision_app_password_wakes_user_manager covering the api/passwords.py wake path (previously only LFv2 web + MCP tool were tested). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d8e3e9bc33
commit
3f09453926
@@ -254,6 +254,48 @@ async def test_provision_app_password_success(temp_storage, mocker):
|
||||
assert get_kwargs["auth"] == ("testuser", "aaaaa-bbbbb-ccccc-ddddd-eeeee")
|
||||
|
||||
|
||||
async def test_provision_app_password_wakes_user_manager(temp_storage, mocker):
|
||||
"""A successful provision rings the background-sync doorbell so the user
|
||||
manager re-polls immediately (the api/passwords.py wake path)."""
|
||||
mocker.patch(
|
||||
"nextcloud_mcp_server.api.passwords.get_settings",
|
||||
return_value=MagicMock(
|
||||
nextcloud_host="http://localhost:8080",
|
||||
nextcloud_verify_ssl=True,
|
||||
nextcloud_ca_bundle=None,
|
||||
),
|
||||
)
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"ocs": {"data": {"id": "testuser"}}}
|
||||
mock_client = AsyncMock()
|
||||
mock_client.get = AsyncMock(return_value=mock_response)
|
||||
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
||||
mock_client.__aexit__ = AsyncMock()
|
||||
mocker.patch(
|
||||
"nextcloud_mcp_server.api.passwords.nextcloud_httpx_client",
|
||||
return_value=mock_client,
|
||||
)
|
||||
|
||||
# Spy on the doorbell helper (imported locally from app at call time).
|
||||
notify = mocker.patch("nextcloud_mcp_server.app.notify_user_provisioned")
|
||||
|
||||
client = TestClient(create_test_app(temp_storage))
|
||||
response = client.post(
|
||||
"/api/v1/users/testuser/app-password",
|
||||
headers={
|
||||
"Authorization": create_basic_auth_header(
|
||||
"testuser", "aaaaa-bbbbb-ccccc-ddddd-eeeee"
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["success"] is True
|
||||
notify.assert_called_once()
|
||||
|
||||
|
||||
async def test_provision_app_password_uses_loginname_not_uid(temp_storage, mocker):
|
||||
"""Regression: when the Nextcloud UID differs from the loginName (e.g.
|
||||
OIDC-provisioned users whose UID is their display name — UID
|
||||
|
||||
@@ -203,11 +203,13 @@ def test_notify_user_provisioned_noop_without_manager(mocker):
|
||||
app_module.notify_user_provisioned()
|
||||
|
||||
|
||||
def test_notify_user_provisioned_rings_when_present(mocker):
|
||||
async def test_notify_user_provisioned_rings_when_present(mocker):
|
||||
"""When a manager is running, the helper rings its signal."""
|
||||
import nextcloud_mcp_server.app as app_module
|
||||
|
||||
signal = ProvisionSignal()
|
||||
mocker.patch.object(app_module._vector_sync_state, "provision_signal", signal)
|
||||
app_module.notify_user_provisioned()
|
||||
assert signal._event.is_set()
|
||||
# Public contract: after a ring, the next wait() returns without blocking.
|
||||
with anyio.fail_after(1):
|
||||
await signal.wait()
|
||||
|
||||
Reference in New Issue
Block a user