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
+337
View File
@@ -0,0 +1,337 @@
import pytest
from httpx import AsyncClient
from unittest.mock import patch, AsyncMock
from app.models.customer import Conversation, Message
from app.models.quotation import Quotation, QuotationItem
from app.models.user import Product
from datetime import datetime
class TestTranslateAPI:
async def test_translate_unauthorized(self, client: AsyncClient):
response = await client.post(
"/api/v1/translate",
json={"text": "Hello", "target_lang": "zh"},
)
assert response.status_code == 401
async def test_translate_success(self, client: AsyncClient, auth_headers):
with patch("app.services.translation.TranslationService.translate") as mock:
mock.return_value = {
"translated_text": "你好",
"source_lang": "en",
"provider_used": "mock",
"from_cache": False,
}
response = await client.post(
"/api/v1/translate",
headers=auth_headers,
json={"text": "Hello", "target_lang": "zh"},
)
assert response.status_code == 200
data = response.json()
assert data["translated_text"] == "你好"
async def test_translate_with_context(self, client: AsyncClient, auth_headers):
with patch("app.services.translation.TranslationService.translate") as mock:
mock.return_value = {
"translated_text": "FOB 上海 价格",
"source_lang": "en",
"provider_used": "mock",
}
response = await client.post(
"/api/v1/translate",
headers=auth_headers,
json={"text": "FOB Shanghai price", "target_lang": "zh", "context": "trade"},
)
assert response.status_code == 200
class TestReplyAPI:
async def test_reply_unauthorized(self, client: AsyncClient):
response = await client.post(
"/api/v1/translate/reply",
json={"inquiry": "How much?", "tone": "professional"},
)
assert response.status_code == 401
async def test_reply_success(self, client: AsyncClient, auth_headers):
with patch("app.services.translation.TranslationService.generate_reply") as mock:
mock.return_value = [
{"reply": "Thank you for your inquiry.", "tone": "professional", "provider": "mock"},
{"reply": "Thanks for reaching out!", "tone": "friendly", "provider": "mock"},
]
response = await client.post(
"/api/v1/translate/reply",
headers=auth_headers,
json={"inquiry": "How much for 500 units?", "tone": "professional", "count": 2},
)
assert response.status_code == 200
data = response.json()
assert len(data["suggestions"]) == 2
assert data["count"] == 2
async def test_reply_with_context(self, client: AsyncClient, auth_headers):
with patch("app.services.translation.TranslationService.generate_reply") as mock:
mock.return_value = [{"reply": "Our price is $10/unit.", "tone": "professional", "provider": "mock"}]
response = await client.post(
"/api/v1/translate/reply",
headers=auth_headers,
json={
"inquiry": "Price?",
"tone": "professional",
"count": 1,
"context": {"product": "Widget X", "price": "$10"},
},
)
assert response.status_code == 200
class TestExtractAPI:
async def test_extract_unauthorized(self, client: AsyncClient):
response = await client.post(
"/api/v1/translate/extract",
json={"text": "I want 500pcs of red widgets FOB Shanghai", "extract_type": "inquiry"},
)
assert response.status_code == 401
async def test_extract_success(self, client: AsyncClient, auth_headers):
with patch("app.services.translation.TranslationService.extract_info") as mock:
mock.return_value = {
"intent": "purchase",
"product_interest": "widgets",
"quantity": "500",
}
response = await client.post(
"/api/v1/translate/extract",
headers=auth_headers,
json={"text": "I want 500pcs of red widgets FOB Shanghai", "extract_type": "inquiry"},
)
assert response.status_code == 200
data = response.json()
assert "extracted" in data
class TestTTSAPI:
async def test_tts_get_unauthorized(self, client: AsyncClient):
response = await client.get("/api/v1/translate/tts?text=hello&lang=en")
assert response.status_code == 401
async def test_tts_get_empty_text(self, client: AsyncClient, auth_headers):
response = await client.get("/api/v1/translate/tts?text=&lang=en", headers=auth_headers)
assert response.status_code == 400
class TestMarketingAPI:
async def test_marketing_unauthorized(self, client: AsyncClient):
response = await client.post(
"/api/v1/marketing/generate",
json={"product_name": "Widget", "description": "A great widget", "target": "US buyers"},
)
assert response.status_code == 401
async def test_marketing_success(self, client: AsyncClient, auth_headers):
with patch("app.services.marketing.MarketingService.generate") as mock:
mock.return_value = [
{"content": "Buy our widget!", "style": "professional", "provider": "mock"},
]
response = await client.post(
"/api/v1/marketing/generate",
headers=auth_headers,
json={
"product_name": "Widget X",
"description": "High quality widget",
"category": "tools",
"target": "US importers",
"style": "professional",
"count": 1,
},
)
assert response.status_code == 200
data = response.json()
assert data["count"] >= 1
assert "results" in data
async def test_marketing_keywords(self, client: AsyncClient, auth_headers):
with patch("app.services.marketing.MarketingService.generate_keywords") as mock:
mock.return_value = ["widget", "tool", "quality"]
response = await client.post(
"/api/v1/marketing/keywords",
headers=auth_headers,
json={"product_name": "Widget", "description": "A widget", "count": 5},
)
assert response.status_code == 200
assert "keywords" in response.json()
class TestProductAPI:
async def test_create_product(self, client: AsyncClient, auth_headers):
response = await client.post(
"/api/v1/products",
headers=auth_headers,
json={
"name": "Test Product",
"description": "A test product",
"category": "electronics",
"price": "10.50",
"price_unit": "USD",
},
)
assert response.status_code == 200
data = response.json()
assert data["name"] == "Test Product"
async def test_list_products(self, client: AsyncClient, auth_headers):
response = await client.get("/api/v1/products", headers=auth_headers)
assert response.status_code == 200
data = response.json()
assert "items" in data
async def test_update_product(self, client: AsyncClient, auth_headers, db_session, test_user):
product = Product(
user_id=test_user.id,
name="Old Name",
category="tools",
is_active=True,
)
db_session.add(product)
await db_session.commit()
response = await client.patch(
f"/api/v1/products/{product.id}",
headers=auth_headers,
json={"name": "New Name", "price": "20.00"},
)
assert response.status_code == 200
assert response.json()["name"] == "New Name"
async def test_delete_product(self, client: AsyncClient, auth_headers, db_session, test_user):
product = Product(user_id=test_user.id, name="To Delete")
db_session.add(product)
await db_session.commit()
pid = product.id
response = await client.delete(f"/api/v1/products/{pid}", headers=auth_headers)
assert response.status_code == 200
response = await client.get(f"/api/v1/products/{pid}", headers=auth_headers)
assert response.status_code == 404
class TestQuotationAPI:
async def test_create_quotation(self, client: AsyncClient, auth_headers, db_session, test_user):
from app.models.customer import Customer
customer = Customer(user_id=test_user.id, name="Test Buyer")
db_session.add(customer)
await db_session.commit()
response = await client.post(
"/api/v1/quotations",
headers=auth_headers,
json={
"customer_id": str(customer.id),
"title": "Test Quote",
"items": [
{"product_name": "Widget", "quantity": 100, "unit_price": 10.0},
],
"currency": "USD",
"payment_terms": "T/T",
},
)
assert response.status_code == 200
data = response.json()
assert data["title"] == "Test Quote"
assert len(data["items"]) == 1
async def test_list_quotations(self, client: AsyncClient, auth_headers):
response = await client.get("/api/v1/quotations", headers=auth_headers)
assert response.status_code == 200
assert "items" in response.json()
async def test_quotation_pdf_not_found(self, client: AsyncClient, auth_headers):
import uuid
response = await client.get(f"/api/v1/quotations/{uuid.uuid4()}/pdf", headers=auth_headers)
assert response.status_code == 404
async def test_quotation_status_update(self, client: AsyncClient, auth_headers, db_session, test_user):
from app.models.customer import Customer
customer = Customer(user_id=test_user.id, name="Status Test Buyer")
db_session.add(customer)
await db_session.commit()
q = Quotation(user_id=test_user.id, customer_id=customer.id, title="Status Test", status="draft")
db_session.add(q)
await db_session.commit()
response = await client.patch(
f"/api/v1/quotations/{q.id}/status",
headers=auth_headers,
json={"status": "sent"},
)
assert response.status_code == 200
assert response.json()["status"] == "sent"
class TestAnalyticsAPI:
async def test_overview(self, client: AsyncClient, auth_headers):
response = await client.get("/api/v1/analytics/overview", headers=auth_headers)
assert response.status_code == 200
data = response.json()
assert "customers" in data
assert "translations" in data
assert "quotations" in data
assert "messages" in data
assert "marketing" in data
async def test_customer_analytics(self, client: AsyncClient, auth_headers):
response = await client.get("/api/v1/analytics/customers", headers=auth_headers)
assert response.status_code == 200
async def test_marketing_analytics(self, client: AsyncClient, auth_headers):
response = await client.get("/api/v1/analytics/marketing", headers=auth_headers)
assert response.status_code == 200
data = response.json()
assert "total_events" in data
class TestOnboardingAPI:
async def test_onboarding_status(self, client: AsyncClient, auth_headers):
response = await client.get("/api/v1/onboarding/status", headers=auth_headers)
assert response.status_code == 200
data = response.json()
assert "completed" in data
assert "product_count" in data
async def test_onboarding_create_product(self, client: AsyncClient, auth_headers):
with patch("app.services.onboarding.OnboardingService.create_product") as mock:
mock.return_value = {
"id": "mock-id",
"name": "Onboarded Product",
"marketing_contents": [],
"keywords": [],
}
response = await client.post(
"/api/v1/onboarding/product",
headers=auth_headers,
json={
"name": "New Product",
"description": "Desc",
"category": "tools",
"target": "US buyers",
},
)
assert response.status_code == 200
assert response.json()["name"] == "Onboarded Product"
class TestExportAPI:
async def test_export_customers_csv(self, client: AsyncClient, auth_headers):
response = await client.get("/api/v1/customers/export/csv", headers=auth_headers)
assert response.status_code == 200
assert response.headers["content-type"] == "text/csv"
assert "customers.csv" in response.headers["content-disposition"]
async def test_export_quotations_csv(self, client: AsyncClient, auth_headers):
response = await client.get("/api/v1/quotations/export/csv", headers=auth_headers)
assert response.status_code == 200
assert response.headers["content-type"] == "text/csv"