mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-06 22:21:29 +02:00
Fetch a signed feed.json from the public bambuddy-notifications repo on GitHub at startup and every 6 hours. Nothing about the install is sent; targeting (version, beta channel, install type) is decided locally. - Ed25519 against a key built into the app; an older serial is refused so a withdrawn message can't come back. The feed replaces the stored list, and a failed or rejected fetch keeps the last good one. - Sidebar entry above System with an unread count, a slide-over list, and a banner for unread important/critical messages. Read state per user. - Admins by default; Settings > General > Updates can show them to all users or switch them off, which also stops the fetch. - Plain text only; links to github.com and bambuddy.cool only.
135 lines
5.5 KiB
Python
135 lines
5.5 KiB
Python
"""Who sees announcements, and per-user read state.
|
|
|
|
Administrators see them; other signed-in users only when
|
|
``announcements_all_users`` is on. With authentication off, the person running
|
|
Bambuddy sees them. Anyone else gets an empty list, which hides the sidebar entry
|
|
and the banner the same way "nothing published" does.
|
|
"""
|
|
|
|
import json
|
|
import secrets
|
|
|
|
import pytest
|
|
from httpx import AsyncClient
|
|
|
|
from backend.app.models.announcement import Announcement
|
|
from backend.app.models.settings import Settings
|
|
|
|
URL = "/api/v1/announcements"
|
|
_FIXTURE_PW = "Aa1!" + secrets.token_urlsafe(12) # pragma: allowlist secret
|
|
|
|
|
|
async def _seed(db, *public_ids: str, level: str = "important") -> None:
|
|
for pid in public_ids:
|
|
db.add(
|
|
Announcement(
|
|
public_id=pid,
|
|
level=level,
|
|
texts=json.dumps({"en": {"title": f"T {pid}", "body": "B"}, "de": {"title": "D", "body": "B"}}),
|
|
)
|
|
)
|
|
await db.commit()
|
|
|
|
|
|
async def _setting(db, key: str, value: str) -> None:
|
|
db.add(Settings(key=key, value=value))
|
|
await db.commit()
|
|
|
|
|
|
async def _admin(client: AsyncClient, username: str) -> dict:
|
|
await client.post(
|
|
"/api/v1/auth/setup",
|
|
json={"auth_enabled": True, "admin_username": username, "admin_password": _FIXTURE_PW},
|
|
)
|
|
login = await client.post("/api/v1/auth/login", json={"username": username, "password": _FIXTURE_PW})
|
|
assert login.status_code == 200, login.text
|
|
return {"Authorization": f"Bearer {login.json()['access_token']}"}
|
|
|
|
|
|
async def _user(client: AsyncClient, admin: dict, username: str) -> dict:
|
|
grp = await client.post(
|
|
"/api/v1/groups/", headers=admin, json={"name": f"ann_{username}", "permissions": ["printers:read"]}
|
|
)
|
|
assert grp.status_code == 201, grp.text
|
|
created = await client.post(
|
|
"/api/v1/users/",
|
|
headers=admin,
|
|
json={"username": username, "password": _FIXTURE_PW, "role": "user", "group_ids": [grp.json()["id"]]},
|
|
)
|
|
assert created.status_code == 201, created.text
|
|
login = await client.post("/api/v1/auth/login", json={"username": username, "password": _FIXTURE_PW})
|
|
return {"Authorization": f"Bearer {login.json()['access_token']}"}
|
|
|
|
|
|
@pytest.mark.integration
|
|
class TestAuthOff:
|
|
@pytest.mark.asyncio
|
|
async def test_listed_with_every_language_and_unread(self, async_client: AsyncClient, db_session):
|
|
await _seed(db_session, "a1")
|
|
r = await async_client.get(URL)
|
|
assert r.status_code == 200
|
|
[item] = r.json()
|
|
assert item["id"] == "a1" and item["level"] == "important" and item["read"] is False
|
|
assert set(item["texts"]) == {"en", "de"}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_read_sticks(self, async_client: AsyncClient, db_session):
|
|
await _seed(db_session, "a1")
|
|
assert (await async_client.post(f"{URL}/a1/read")).status_code == 204
|
|
assert (await async_client.get(URL)).json()[0]["read"] is True
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unknown_id_is_404(self, async_client: AsyncClient):
|
|
assert (await async_client.post(f"{URL}/nope/read")).status_code == 404
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_switched_off_shows_nothing(self, async_client: AsyncClient, db_session):
|
|
await _seed(db_session, "a1")
|
|
await _setting(db_session, "announcements_enabled", "false")
|
|
assert (await async_client.get(URL)).json() == []
|
|
assert (await async_client.post(f"{URL}/a1/read")).status_code == 404
|
|
|
|
|
|
@pytest.mark.integration
|
|
class TestAuthOn:
|
|
@pytest.mark.asyncio
|
|
async def test_signed_out_is_401(self, async_client: AsyncClient, db_session):
|
|
await _admin(async_client, "annadmin0")
|
|
assert (await async_client.get(URL)).status_code == 401
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_admin_sees_them_a_user_does_not(self, async_client: AsyncClient, db_session):
|
|
await _seed(db_session, "a1")
|
|
admin = await _admin(async_client, "annadmin1")
|
|
user = await _user(async_client, admin, "annuser1")
|
|
assert [i["id"] for i in (await async_client.get(URL, headers=admin)).json()] == ["a1"]
|
|
assert (await async_client.get(URL, headers=user)).json() == []
|
|
assert (await async_client.post(f"{URL}/a1/read", headers=user)).status_code == 404
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_all_users_setting_shows_them_with_separate_read_state(self, async_client: AsyncClient, db_session):
|
|
await _seed(db_session, "a1")
|
|
admin = await _admin(async_client, "annadmin2")
|
|
user = await _user(async_client, admin, "annuser2")
|
|
await _setting(db_session, "announcements_all_users", "true")
|
|
|
|
assert (await async_client.post(f"{URL}/a1/read", headers=user)).status_code == 204
|
|
assert (await async_client.get(URL, headers=user)).json()[0]["read"] is True
|
|
assert (await async_client.get(URL, headers=admin)).json()[0]["read"] is False
|
|
|
|
|
|
@pytest.mark.integration
|
|
class TestSettings:
|
|
@pytest.mark.asyncio
|
|
async def test_both_switches_round_trip(self, async_client: AsyncClient):
|
|
defaults = (await async_client.get("/api/v1/settings/")).json()
|
|
assert defaults["announcements_enabled"] is True
|
|
assert defaults["announcements_all_users"] is False
|
|
r = await async_client.put(
|
|
"/api/v1/settings/", json={"announcements_enabled": False, "announcements_all_users": True}
|
|
)
|
|
assert r.status_code == 200, r.text
|
|
after = (await async_client.get("/api/v1/settings/")).json()
|
|
assert after["announcements_enabled"] is False
|
|
assert after["announcements_all_users"] is True
|