From 727df4c7f89781f83a5df9d85e495763a8b5ecdd Mon Sep 17 00:00:00 2001 From: benya Date: Sun, 8 Mar 2026 21:08:39 +0300 Subject: [PATCH] test(privacy): cover avatar everyone visibility in user search --- docs/core-checklist-status.md | 2 +- tests/test_chat_message_flow.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/core-checklist-status.md b/docs/core-checklist-status.md index df74e62..d71f67f 100644 --- a/docs/core-checklist-status.md +++ b/docs/core-checklist-status.md @@ -37,7 +37,7 @@ Legend: 28. Notifications - `PARTIAL` (browser notifications + mute/settings; chat mute is propagated in chat list payload, honored by web realtime notifications with mention override, and mute toggle now syncs instantly in chat store; backend now emits `chat_updated` after notification mute/unmute for cross-tab consistency; no mobile push infra) 29. Archive - `DONE` 30. Blacklist - `DONE` -31. Privacy - `PARTIAL` (avatar/last-seen/group-invites + PM policy `everyone|contacts|nobody`; group-invite `nobody` is available in API and web settings; integration tests cover PM policy matrix (`everyone/contacts/nobody`), group-invite policy matrix (`everyone/contacts/nobody`), private chat counterpart visibility for `nobody/contacts/everyone`, and avatar visibility matrix in search for `nobody/contacts` (with contact allowance), remaining UX/matrix hardening) +31. Privacy - `PARTIAL` (avatar/last-seen/group-invites + PM policy `everyone|contacts|nobody`; group-invite `nobody` is available in API and web settings; integration tests cover PM policy matrix (`everyone/contacts/nobody`), group-invite policy matrix (`everyone/contacts/nobody`), private chat counterpart visibility for `nobody/contacts/everyone`, and avatar visibility matrix in search for `everyone/contacts/nobody`, remaining UX/matrix hardening) 32. Security - `PARTIAL` (sessions + revoke + 2FA base + access-session visibility; integration tests cover single-session revoke and revoke-all invalidation/force-disconnect; 2FA setup now blocked after enable to prevent secret re-issuance; one-time recovery codes added and covered for normalization/lifecycle (`remaining_codes` decrement + one-time usage); web auth panel supports recovery-code login; settings now warns when recovery codes are empty and provides copy/download actions for freshly generated codes) 33. Realtime Events - `DONE` (connect/disconnect/send/receive/typing/read/delivered/online/offline + chat/message updates + chat_deleted) 34. Sync - `PARTIAL` (cross-device via backend state + realtime; reconciliation improved for loaded chats/messages, chat-info panel hot-refreshes on `chat_updated`, delete/leave updates realtime subscriptions, full-chat delete emits `chat_deleted`, and per-user chat state mutations (archive/unarchive/pin/unpin/mute) now emit `chat_updated`) diff --git a/tests/test_chat_message_flow.py b/tests/test_chat_message_flow.py index 3cc2415..c6dda08 100644 --- a/tests/test_chat_message_flow.py +++ b/tests/test_chat_message_flow.py @@ -972,6 +972,29 @@ async def test_avatar_privacy_contacts_in_search_visible_only_for_contacts(clien assert owner_after["avatar_url"] == "https://cdn.example.com/avatar-contacts-owner.png" +async def test_avatar_privacy_everyone_in_search_visible_without_contacts(client, db_session): + owner = await _create_verified_user(client, db_session, "avatar_everyone_owner@example.com", "avatar_everyone_owner", "strongpass123") + viewer = await _create_verified_user(client, db_session, "avatar_everyone_viewer@example.com", "avatar_everyone_viewer", "strongpass123") + + set_avatar_and_privacy = await client.put( + "/api/v1/users/profile", + headers={"Authorization": f"Bearer {owner['access_token']}"}, + json={"avatar_url": "https://cdn.example.com/avatar-everyone-owner.png", "privacy_avatar": "everyone"}, + ) + assert set_avatar_and_privacy.status_code == 200 + + search_response = await client.get( + "/api/v1/users/search", + params={"query": "avatar_everyone_owner", "limit": 20}, + headers={"Authorization": f"Bearer {viewer['access_token']}"}, + ) + assert search_response.status_code == 200 + rows = search_response.json() + owner_row = next((item for item in rows if item["username"] == "avatar_everyone_owner"), None) + assert owner_row is not None + assert owner_row["avatar_url"] == "https://cdn.example.com/avatar-everyone-owner.png" + + async def test_private_chat_everyone_privacy_reveals_avatar_and_presence_without_contacts(client, db_session): owner = await _create_verified_user(client, db_session, "privacy_everyone_owner@example.com", "privacy_everyone_owner", "strongpass123") viewer = await _create_verified_user(client, db_session, "privacy_everyone_viewer@example.com", "privacy_everyone_viewer", "strongpass123")