"""工作区 / 成员 / 邀请。""" from __future__ import annotations from httpx import AsyncClient from .conftest import auth, create_workspace, login, unwrap async def test_create_workspace_makes_creator_owner(client: AsyncClient) -> None: user = await login(client) ws = await create_workspace(client, user, "达道科技") assert ws["role"] == "owner" assert ws["plan"] == "free" assert ws["slug"].startswith("ws-") async def test_workspace_appears_in_my_list_and_me(client: AsyncClient) -> None: user = await login(client) ws = await create_workspace(client, user, "个人工作区") listed = unwrap(await client.get("/api/v1/workspaces", headers=user["headers"])) assert [w["id"] for w in listed["items"]] == [ws["id"]] me = unwrap(await client.get("/api/v1/auth/me", headers=user["headers"])) assert me["workspaces"][0]["role"] == "owner" async def test_members_list_contains_owner_with_masked_phone(client: AsyncClient) -> None: user = await login(client) ws = await create_workspace(client, user, "团队A") page = unwrap( await client.get(f"/api/v1/workspaces/{ws['id']}/members", headers=user["headers"]) ) assert page["has_more"] is False member = page["items"][0] assert member["role"] == "owner" assert "****" in member["phone"] async def test_invite_and_join_flow(client: AsyncClient) -> None: owner = await login(client) guest = await login(client) ws = await create_workspace(client, owner, "协作空间") invite = unwrap( await client.post( f"/api/v1/workspaces/{ws['id']}/invitations", json={"role": "member", "expires_hours": 24}, headers=owner["headers"], ) ) assert len(invite["code"]) == 8 joined = unwrap( await client.post( f"/api/v1/workspaces/{ws['id']}/join", json={"code": invite["code"].lower()}, # 邀请码大小写不敏感 headers=guest["headers"], ) ) assert joined["role"] == "member" page = unwrap( await client.get(f"/api/v1/workspaces/{ws['id']}/members", headers=guest["headers"]) ) assert {m["role"] for m in page["items"]} == {"owner", "member"} async def test_invite_code_single_use(client: AsyncClient) -> None: owner = await login(client) second = await login(client) third = await login(client) ws = await create_workspace(client, owner, "限量空间") invite = unwrap( await client.post( f"/api/v1/workspaces/{ws['id']}/invitations", json={"role": "member"}, headers=owner["headers"], ) ) await client.post( f"/api/v1/workspaces/{ws['id']}/join", json={"code": invite["code"]}, headers=second["headers"], ) resp = await client.post( f"/api/v1/workspaces/{ws['id']}/join", json={"code": invite["code"]}, headers=third["headers"], ) assert resp.status_code == 400 assert resp.json()["code"] == 3010 async def test_bad_invite_code_rejected(client: AsyncClient) -> None: owner = await login(client) guest = await login(client) ws = await create_workspace(client, owner, "封闭空间") resp = await client.post( f"/api/v1/workspaces/{ws['id']}/join", json={"code": "ZZZZZZZZ"}, headers=guest["headers"], ) assert resp.json()["code"] == 3010 async def test_plain_member_cannot_invite(client: AsyncClient) -> None: owner = await login(client) member = await login(client) ws = await create_workspace(client, owner, "权限空间") invite = unwrap( await client.post( f"/api/v1/workspaces/{ws['id']}/invitations", json={"role": "member"}, headers=owner["headers"], ) ) await client.post( f"/api/v1/workspaces/{ws['id']}/join", json={"code": invite["code"]}, headers=member["headers"], ) resp = await client.post( f"/api/v1/workspaces/{ws['id']}/invitations", json={"role": "member"}, headers=member["headers"], ) assert resp.status_code == 403 assert resp.json()["code"] == 3004 async def test_only_owner_can_invite_admin(client: AsyncClient) -> None: owner = await login(client) admin = await login(client) ws = await create_workspace(client, owner, "治理空间") first = unwrap( await client.post( f"/api/v1/workspaces/{ws['id']}/invitations", json={"role": "admin"}, headers=owner["headers"], ) ) await client.post( f"/api/v1/workspaces/{ws['id']}/join", json={"code": first["code"]}, headers=admin["headers"], ) resp = await client.post( f"/api/v1/workspaces/{ws['id']}/invitations", json={"role": "admin"}, headers=admin["headers"], ) assert resp.json()["code"] == 3004 async def test_duplicate_slug_conflict(client: AsyncClient) -> None: user = await login(client) await client.post( "/api/v1/workspaces", json={"name": "占位", "slug": "taken"}, headers=user["headers"] ) resp = await client.post( "/api/v1/workspaces", json={"name": "抢占", "slug": "taken"}, headers=user["headers"] ) assert resp.status_code == 409 assert resp.json()["code"] == 1009 async def test_requires_auth(client: AsyncClient) -> None: resp = await client.get("/api/v1/workspaces") assert resp.status_code == 401 assert auth("bad.token.here") # 保证 helper 可用