chore: 优化本地开发环境配置

- 添加 .env.local 支持,app.sh 和 dev-migrate.sh 自动覆盖
- Docker Compose 使用 profiles 区分 dev/prod 环境
- 改进认证 dev session 判断逻辑,使用 test account 配置
- 修复 CoinPackageCard 重复代码问题
- 清理 opencode 配置,移除敏感信息
- 新增 infra/docker/README.md 文档
- 修复 ruff/pyright/flutter lint 错误
- 更新测试用例移除已删除的 country 字段
This commit is contained in:
qzl
2026-04-28 18:49:38 +08:00
parent 86062d5e78
commit dab47f0cb3
21 changed files with 642 additions and 155 deletions
@@ -1,5 +1,3 @@
from __future__ import annotations
"""
Integration tests for Apple IAP payment verify flow.
@@ -7,6 +5,8 @@ Prerequisite: backend must be running via `./infra/scripts/app.sh restart`.
These tests hit the live HTTP API against the test database.
"""
from __future__ import annotations
import pytest
-1
View File
@@ -6,7 +6,6 @@ import json
from v1.payments.apple_verifier import (
AppleJwsVerifier,
VerificationError,
VerifiedTransaction,
)
@@ -571,7 +571,6 @@ class TestHandleServerNotificationRefund:
verifier=_FakeVerifier(result=_make_verified_transaction()),
)
import base64
import json
signed_txn = _make_fake_signed_transaction(transaction_id="2000000999999001")
@@ -10,7 +10,7 @@ from v1.auth.schemas import AuthUser, EmailSessionCreateRequest, SessionResponse
@pytest.mark.asyncio
async def test_create_email_session_uses_dev_bypass(
async def test_create_email_session_uses_test_account_bypass(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway = SupabaseAuthGateway()
@@ -28,7 +28,8 @@ async def test_create_email_session_uses_dev_bypass(
calls.update(kwargs)
return expected
monkeypatch.setattr(gateway_module.config.runtime, "environment", "dev")
monkeypatch.setattr(gateway_module.config.test, "email", "test@example.com")
monkeypatch.setattr(gateway_module.config.test, "code", "123456")
monkeypatch.setattr(
gateway_module, "create_dev_email_session", _fake_create_dev_email_session
)
@@ -47,7 +48,7 @@ async def test_create_email_session_uses_dev_bypass(
@pytest.mark.asyncio
async def test_create_email_session_uses_verify_otp_in_non_dev(
async def test_create_email_session_uses_verify_otp_when_test_account_not_configured(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway = SupabaseAuthGateway()
@@ -66,7 +67,8 @@ async def test_create_email_session_uses_verify_otp_in_non_dev(
user=SimpleNamespace(id="user-2", email="test@example.com"),
)
monkeypatch.setattr(gateway_module.config.runtime, "environment", "prod")
monkeypatch.setattr(gateway_module.config.test, "email", "")
monkeypatch.setattr(gateway_module.config.test, "code", "")
monkeypatch.setattr(
gateway,
"_get_client",
@@ -81,3 +83,79 @@ async def test_create_email_session_uses_verify_otp_in_non_dev(
"token": "123456",
}
assert response.user.email == "test@example.com"
@pytest.mark.asyncio
async def test_create_email_session_uses_verify_otp_when_email_mismatch(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway = SupabaseAuthGateway()
request = EmailSessionCreateRequest(email="other@example.com", token="123456")
captured_payload: dict[str, str] = {}
def _verify_otp(payload: dict[str, str]) -> SimpleNamespace:
captured_payload.update(payload)
return SimpleNamespace(
session=SimpleNamespace(
access_token="access",
refresh_token="refresh",
expires_in=3600,
token_type="bearer",
),
user=SimpleNamespace(id="user-3", email="other@example.com"),
)
monkeypatch.setattr(gateway_module.config.test, "email", "test@example.com")
monkeypatch.setattr(gateway_module.config.test, "code", "123456")
monkeypatch.setattr(
gateway,
"_get_client",
lambda: SimpleNamespace(auth=SimpleNamespace(verify_otp=_verify_otp)),
)
response = await gateway.create_email_session(request)
assert captured_payload == {
"type": "email",
"email": "other@example.com",
"token": "123456",
}
assert response.user.email == "other@example.com"
@pytest.mark.asyncio
async def test_create_email_session_uses_verify_otp_when_code_mismatch(
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway = SupabaseAuthGateway()
request = EmailSessionCreateRequest(email="test@example.com", token="654321")
captured_payload: dict[str, str] = {}
def _verify_otp(payload: dict[str, str]) -> SimpleNamespace:
captured_payload.update(payload)
return SimpleNamespace(
session=SimpleNamespace(
access_token="access",
refresh_token="refresh",
expires_in=3600,
token_type="bearer",
),
user=SimpleNamespace(id="user-4", email="test@example.com"),
)
monkeypatch.setattr(gateway_module.config.test, "email", "test@example.com")
monkeypatch.setattr(gateway_module.config.test, "code", "123456")
monkeypatch.setattr(
gateway,
"_get_client",
lambda: SimpleNamespace(auth=SimpleNamespace(verify_otp=_verify_otp)),
)
response = await gateway.create_email_session(request)
assert captured_payload == {
"type": "email",
"email": "test@example.com",
"token": "654321",
}
assert response.user.email == "test@example.com"
@@ -17,7 +17,6 @@ class TestParseProfileSettings:
"preferences": {
"language": "en-US",
"timezone": "America/New_York",
"country": "US",
},
"privacy": {"profile_visibility": "private"},
"notification": {
@@ -32,7 +31,6 @@ class TestParseProfileSettings:
assert isinstance(result.preferences, PreferenceSettings)
assert result.preferences.language == "en-US"
assert result.preferences.timezone == "America/New_York"
assert result.preferences.country == "US"
assert isinstance(result.notification, NotificationSettings)
assert result.notification.allow_notifications is True
assert result.notification.allow_vibration is False
@@ -45,7 +43,6 @@ class TestParseProfileSettings:
assert isinstance(result.preferences, PreferenceSettings)
assert result.preferences.language == "zh-CN"
assert result.preferences.timezone == "Asia/Shanghai"
assert result.preferences.country == "US"
assert isinstance(result.notification, NotificationSettings)
assert result.notification.allow_notifications is True
assert result.notification.allow_vibration is True
@@ -60,7 +57,6 @@ class TestParseProfileSettings:
assert result.preferences.language == "en-US"
assert result.preferences.timezone == "Asia/Shanghai"
assert result.preferences.country == "US"
def test_parse_profile_settings_with_partial_notification(self) -> None:
raw = {
@@ -104,21 +100,11 @@ class TestParseProfileSettings:
with pytest.raises(ValueError, match="timezone must be a valid IANA timezone"):
parse_profile_settings(raw)
def test_parse_profile_settings_country_normalized_to_uppercase(self) -> None:
raw = {
"preferences": {
"country": "us",
},
}
result = parse_profile_settings(raw)
assert result.preferences.country == "US"
def test_profile_settings_v1_model_dump(self) -> None:
settings = ProfileSettingsV1(
preferences=PreferenceSettings(
language="en-US",
timezone="UTC",
country="US",
),
notification=NotificationSettings(
allow_notifications=True,