Spaces:
Running
Running
Update tests/test_e2e.py
Browse files- tests/test_e2e.py +143 -2
tests/test_e2e.py
CHANGED
|
@@ -198,9 +198,9 @@ async def main():
|
|
| 198 |
await bob.send({"type": "load_messages", "conversation_id": 1, "limit": 100})
|
| 199 |
bl = await bob.expect("messages_loaded")
|
| 200 |
check(all(m["id"] != dmsg["id"] for m in bl["messages"]), "deleted msg absent from history")
|
| 201 |
-
# double delete is idempotent
|
| 202 |
r = await http.delete(f"{BASE}/api/messages/{dmsg['id']}", headers={"X-Auth-Token": alice.token})
|
| 203 |
-
check(r.status_code ==
|
| 204 |
# deleting someone else's message forbidden
|
| 205 |
r = await http.delete(f"{BASE}/api/messages/{mid}", headers={"X-Auth-Token": bob.token})
|
| 206 |
check(r.status_code == 404 or r.status_code == 403, "can't delete others' msg", r.text[:100])
|
|
@@ -325,6 +325,147 @@ async def main():
|
|
| 325 |
allmsg = (await alice.expect("messages_loaded"))["messages"]
|
| 326 |
check(sum(1 for m in allmsg if m["content"] == "dedupe me") == 1, "no duplicate messages stored")
|
| 327 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 328 |
# ---- auth edge ----
|
| 329 |
r = await http.get(BASE + "/api/auth/verify", headers={"X-Auth-Token": "bogus"})
|
| 330 |
check(r.status_code == 401, "bad token rejected")
|
|
|
|
| 198 |
await bob.send({"type": "load_messages", "conversation_id": 1, "limit": 100})
|
| 199 |
bl = await bob.expect("messages_loaded")
|
| 200 |
check(all(m["id"] != dmsg["id"] for m in bl["messages"]), "deleted msg absent from history")
|
| 201 |
+
# double delete is idempotent (200 "already_deleted")
|
| 202 |
r = await http.delete(f"{BASE}/api/messages/{dmsg['id']}", headers={"X-Auth-Token": alice.token})
|
| 203 |
+
check(r.status_code == 200, "second delete is a no-op (no crash)")
|
| 204 |
# deleting someone else's message forbidden
|
| 205 |
r = await http.delete(f"{BASE}/api/messages/{mid}", headers={"X-Auth-Token": bob.token})
|
| 206 |
check(r.status_code == 404 or r.status_code == 403, "can't delete others' msg", r.text[:100])
|
|
|
|
| 325 |
allmsg = (await alice.expect("messages_loaded"))["messages"]
|
| 326 |
check(sum(1 for m in allmsg if m["content"] == "dedupe me") == 1, "no duplicate messages stored")
|
| 327 |
|
| 328 |
+
# ---- group chats, invites, blocking and renaming ----
|
| 329 |
+
await alice.drain(0.4)
|
| 330 |
+
await bob.drain(0.4)
|
| 331 |
+
r = await http.post(f"{BASE}/api/conversations/group?name=Group Test&member_ids={bob.user['id']}",
|
| 332 |
+
headers={"X-Auth-Token": alice.token})
|
| 333 |
+
check(r.status_code == 200, "group create ok", r.text[:200])
|
| 334 |
+
group = r.json()["conversation"]
|
| 335 |
+
gid = group["id"]
|
| 336 |
+
check(group["is_group"] is True and group["role"] == "owner", "creator is group owner")
|
| 337 |
+
|
| 338 |
+
# bob receives a pending invite and must accept before chatting
|
| 339 |
+
pending = await bob.expect("conversation_updated", 6)
|
| 340 |
+
check(pending["conversation"]["id"] == gid and pending["conversation"]["is_pending"],
|
| 341 |
+
"bob gets pending group invite")
|
| 342 |
+
r = await http.post(f"{BASE}/api/conversations/{gid}/accept", headers={"X-Auth-Token": bob.token})
|
| 343 |
+
check(r.status_code == 200 and r.json()["conversation"]["is_pending"] is False,
|
| 344 |
+
"bob accepts invite")
|
| 345 |
+
await alice.drain(0.3)
|
| 346 |
+
await bob.drain(0.3)
|
| 347 |
+
|
| 348 |
+
# rename the group; everyone else sees it
|
| 349 |
+
r = await http.patch(f"{BASE}/api/conversations/{gid}?name=Renamed Group",
|
| 350 |
+
headers={"X-Auth-Token": alice.token})
|
| 351 |
+
check(r.status_code == 200 and r.json()["conversation"]["custom_name"] == "Renamed Group",
|
| 352 |
+
"group rename ok")
|
| 353 |
+
renamed = await bob.expect("conversation_updated", 6)
|
| 354 |
+
check(renamed["conversation"]["custom_name"] == "Renamed Group", "bob sees renamed group")
|
| 355 |
+
|
| 356 |
+
# non-owner leaves; owner can delete afterwards
|
| 357 |
+
r = await http.delete(f"{BASE}/api/conversations/{gid}", headers={"X-Auth-Token": bob.token})
|
| 358 |
+
check(r.status_code == 200 and r.json()["status"] == "left", "group member can leave")
|
| 359 |
+
r = await http.delete(f"{BASE}/api/conversations/{gid}", headers={"X-Auth-Token": alice.token})
|
| 360 |
+
check(r.status_code == 200 and r.json()["status"] == "deleted", "owner can delete group")
|
| 361 |
+
|
| 362 |
+
# ---- group invites stay acceptable and pending users cannot act ----
|
| 363 |
+
charlie = Client("charlie")
|
| 364 |
+
r = await http.post(f"{BASE}/api/auth/signup?username=charlie&password=password123&display_name=Charlie")
|
| 365 |
+
check(r.status_code == 200, "signup charlie", r.text[:200])
|
| 366 |
+
charlie.token = r.json()["token"]
|
| 367 |
+
charlie.user = r.json()["user"]
|
| 368 |
+
|
| 369 |
+
await alice.drain(0.3)
|
| 370 |
+
await bob.drain(0.3)
|
| 371 |
+
r = await http.post(
|
| 372 |
+
f"{BASE}/api/conversations/group?name=Invite Group&member_ids={bob.user['id']},{charlie.user['id']}",
|
| 373 |
+
headers={"X-Auth-Token": alice.token})
|
| 374 |
+
check(r.status_code == 200, "group with two invitees created", r.text[:200])
|
| 375 |
+
g2id = r.json()["conversation"]["id"]
|
| 376 |
+
|
| 377 |
+
pend = await bob.expect("conversation_updated", 6)
|
| 378 |
+
check(pend["conversation"]["id"] == g2id and pend["conversation"]["is_pending"],
|
| 379 |
+
"second group invite arrives as pending")
|
| 380 |
+
|
| 381 |
+
# A pending invitee cannot leave, rename, send, or add members.
|
| 382 |
+
r = await http.delete(f"{BASE}/api/conversations/{g2id}", headers={"X-Auth-Token": bob.token})
|
| 383 |
+
check(r.status_code == 403, "pending user cannot leave", r.text[:120])
|
| 384 |
+
r = await http.patch(f"{BASE}/api/conversations/{g2id}?name=Nope", headers={"X-Auth-Token": bob.token})
|
| 385 |
+
check(r.status_code == 403, "pending user cannot rename", r.text[:120])
|
| 386 |
+
await bob.send({"type": "send_message", "conversation_id": g2id,
|
| 387 |
+
"content": "should not send", "client_id": "pending-send"})
|
| 388 |
+
send_err = await bob.expect("error", 6)
|
| 389 |
+
check(send_err.get("code") == "FORBIDDEN", "pending user cannot send", send_err.get("message", ""))
|
| 390 |
+
r = await http.post(f"{BASE}/api/conversations/{g2id}/members?member_ids={alice.user['id']}",
|
| 391 |
+
headers={"X-Auth-Token": bob.token})
|
| 392 |
+
check(r.status_code == 403, "pending user cannot add members", r.text[:120])
|
| 393 |
+
|
| 394 |
+
# Rejecting removes the pending row.
|
| 395 |
+
r = await http.post(f"{BASE}/api/conversations/{g2id}/reject", headers={"X-Auth-Token": bob.token})
|
| 396 |
+
check(r.status_code == 200 and r.json()["status"] == "rejected", "pending user can reject invite")
|
| 397 |
+
|
| 398 |
+
# A future re-invite must go through acceptance again.
|
| 399 |
+
await alice.drain(0.3)
|
| 400 |
+
await bob.drain(0.3)
|
| 401 |
+
r = await http.post(f"{BASE}/api/conversations/{g2id}/members?member_ids={bob.user['id']}",
|
| 402 |
+
headers={"X-Auth-Token": alice.token})
|
| 403 |
+
check(r.status_code == 200, "owner can re-invite after reject", r.text[:200])
|
| 404 |
+
re_pend = await bob.expect("conversation_updated", 6)
|
| 405 |
+
check(re_pend["conversation"]["id"] == g2id and re_pend["conversation"]["is_pending"],
|
| 406 |
+
"re-invite needs acceptance again")
|
| 407 |
+
r = await http.post(f"{BASE}/api/conversations/{g2id}/accept", headers={"X-Auth-Token": bob.token})
|
| 408 |
+
check(r.status_code == 200 and r.json()["conversation"]["is_pending"] is False,
|
| 409 |
+
"bob accepts the re-invite")
|
| 410 |
+
r = await http.post(f"{BASE}/api/conversations/{g2id}/accept", headers={"X-Auth-Token": charlie.token})
|
| 411 |
+
check(r.status_code == 200 and r.json()["conversation"]["is_pending"] is False,
|
| 412 |
+
"charlie accepts its own invite independently")
|
| 413 |
+
await alice.drain(0.3)
|
| 414 |
+
await bob.drain(0.3)
|
| 415 |
+
|
| 416 |
+
# Owner can also delete a group that still has pending/unopened invites.
|
| 417 |
+
r = await http.delete(f"{BASE}/api/conversations/{g2id}", headers={"X-Auth-Token": alice.token})
|
| 418 |
+
check(r.status_code == 200 and r.json()["status"] == "deleted", "owner can delete group with invites")
|
| 419 |
+
|
| 420 |
+
# blocking hides all existing chats but does not delete them
|
| 421 |
+
pre_block = await http.get(f"{BASE}/api/conversations", headers={"X-Auth-Token": alice.token})
|
| 422 |
+
check(any(c["id"] == dmid for c in pre_block.json()["conversations"]), "dm exists before block")
|
| 423 |
+
r = await http.post(f"{BASE}/api/users/{bob.user['id']}/block",
|
| 424 |
+
headers={"X-Auth-Token": alice.token})
|
| 425 |
+
check(r.status_code == 200, "alice blocks bob")
|
| 426 |
+
blocked_event = await bob.expect("block_changed", 6)
|
| 427 |
+
check(blocked_event["blocked"] is True and blocked_event["blocker_id"] == alice.user["id"],
|
| 428 |
+
"blocked user is notified")
|
| 429 |
+
r = await http.get(f"{BASE}/api/conversations", headers={"X-Auth-Token": alice.token})
|
| 430 |
+
check(all(c["id"] != dmid for c in r.json()["conversations"]), "blocked dm hidden for blocker")
|
| 431 |
+
r = await http.get(f"{BASE}/api/conversations", headers={"X-Auth-Token": bob.token})
|
| 432 |
+
check(all(c["id"] != dmid for c in r.json()["conversations"]), "blocked dm hidden for blocked user")
|
| 433 |
+
r = await http.post(f"{BASE}/api/conversations/dm?user_id={alice.user['id']}",
|
| 434 |
+
headers={"X-Auth-Token": bob.token})
|
| 435 |
+
check(r.status_code == 403, "blocked user cannot start new dm")
|
| 436 |
+
|
| 437 |
+
# the global room must stay usable while a pair is blocked
|
| 438 |
+
await alice.drain(0.3)
|
| 439 |
+
await bob.drain(0.3)
|
| 440 |
+
await alice.send({"type": "send_message", "content": "global while blocked",
|
| 441 |
+
"conversation_id": 1, "client_id": "gblock1"})
|
| 442 |
+
echo = await alice.expect("new_message")
|
| 443 |
+
check(echo["message"]["content"] == "global while blocked",
|
| 444 |
+
"blocker can still use the global room")
|
| 445 |
+
got = await wait_events([bob], "new_message")
|
| 446 |
+
check(any(m["message"].get("content") == "global while blocked" for m in got["bob"]),
|
| 447 |
+
"blocked user still receives global-room messages", str(got["bob"][:1]))
|
| 448 |
+
await alice.drain(0.2)
|
| 449 |
+
await bob.drain(0.2)
|
| 450 |
+
await bob.send({"type": "send_message", "content": "from blocked user in global",
|
| 451 |
+
"conversation_id": 1, "client_id": "gblock2"})
|
| 452 |
+
echo = await bob.expect("new_message")
|
| 453 |
+
check(echo["message"]["content"] == "from blocked user in global",
|
| 454 |
+
"blocked user can still send in the global room")
|
| 455 |
+
got = await wait_events([alice], "new_message")
|
| 456 |
+
check(any(m["message"].get("content") == "from blocked user in global" for m in got["alice"]),
|
| 457 |
+
"blocker still receives messages from blocked user in global room", str(got["alice"][:1]))
|
| 458 |
+
|
| 459 |
+
# unblock restores the hidden chat (still stored, never deleted)
|
| 460 |
+
r = await http.delete(f"{BASE}/api/users/{bob.user['id']}/block",
|
| 461 |
+
headers={"X-Auth-Token": alice.token})
|
| 462 |
+
check(r.status_code == 200, "unblock ok")
|
| 463 |
+
await alice.drain(0.3)
|
| 464 |
+
await bob.drain(0.3)
|
| 465 |
+
after = await http.get(f"{BASE}/api/conversations", headers={"X-Auth-Token": alice.token})
|
| 466 |
+
check(any(c["id"] == dmid for c in after.json()["conversations"]),
|
| 467 |
+
"hidden dm returns after unblock")
|
| 468 |
+
|
| 469 |
# ---- auth edge ----
|
| 470 |
r = await http.get(BASE + "/api/auth/verify", headers={"X-Auth-Token": "bogus"})
|
| 471 |
check(r.status_code == 401, "bad token rejected")
|