feat: 修复 H5 底部导航覆盖 + 更新项目进度文档

## H5 底部导航修复 (Bug #10)
- 精简 App.vue,移除重复 tabbar,仅保留全局样式
- uni-page 设置 height: calc(100% - 50px) + overflow-y: auto
- 内容区域精确停在底部导航上方,独立滚动不再叠加
- 恢复 custom-tab-bar 组件

## 项目进度文档
- PROGRESS.md 更新至 10 个 Bug 修复
- 新增 H5 底部导航修复记录
- 新增历史变更条目
This commit is contained in:
TradeMate Dev
2026-05-12 20:24:42 +08:00
parent 69e164dcae
commit 7b62c2f8b4
125 changed files with 19725 additions and 728 deletions
+156
View File
@@ -0,0 +1,156 @@
import pytest
from httpx import AsyncClient
from app.models.notification import Notification
from app.models.feedback import Feedback
class TestNotificationAPI:
async def test_list_notifications_empty(self, client: AsyncClient, auth_headers):
response = await client.get("/api/v1/notifications", headers=auth_headers)
assert response.status_code == 200
data = response.json()
assert "items" in data
assert data["items"] == []
async def test_unread_count_zero(self, client: AsyncClient, auth_headers):
response = await client.get("/api/v1/notifications/unread-count", headers=auth_headers)
assert response.status_code == 200
assert response.json()["count"] == 0
async def test_create_and_list_notification(self, client: AsyncClient, auth_headers, db_session, test_user):
n = Notification(
user_id=test_user.id,
title="Test Title",
content="Test Content",
)
db_session.add(n)
await db_session.commit()
response = await client.get("/api/v1/notifications", headers=auth_headers)
assert response.status_code == 200
data = response.json()
assert len(data["items"]) >= 1
assert data["items"][0]["title"] == "Test Title"
async def test_mark_read(self, client: AsyncClient, auth_headers, db_session, test_user):
n = Notification(user_id=test_user.id, title="Read Test", content="Content")
db_session.add(n)
await db_session.commit()
response = await client.patch(
f"/api/v1/notifications/{n.id}/read",
headers=auth_headers,
)
assert response.status_code == 200
count_resp = await client.get("/api/v1/notifications/unread-count", headers=auth_headers)
assert count_resp.json()["count"] == 0
async def test_mark_all_read(self, client: AsyncClient, auth_headers, db_session, test_user):
for i in range(3):
db_session.add(Notification(user_id=test_user.id, title=f"Notif {i}", content="Content"))
await db_session.commit()
response = await client.post("/api/v1/notifications/read-all", headers=auth_headers)
assert response.status_code == 200
assert response.json()["count"] == 3
async def test_delete_notification(self, client: AsyncClient, auth_headers, db_session, test_user):
n = Notification(user_id=test_user.id, title="Delete Me", content="Content")
db_session.add(n)
await db_session.commit()
nid = n.id
response = await client.delete(f"/api/v1/notifications/{nid}", headers=auth_headers)
assert response.status_code == 200
list_resp = await client.get("/api/v1/notifications", headers=auth_headers)
ids = [item["id"] for item in list_resp.json()["items"]]
assert str(nid) not in ids
async def test_delete_not_found(self, client: AsyncClient, auth_headers):
import uuid
response = await client.delete(
f"/api/v1/notifications/{uuid.uuid4()}",
headers=auth_headers,
)
assert response.status_code == 404
async def test_unread_count_after_read(self, client: AsyncClient, auth_headers, db_session, test_user):
for i in range(2):
db_session.add(Notification(user_id=test_user.id, title=f"Unread {i}", content="C"))
await db_session.commit()
resp = await client.get("/api/v1/notifications/unread-count", headers=auth_headers)
assert resp.json()["count"] == 2
async def test_unread_only_filter(self, client: AsyncClient, auth_headers, db_session, test_user):
n1 = Notification(user_id=test_user.id, title="Read", content="C", is_read=True)
n2 = Notification(user_id=test_user.id, title="Unread", content="C")
db_session.add_all([n1, n2])
await db_session.commit()
response = await client.get(
"/api/v1/notifications?unread_only=true",
headers=auth_headers,
)
assert response.status_code == 200
for item in response.json()["items"]:
assert item["is_read"] is False
class TestFeedbackAPI:
async def test_submit_feedback(self, client: AsyncClient, auth_headers):
response = await client.post(
"/api/v1/feedback",
headers=auth_headers,
json={
"content": "Great app!",
"category": "feature",
"contact": "test@example.com",
},
)
assert response.status_code == 200
assert response.json()["status"] == "ok"
async def test_submit_feedback_minimal(self, client: AsyncClient, auth_headers):
response = await client.post(
"/api/v1/feedback",
headers=auth_headers,
json={"content": "Bug report"},
)
assert response.status_code == 200
assert response.json()["status"] == "ok"
async def test_submit_feedback_unauthorized(self, client: AsyncClient):
response = await client.post(
"/api/v1/feedback",
json={"content": "Test"},
)
assert response.status_code == 401
class TestPaymentAPI:
async def test_get_plans(self, client: AsyncClient, auth_headers):
response = await client.get("/api/v1/payment/plans", headers=auth_headers)
assert response.status_code == 200
data = response.json()
assert "plans" in data
assert len(data["plans"]) >= 3
async def test_get_subscription_free(self, client: AsyncClient, auth_headers):
response = await client.get("/api/v1/payment/subscription", headers=auth_headers)
assert response.status_code == 200
data = response.json()
assert "plan" in data
assert "status" in data
async def test_create_order(self, client: AsyncClient, auth_headers):
response = await client.post(
"/api/v1/payment/create-order",
headers=auth_headers,
json={"plan": "pro"},
)
assert response.status_code == 200
data = response.json()
assert "prepay_id" in data or "order_id" in data or "url" in data