import uuid import pytest from fastapi.testclient import TestClient from tests.conftest import ( admin_token, approve_cloud, bearer, png_bytes, register_and_confirm, ) # --------------------------------------------------------------------------- # get profile # --------------------------------------------------------------------------- def test_get_profile(client: TestClient, monkeypatch) -> None: info = register_and_confirm( client, monkeypatch, f"prof-{uuid.uuid4().hex[:8]}@example.com", "profpass1", "资料展示", ) r = client.get(f"/api/v1/profiles/{info['user_id']}") assert r.status_code == 200 assert r.json()["username"] == "资料展示" def test_get_nonexistent_profile(client: TestClient) -> None: r = client.get(f"/api/v1/profiles/{uuid.uuid4()}") assert r.status_code == 404 # --------------------------------------------------------------------------- # update my profile # --------------------------------------------------------------------------- def test_update_me(client: TestClient, monkeypatch) -> None: info = register_and_confirm( client, monkeypatch, f"updme-{uuid.uuid4().hex[:8]}@example.com", "updmepass", "原昵称", ) r = client.patch( "/api/v1/profiles/me", headers=bearer(info["token"]), json={"username": "新昵称"}, ) assert r.status_code == 200 assert r.json()["username"] == "新昵称" def test_update_me_short_username(client: TestClient, monkeypatch) -> None: info = register_and_confirm( client, monkeypatch, f"shortname-{uuid.uuid4().hex[:8]}@example.com", "shortname", "短名字测试", ) r = client.patch( "/api/v1/profiles/me", headers=bearer(info["token"]), json={"username": "A"}, ) assert r.status_code == 422 def test_update_me_duplicate_username(client: TestClient, monkeypatch) -> None: register_and_confirm( client, monkeypatch, f"dupname-u1-{uuid.uuid4().hex[:8]}@example.com", "pass11112", "已占用的名字", ) info2 = register_and_confirm( client, monkeypatch, f"dupname-u2-{uuid.uuid4().hex[:8]}@example.com", "pass22223", "第二人", ) r = client.patch( "/api/v1/profiles/me", headers=bearer(info2["token"]), json={"username": "已占用的名字"}, ) assert r.status_code == 409 assert "昵称" in r.json()["detail"] # --------------------------------------------------------------------------- # get user clouds # --------------------------------------------------------------------------- def test_get_user_clouds_public(client: TestClient, monkeypatch) -> None: info = register_and_confirm( client, monkeypatch, f"pubcloud-{uuid.uuid4().hex[:8]}@example.com", "pubcloud1", "公开云主", ) ur = client.post( "/api/v1/clouds", headers=bearer(info["token"]), files={"image": ("cloud.png", png_bytes(), "image/png")}, data={"cloud_type_id": "1", "is_hidden": "false"}, ) approve_cloud(client, admin_token(client), ur.json()["cloud"]["id"]) r = client.get(f"/api/v1/profiles/{info['user_id']}/clouds") assert r.status_code == 200 assert r.json()["total"] >= 1 for item in r.json()["items"]: assert item["status"] == "approved" def test_get_my_clouds_via_me(client: TestClient, monkeypatch) -> None: info = register_and_confirm( client, monkeypatch, f"myclouds-{uuid.uuid4().hex[:8]}@example.com", "myclouds1", "me 云图", ) client.post( "/api/v1/clouds", headers=bearer(info["token"]), files={"image": ("cloud.png", png_bytes(), "image/png")}, data={"cloud_type_id": "1", "is_hidden": "false"}, ) r = client.get("/api/v1/profiles/me/clouds", headers=bearer(info["token"])) assert r.status_code == 200 assert r.json()["total"] >= 1 # 本人可以看到 pending 状态 assert r.json()["items"][0]["status"] == "pending" def test_get_my_clouds_via_me_unauthenticated(client: TestClient) -> None: r = client.get("/api/v1/profiles/me/clouds") assert r.status_code == 401 def test_get_user_clouds_as_owner(client: TestClient, monkeypatch) -> None: info = register_and_confirm( client, monkeypatch, f"owncloud-{uuid.uuid4().hex[:8]}@example.com", "owncloud1", "私有云主", ) ur = client.post( "/api/v1/clouds", headers=bearer(info["token"]), files={"image": ("cloud.png", png_bytes(), "image/png")}, data={"cloud_type_id": "1", "is_hidden": "false"}, ) approve_cloud(client, admin_token(client), ur.json()["cloud"]["id"]) r = client.get( f"/api/v1/profiles/{info['user_id']}/clouds", headers=bearer(info["token"]), ) assert r.status_code == 200 assert r.json()["total"] >= 1 def test_get_nonexistent_user_clouds(client: TestClient) -> None: r = client.get(f"/api/v1/profiles/{uuid.uuid4()}/clouds") assert r.status_code == 404 # --------------------------------------------------------------------------- # /me profile shortcut # --------------------------------------------------------------------------- def test_get_my_profile_via_me(client: TestClient, monkeypatch) -> None: info = register_and_confirm( client, monkeypatch, f"me-prof-{uuid.uuid4().hex[:8]}@example.com", "meprof12", "me 资料", ) r = client.get("/api/v1/profiles/me", headers=bearer(info["token"])) assert r.status_code == 200 assert r.json()["username"] == "me 资料" assert r.json()["id"] == info["user_id"] def test_get_my_profile_via_me_unauthenticated(client: TestClient) -> None: r = client.get("/api/v1/profiles/me") assert r.status_code == 401 # --------------------------------------------------------------------------- # stats # --------------------------------------------------------------------------- def test_get_my_stats_via_me(client: TestClient, monkeypatch) -> None: info = register_and_confirm( client, monkeypatch, f"mestats-{uuid.uuid4().hex[:8]}@example.com", "mestatsp", "me 统计", ) client.post( "/api/v1/clouds", headers=bearer(info["token"]), files={"image": ("cloud.png", png_bytes(), "image/png")}, data={"cloud_type_id": "1", "is_hidden": "false"}, ) r = client.get("/api/v1/profiles/me/stats", headers=bearer(info["token"])) assert r.status_code == 200 body = r.json() assert body["cloud_count"] == 1 assert body["username"] == "me 统计" assert body["user_id"] == info["user_id"] def test_get_my_stats_via_user_id(client: TestClient, monkeypatch) -> None: info = register_and_confirm( client, monkeypatch, f"selfstats-{uuid.uuid4().hex[:8]}@example.com", "selfstat", "自统计", ) r = client.get( f"/api/v1/profiles/{info['user_id']}/stats", headers=bearer(info["token"]), ) assert r.status_code == 200 assert r.json()["user_id"] == info["user_id"] def test_get_user_stats_admin(client: TestClient, monkeypatch) -> None: info = register_and_confirm( client, monkeypatch, f"stats-{uuid.uuid4().hex[:8]}@example.com", "statspass", "统计用户", ) ur1 = client.post( "/api/v1/clouds", headers=bearer(info["token"]), files={"image": ("cloud.png", png_bytes(), "image/png")}, data={"cloud_type_id": "1", "is_hidden": "false"}, ) approve_cloud(client, admin_token(client), ur1.json()["cloud"]["id"]) client.post( "/api/v1/clouds", headers=bearer(info["token"]), files={"image": ("cloud.png", png_bytes(), "image/png")}, data={"cloud_type_id": "2", "is_hidden": "false"}, ) r = client.get( f"/api/v1/profiles/{info['user_id']}/stats", headers=bearer(admin_token(client)), ) assert r.status_code == 200 body = r.json() assert body["cloud_count"] == 2 assert body["public_cloud_count"] == 1 assert body["collection_count"] == 2 assert body["username"] == "统计用户" def test_get_user_stats_other_user_forbidden(client: TestClient, monkeypatch) -> None: info_a = register_and_confirm( client, monkeypatch, f"stats-a-{uuid.uuid4().hex[:8]}@example.com", "statsapp1", "A 的统计", ) info_b = register_and_confirm( client, monkeypatch, f"stats-b-{uuid.uuid4().hex[:8]}@example.com", "statsbpp2", "B 的统计", ) r = client.get( f"/api/v1/profiles/{info_a['user_id']}/stats", headers=bearer(info_b["token"]), ) assert r.status_code == 403 def test_get_user_stats_unauthenticated(client: TestClient) -> None: r = client.get(f"/api/v1/profiles/{uuid.uuid4()}/stats") assert r.status_code == 401 def test_get_user_stats_nonexistent(client: TestClient) -> None: r = client.get( f"/api/v1/profiles/{uuid.uuid4()}/stats", headers=bearer(admin_token(client)), ) assert r.status_code == 404 # --------------------------------------------------------------------------- # avatar # --------------------------------------------------------------------------- def test_upload_avatar(client: TestClient, monkeypatch) -> None: info = register_and_confirm( client, monkeypatch, f"avatar-{uuid.uuid4().hex[:8]}@example.com", "avatarp12", "头像用户", ) r = client.post( "/api/v1/profiles/me/avatar", headers=bearer(info["token"]), files={"image": ("avatar.png", png_bytes(), "image/png")}, ) assert r.status_code == 200 body = r.json() assert body["avatar_url"] is not None assert "avatars/" in body["avatar_url"] def test_replace_avatar(client: TestClient, monkeypatch) -> None: info = register_and_confirm( client, monkeypatch, f"avat2-{uuid.uuid4().hex[:8]}@example.com", "avat2p12", "换头像", ) r1 = client.post( "/api/v1/profiles/me/avatar", headers=bearer(info["token"]), files={"image": ("avatar.png", png_bytes(), "image/png")}, ) assert r1.status_code == 200 assert r1.json()["avatar_url"] is not None r2 = client.post( "/api/v1/profiles/me/avatar", headers=bearer(info["token"]), files={"image": ("avatar2.png", png_bytes(), "image/png")}, ) assert r2.status_code == 200 assert r2.json()["avatar_url"] is not None def test_delete_avatar(client: TestClient, monkeypatch) -> None: info = register_and_confirm( client, monkeypatch, f"avat3-{uuid.uuid4().hex[:8]}@example.com", "avat3p12", "删头像", ) client.post( "/api/v1/profiles/me/avatar", headers=bearer(info["token"]), files={"image": ("avatar.png", png_bytes(), "image/png")}, ) r = client.delete("/api/v1/profiles/me/avatar", headers=bearer(info["token"])) assert r.status_code == 200 assert r.json()["avatar_url"] is None def test_delete_avatar_not_found(client: TestClient, monkeypatch) -> None: info = register_and_confirm( client, monkeypatch, f"avat4-{uuid.uuid4().hex[:8]}@example.com", "avat4p12", "没头像删", ) r = client.delete("/api/v1/profiles/me/avatar", headers=bearer(info["token"])) assert r.status_code == 404 def test_upload_avatar_unauthenticated(client: TestClient) -> None: r = client.post( "/api/v1/profiles/me/avatar", files={"image": ("avatar.png", png_bytes(), "image/png")}, ) assert r.status_code == 401 def test_delete_avatar_unauthenticated(client: TestClient) -> None: r = client.delete("/api/v1/profiles/me/avatar") assert r.status_code == 401