Boards use /b/<uid> with no invite password; restore renamable columns, recordings, and seeded locale notes, with first column locked.
202 lines
7.6 KiB
Python
202 lines
7.6 KiB
Python
"""API tests for Stork MVP."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
def test_health(client: TestClient) -> None:
|
|
res = client.get("/api/health")
|
|
assert res.status_code == 200
|
|
assert res.json()["ok"] is True
|
|
|
|
|
|
def test_logos_gallery(client: TestClient) -> None:
|
|
res = client.get("/logos")
|
|
assert res.status_code == 200
|
|
assert b"Pick a Stork mark" in res.content
|
|
|
|
|
|
def test_home_is_paper_board(client: TestClient) -> None:
|
|
res = client.get("/")
|
|
assert res.status_code == 200
|
|
assert b"Start a private board" in res.content
|
|
|
|
|
|
def test_v2_redirects_home(client: TestClient) -> None:
|
|
res = client.get("/v2", follow_redirects=False)
|
|
assert res.status_code == 302
|
|
assert res.headers["location"] == "/"
|
|
|
|
|
|
def test_public_create_board_and_share_url(client: TestClient) -> None:
|
|
created = client.post("/api/boards", json={"title": "Baby Levkin"})
|
|
assert created.status_code == 200
|
|
board_id = created.json()["id"]
|
|
assert board_id.startswith("b_")
|
|
assert created.json()["url"].endswith(f"/b/{board_id}")
|
|
page = client.get(f"/b/{board_id}")
|
|
assert page.status_code == 200
|
|
assert b"Start a private board" in page.content or b"Your name" in page.content
|
|
|
|
|
|
def test_join_board_with_display_name(client: TestClient) -> None:
|
|
created = client.post("/api/boards", json={"title": "Friends"}).json()
|
|
session = client.post(
|
|
"/api/session",
|
|
json={"board_id": created["id"], "display_name": "Dan"},
|
|
)
|
|
assert session.status_code == 200
|
|
assert session.json()["board"]["id"] == created["id"]
|
|
assert client.post("/api/names", json={"kind": "c1", "spelling": "Noa"}).status_code == 200
|
|
|
|
|
|
def test_boards_are_isolated(client: TestClient) -> None:
|
|
a = client.post("/api/boards", json={"title": "A"}).json()
|
|
b = client.post("/api/boards", json={"title": "B"}).json()
|
|
client.post("/api/session", json={"board_id": a["id"], "display_name": "Ilia"})
|
|
assert client.post("/api/names", json={"kind": "c1", "spelling": "Roze"}).status_code == 200
|
|
client.post("/api/session", json={"board_id": b["id"], "display_name": "Dan"})
|
|
assert client.post("/api/names", json={"kind": "c1", "spelling": "Roze"}).status_code == 200
|
|
a_names = client.get(
|
|
"/api/names?kind=c1",
|
|
headers={"X-Stork-Board": a["id"], "X-Stork-Display-Name": "Ilia"},
|
|
).json()["items"]
|
|
b_names = client.get(
|
|
"/api/names?kind=c1",
|
|
headers={"X-Stork-Board": b["id"], "X-Stork-Display-Name": "Dan"},
|
|
).json()["items"]
|
|
assert len(a_names) == 1 and len(b_names) == 1
|
|
assert a_names[0]["id"] != b_names[0]["id"]
|
|
|
|
|
|
def test_default_one_column(authed: TestClient) -> None:
|
|
cols = authed.get("/api/columns").json()["items"]
|
|
assert len(cols) == 1
|
|
assert cols[0]["id"] == "c1"
|
|
assert cols[0]["label"] == "First"
|
|
|
|
|
|
def test_add_rename_columns(authed: TestClient) -> None:
|
|
c2 = authed.post("/api/columns")
|
|
assert c2.status_code == 200
|
|
assert c2.json()["id"] == "c2"
|
|
renamed = authed.patch("/api/columns/c2", json={"label": "Hebrew"})
|
|
assert renamed.status_code == 200
|
|
assert renamed.json()["label"] == "Hebrew"
|
|
assert authed.post("/api/names", json={"kind": "c2", "spelling": "שי"}).status_code == 200
|
|
|
|
|
|
def test_column_delete_by_member(authed: TestClient) -> None:
|
|
assert authed.post("/api/columns").status_code == 200
|
|
assert authed.delete("/api/columns/c2").status_code == 200
|
|
assert len(authed.get("/api/columns").json()["items"]) == 1
|
|
|
|
|
|
def test_cannot_delete_first_column(authed: TestClient) -> None:
|
|
assert authed.post("/api/columns").status_code == 200
|
|
assert authed.delete("/api/columns/c1").status_code == 400
|
|
assert len(authed.get("/api/columns").json()["items"]) == 2
|
|
|
|
|
|
def test_max_four_columns(authed: TestClient) -> None:
|
|
for _ in range(3):
|
|
assert authed.post("/api/columns").status_code == 200
|
|
assert authed.post("/api/columns").status_code == 400
|
|
|
|
|
|
def test_cannot_delete_last_column(authed: TestClient) -> None:
|
|
assert authed.delete("/api/columns/c1").status_code == 400
|
|
|
|
|
|
def test_recording_upload_and_play(authed: TestClient) -> None:
|
|
created = authed.post("/api/names", json={"kind": "c1", "spelling": "Roze"}).json()
|
|
name_id = created["id"]
|
|
files = {"file": ("voice.webm", b"fake-webm-bytes", "audio/webm")}
|
|
up = authed.post(f"/api/names/{name_id}/recording/en", files=files)
|
|
assert up.status_code == 200
|
|
assert up.json()["recordings"]["en"] is True
|
|
audio = authed.get(f"/api/names/{name_id}/recording/en")
|
|
assert audio.status_code == 200
|
|
assert audio.content == b"fake-webm-bytes"
|
|
|
|
|
|
def test_session_rejects_missing_board(client: TestClient) -> None:
|
|
res = client.post("/api/session", json={"display_name": "Ilia", "board_id": "b_missing"})
|
|
assert res.status_code == 404
|
|
|
|
|
|
def test_names_require_session(client: TestClient) -> None:
|
|
res = client.get("/api/names")
|
|
assert res.status_code in (401, 404)
|
|
|
|
|
|
def test_add_vote_and_rank(authed: TestClient) -> None:
|
|
a = authed.post(
|
|
"/api/names",
|
|
json={
|
|
"kind": "c1",
|
|
"spelling": "Noa",
|
|
"locales": {
|
|
"he": {"pronunciation": "No-ah", "origin": "Hebrew", "meaning": "motion"},
|
|
"en": {"pronunciation": "NO-uh", "origin": "Hebrew", "meaning": "movement"},
|
|
},
|
|
},
|
|
)
|
|
assert a.status_code == 200
|
|
noa_id = a.json()["id"]
|
|
b = authed.post("/api/names", json={"kind": "c1", "spelling": "Levi"})
|
|
levi_id = b.json()["id"]
|
|
assert authed.post(f"/api/names/{noa_id}/vote", json={"value": 1}).status_code == 200
|
|
assert authed.post(f"/api/names/{levi_id}/vote", json={"value": -1}).status_code == 200
|
|
listed = authed.get("/api/names?kind=c1").json()["items"]
|
|
assert [n["spelling"] for n in listed] == ["Noa", "Levi"]
|
|
|
|
|
|
def test_duplicate_name_rejected(authed: TestClient) -> None:
|
|
assert authed.post("/api/names", json={"kind": "c1", "spelling": "Maya"}).status_code == 200
|
|
assert authed.post("/api/names", json={"kind": "c1", "spelling": "maya"}).status_code == 400
|
|
|
|
|
|
def test_patch_locales(authed: TestClient) -> None:
|
|
created = authed.post("/api/names", json={"kind": "c1", "spelling": "Eden"}).json()
|
|
patched = authed.patch(
|
|
f"/api/names/{created['id']}",
|
|
json={"locales": {"ru": {"pronunciation": "Э-ден", "origin": "иврит", "meaning": "рай"}}},
|
|
)
|
|
assert patched.status_code == 200
|
|
assert patched.json()["locales"]["ru"]["meaning"] == "рай"
|
|
assert patched.json()["locales"]["en"]["pronunciation"] == ""
|
|
|
|
|
|
def test_admin_delete_name(authed: TestClient) -> None:
|
|
created = authed.post("/api/names", json={"kind": "c1", "spelling": "Temp"}).json()
|
|
assert authed.delete(f"/api/names/{created['id']}").status_code == 403
|
|
assert (
|
|
authed.delete(
|
|
f"/api/names/{created['id']}", headers={"X-Stork-Admin": "test-admin-token"}
|
|
).status_code
|
|
== 200
|
|
)
|
|
|
|
|
|
def test_header_auth_with_board(client: TestClient, family_board: dict) -> None:
|
|
headers = {
|
|
"X-Stork-Board": family_board["id"],
|
|
"X-Stork-Display-Name": "Seed Bot",
|
|
}
|
|
res = client.post(
|
|
"/api/names",
|
|
headers=headers,
|
|
json={"kind": "c1", "spelling": "HeaderOnly"},
|
|
)
|
|
assert res.status_code == 200
|
|
listed = client.get("/api/names?kind=c1", headers=headers)
|
|
assert any(n["spelling"] == "HeaderOnly" for n in listed.json()["items"])
|
|
|
|
|
|
def test_legacy_invite_resolve(client: TestClient, family_board: dict) -> None:
|
|
res = client.get("/api/resolve", params={"invite": "test-invite-token"})
|
|
assert res.status_code == 200
|
|
assert res.json()["id"] == family_board["id"]
|