refactor: 移除前端 Mock API,新增共享组件,优化认证流程

- 删除 mock_api_client、mock_calendar_service、mock_history_service
- 新增 fixed_length_code_input、link_button、message_composer 共享组件
- 优化登录/注册/密码重置页面使用新组件
- 简化 injection.dart 移除 mock 分支
- 更新 env.dart 配置(BACKEND_URL 替换 API_URL)
- 后端 agentscope 工具和测试更新
- 重构 AGENTS.md 文档结构
- 新增 deploy/ 目录和 protocol 文档
This commit is contained in:
qzl
2026-03-12 16:41:45 +08:00
parent d7fbb74bf8
commit 01c36eb32e
70 changed files with 5138 additions and 5829 deletions
@@ -16,13 +16,9 @@ async def test_calendar_read_returns_list_payload(
) -> None:
async def _fake_execute(**kwargs: Any) -> dict[str, object]:
del kwargs
return {"type": "calendar_event_list.v1", "version": "v1", "data": {}}
return {"type": "calendar_event_list.v1", "version": "v1", "data": {"ok": True}}
monkeypatch.setattr(
calendar_module,
"_execute_list_calendar_events",
_fake_execute,
)
monkeypatch.setattr(calendar_module, "_execute_list_calendar_events", _fake_execute)
monkeypatch.setattr(calendar_module, "_verify_user_token", lambda **_: True)
monkeypatch.setattr(calendar_module, "build_tool_response", lambda payload: payload)
@@ -62,9 +58,7 @@ async def test_calendar_write_maps_event_id_for_update(
return {"type": "calendar_card.v1", "version": "v1", "data": {"ok": True}}
monkeypatch.setattr(
calendar_module,
"_execute_mutate_calendar_event",
_fake_execute,
calendar_module, "_execute_mutate_calendar_event", _fake_execute
)
monkeypatch.setattr(calendar_module, "_verify_user_token", lambda **_: True)
monkeypatch.setattr(calendar_module, "build_tool_response", lambda payload: payload)
@@ -82,57 +76,6 @@ async def test_calendar_write_maps_event_id_for_update(
assert "eventId" in captured
@pytest.mark.asyncio
async def test_calendar_write_requires_preset_user_token(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(calendar_module, "build_tool_response", lambda payload: payload)
monkeypatch.setattr(calendar_module, "_verify_user_token", lambda **_: False)
result = await calendar_module.calendar_write(
session=cast(AsyncSession, SimpleNamespace()),
owner_id=uuid4(),
user_token="bad-token",
operation="create",
)
assert result["data"]["ok"] is False
assert result["data"]["code"] == "UNAUTHORIZED"
@pytest.mark.asyncio
async def test_calendar_write_rejects_missing_event_id_for_update(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(calendar_module, "build_tool_response", lambda payload: payload)
result = await calendar_module.calendar_write(
session=cast(AsyncSession, SimpleNamespace()),
owner_id=uuid4(),
user_token="token-abc",
operation="update",
)
assert result["data"]["ok"] is False
assert result["data"]["code"] == "INVALID_ARGUMENT"
@pytest.mark.asyncio
async def test_calendar_write_rejects_event_id_for_create(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(calendar_module, "build_tool_response", lambda payload: payload)
result = await calendar_module.calendar_write(
session=cast(AsyncSession, SimpleNamespace()),
owner_id=uuid4(),
user_token="token-abc",
operation="create",
event_id=str(uuid4()),
)
assert result["data"]["ok"] is False
assert result["data"]["code"] == "INVALID_ARGUMENT"
@pytest.mark.asyncio
async def test_calendar_write_maps_reminder_minutes(
monkeypatch: pytest.MonkeyPatch,
@@ -144,9 +87,7 @@ async def test_calendar_write_maps_reminder_minutes(
return {"type": "calendar_card.v1", "version": "v1", "data": {"ok": True}}
monkeypatch.setattr(
calendar_module,
"_execute_mutate_calendar_event",
_fake_execute,
calendar_module, "_execute_mutate_calendar_event", _fake_execute
)
monkeypatch.setattr(calendar_module, "_verify_user_token", lambda **_: True)
monkeypatch.setattr(calendar_module, "build_tool_response", lambda payload: payload)
@@ -163,46 +104,54 @@ async def test_calendar_write_maps_reminder_minutes(
@pytest.mark.asyncio
async def test_calendar_write_rejects_invalid_reminder_minutes(
async def test_calendar_write_returns_failed_tool_response_on_error(
monkeypatch: pytest.MonkeyPatch,
) -> None:
async def _fake_execute(**kwargs: Any) -> dict[str, object]:
del kwargs
raise ValueError("eventId is required")
monkeypatch.setattr(
calendar_module, "_execute_mutate_calendar_event", _fake_execute
)
monkeypatch.setattr(calendar_module, "_verify_user_token", lambda **_: True)
monkeypatch.setattr(calendar_module, "build_tool_response", lambda payload: payload)
result = await calendar_module.calendar_write(
session=cast(AsyncSession, SimpleNamespace()),
owner_id=uuid4(),
user_token="token-abc",
operation="create",
reminder_minutes=10081,
operation="update",
)
assert result["type"] == "calendar_operation.v1"
assert result["data"]["ok"] is False
assert result["data"]["code"] == "INVALID_ARGUMENT"
@pytest.mark.asyncio
async def test_calendar_write_maps_invite_arguments(
async def test_calendar_share_maps_arguments(
monkeypatch: pytest.MonkeyPatch,
) -> None:
captured: dict[str, object] = {}
async def _fake_execute(**kwargs: Any) -> dict[str, object]:
captured.update(cast(dict[str, object], kwargs["tool_args"]))
return {"type": "calendar_card.v1", "version": "v1", "data": {"ok": True}}
return {
"type": "calendar_operation.v1",
"version": "v1",
"data": {"operation": "share", "ok": True},
}
monkeypatch.setattr(
calendar_module,
"_execute_mutate_calendar_event",
_fake_execute,
)
monkeypatch.setattr(calendar_module, "_execute_share_calendar_event", _fake_execute)
monkeypatch.setattr(calendar_module, "_verify_user_token", lambda **_: True)
monkeypatch.setattr(calendar_module, "build_tool_response", lambda payload: payload)
await calendar_module.calendar_write(
result = await calendar_module.calendar_share(
session=cast(AsyncSession, SimpleNamespace()),
owner_id=uuid4(),
user_token="token-abc",
operation="create",
event_id=str(uuid4()),
invite_user_emails=["a@example.com"],
invite_user_names=["alice"],
invite_user_ids=[str(uuid4())],
@@ -211,6 +160,8 @@ async def test_calendar_write_maps_invite_arguments(
invite_permission_invite=True,
)
assert result["type"] == "calendar_operation.v1"
assert captured["eventId"]
assert captured["inviteUserEmails"] == ["a@example.com"]
assert captured["inviteUserNames"] == ["alice"]
assert isinstance(captured["inviteUserIds"], list)
@@ -220,46 +171,18 @@ async def test_calendar_write_maps_invite_arguments(
@pytest.mark.asyncio
async def test_user_resolve_maps_identity_arguments(
monkeypatch: pytest.MonkeyPatch,
) -> None:
captured: dict[str, object] = {}
async def _fake_execute(**kwargs: Any) -> dict[str, object]:
captured.update(cast(dict[str, object], kwargs["tool_args"]))
return {"type": "user_lookup.v1", "version": "v1", "data": {"ok": True}}
monkeypatch.setattr(
calendar_module,
"_execute_resolve_user_identity",
_fake_execute,
)
monkeypatch.setattr(calendar_module, "_verify_user_token", lambda **_: True)
monkeypatch.setattr(calendar_module, "build_tool_response", lambda payload: payload)
result = await calendar_module.user_resolve(
session=cast(AsyncSession, SimpleNamespace()),
owner_id=uuid4(),
user_token="token-abc",
user_email="a@example.com",
)
assert result["type"] == "user_lookup.v1"
assert captured == {"userEmail": "a@example.com", "userName": None}
@pytest.mark.asyncio
async def test_user_resolve_requires_valid_user_token(
async def test_calendar_share_requires_valid_user_token(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(calendar_module, "_verify_user_token", lambda **_: False)
monkeypatch.setattr(calendar_module, "build_tool_response", lambda payload: payload)
result = await calendar_module.user_resolve(
result = await calendar_module.calendar_share(
session=cast(AsyncSession, SimpleNamespace()),
owner_id=uuid4(),
user_token="bad-token",
user_name="alice",
event_id=str(uuid4()),
invite_user_emails=["a@example.com"],
)
assert result["data"]["ok"] is False
@@ -22,7 +22,7 @@ async def test_build_toolkit_registers_calendar_tools() -> None:
names = {item["function"]["name"] for item in schemas}
assert "calendar_read" in names
assert "calendar_write" in names
assert "user_resolve" in names
assert "calendar_share" in names
write_schema = next(
item for item in schemas if item["function"]["name"] == "calendar_write"
@@ -28,6 +28,17 @@ def test_signup_requires_username() -> None:
)
def test_signup_allows_any_invite_code_input() -> None:
request = VerificationCreateRequest(
username="demo",
email="user@example.com",
password="secret123",
invite_code="abc123",
)
assert request.invite_code == "abc123"
def test_signup_verify_requires_six_digit_token() -> None:
with pytest.raises(ValidationError):
VerificationVerifyRequest(email="user@example.com", token="abc123")
@@ -22,10 +22,12 @@ from v1.auth.service import AuthService, AuthServiceGateway
class FakeGateway(AuthServiceGateway):
def __init__(self, response: SessionResponse) -> None:
self._response = response
self.last_create_verification_request: VerificationCreateRequest | None = None
async def create_verification(
self, request: VerificationCreateRequest
) -> VerificationCreateResponse:
self.last_create_verification_request = request
return VerificationCreateResponse(email=request.email)
async def verify_verification(
@@ -121,6 +123,58 @@ async def test_signup_resend_returns_none() -> None:
assert result is None
@pytest.mark.asyncio
async def test_create_verification_ignores_invalid_invite_code() -> None:
user = AuthUser(id="user-1", email="user@example.com")
token_response = SessionResponse(
access_token="access",
refresh_token="refresh",
expires_in=3600,
token_type="bearer",
user=user,
)
gateway = FakeGateway(token_response)
service = AuthService(gateway=gateway)
await service.create_verification(
VerificationCreateRequest(
username="demo",
email="user@example.com",
password="secret123",
invite_code="bad-code",
)
)
assert gateway.last_create_verification_request is not None
assert gateway.last_create_verification_request.invite_code is None
@pytest.mark.asyncio
async def test_create_verification_normalizes_valid_invite_code() -> None:
user = AuthUser(id="user-1", email="user@example.com")
token_response = SessionResponse(
access_token="access",
refresh_token="refresh",
expires_in=3600,
token_type="bearer",
user=user,
)
gateway = FakeGateway(token_response)
service = AuthService(gateway=gateway)
await service.create_verification(
VerificationCreateRequest(
username="demo",
email="user@example.com",
password="secret123",
invite_code="a2b3",
)
)
assert gateway.last_create_verification_request is not None
assert gateway.last_create_verification_request.invite_code == "A2B3"
@pytest.mark.asyncio
async def test_supabase_signup_passes_username_in_metadata(
monkeypatch: pytest.MonkeyPatch,