test_announcements_api.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. """Who sees announcements, and per-user read state.
  2. Administrators see them; other signed-in users only when
  3. ``announcements_all_users`` is on. With authentication off, the person running
  4. Bambuddy sees them. Anyone else gets an empty list, which hides the sidebar entry
  5. and the banner the same way "nothing published" does.
  6. """
  7. import json
  8. import secrets
  9. import pytest
  10. from httpx import AsyncClient
  11. from backend.app.models.announcement import Announcement
  12. from backend.app.models.settings import Settings
  13. URL = "/api/v1/announcements"
  14. _FIXTURE_PW = "Aa1!" + secrets.token_urlsafe(12) # pragma: allowlist secret
  15. async def _seed(db, *public_ids: str, level: str = "important") -> None:
  16. for pid in public_ids:
  17. db.add(
  18. Announcement(
  19. public_id=pid,
  20. level=level,
  21. texts=json.dumps({"en": {"title": f"T {pid}", "body": "B"}, "de": {"title": "D", "body": "B"}}),
  22. )
  23. )
  24. await db.commit()
  25. async def _setting(db, key: str, value: str) -> None:
  26. db.add(Settings(key=key, value=value))
  27. await db.commit()
  28. async def _admin(client: AsyncClient, username: str) -> dict:
  29. await client.post(
  30. "/api/v1/auth/setup",
  31. json={"auth_enabled": True, "admin_username": username, "admin_password": _FIXTURE_PW},
  32. )
  33. login = await client.post("/api/v1/auth/login", json={"username": username, "password": _FIXTURE_PW})
  34. assert login.status_code == 200, login.text
  35. return {"Authorization": f"Bearer {login.json()['access_token']}"}
  36. async def _user(client: AsyncClient, admin: dict, username: str) -> dict:
  37. grp = await client.post(
  38. "/api/v1/groups/", headers=admin, json={"name": f"ann_{username}", "permissions": ["printers:read"]}
  39. )
  40. assert grp.status_code == 201, grp.text
  41. created = await client.post(
  42. "/api/v1/users/",
  43. headers=admin,
  44. json={"username": username, "password": _FIXTURE_PW, "role": "user", "group_ids": [grp.json()["id"]]},
  45. )
  46. assert created.status_code == 201, created.text
  47. login = await client.post("/api/v1/auth/login", json={"username": username, "password": _FIXTURE_PW})
  48. return {"Authorization": f"Bearer {login.json()['access_token']}"}
  49. @pytest.mark.integration
  50. class TestAuthOff:
  51. @pytest.mark.asyncio
  52. async def test_listed_with_every_language_and_unread(self, async_client: AsyncClient, db_session):
  53. await _seed(db_session, "a1")
  54. r = await async_client.get(URL)
  55. assert r.status_code == 200
  56. [item] = r.json()
  57. assert item["id"] == "a1" and item["level"] == "important" and item["read"] is False
  58. assert set(item["texts"]) == {"en", "de"}
  59. @pytest.mark.asyncio
  60. async def test_read_sticks(self, async_client: AsyncClient, db_session):
  61. await _seed(db_session, "a1")
  62. assert (await async_client.post(f"{URL}/a1/read")).status_code == 204
  63. assert (await async_client.get(URL)).json()[0]["read"] is True
  64. @pytest.mark.asyncio
  65. async def test_unknown_id_is_404(self, async_client: AsyncClient):
  66. assert (await async_client.post(f"{URL}/nope/read")).status_code == 404
  67. @pytest.mark.asyncio
  68. async def test_switched_off_shows_nothing(self, async_client: AsyncClient, db_session):
  69. await _seed(db_session, "a1")
  70. await _setting(db_session, "announcements_enabled", "false")
  71. assert (await async_client.get(URL)).json() == []
  72. assert (await async_client.post(f"{URL}/a1/read")).status_code == 404
  73. @pytest.mark.integration
  74. class TestAuthOn:
  75. @pytest.mark.asyncio
  76. async def test_signed_out_is_401(self, async_client: AsyncClient, db_session):
  77. await _admin(async_client, "annadmin0")
  78. assert (await async_client.get(URL)).status_code == 401
  79. @pytest.mark.asyncio
  80. async def test_admin_sees_them_a_user_does_not(self, async_client: AsyncClient, db_session):
  81. await _seed(db_session, "a1")
  82. admin = await _admin(async_client, "annadmin1")
  83. user = await _user(async_client, admin, "annuser1")
  84. assert [i["id"] for i in (await async_client.get(URL, headers=admin)).json()] == ["a1"]
  85. assert (await async_client.get(URL, headers=user)).json() == []
  86. assert (await async_client.post(f"{URL}/a1/read", headers=user)).status_code == 404
  87. @pytest.mark.asyncio
  88. async def test_all_users_setting_shows_them_with_separate_read_state(self, async_client: AsyncClient, db_session):
  89. await _seed(db_session, "a1")
  90. admin = await _admin(async_client, "annadmin2")
  91. user = await _user(async_client, admin, "annuser2")
  92. await _setting(db_session, "announcements_all_users", "true")
  93. assert (await async_client.post(f"{URL}/a1/read", headers=user)).status_code == 204
  94. assert (await async_client.get(URL, headers=user)).json()[0]["read"] is True
  95. assert (await async_client.get(URL, headers=admin)).json()[0]["read"] is False
  96. @pytest.mark.integration
  97. class TestSettings:
  98. @pytest.mark.asyncio
  99. async def test_both_switches_round_trip(self, async_client: AsyncClient):
  100. defaults = (await async_client.get("/api/v1/settings/")).json()
  101. assert defaults["announcements_enabled"] is True
  102. assert defaults["announcements_all_users"] is False
  103. r = await async_client.put(
  104. "/api/v1/settings/", json={"announcements_enabled": False, "announcements_all_users": True}
  105. )
  106. assert r.status_code == 200, r.text
  107. after = (await async_client.get("/api/v1/settings/")).json()
  108. assert after["announcements_enabled"] is False
  109. assert after["announcements_all_users"] is True