| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- from __future__ import annotations
- import hashlib
- import hmac
- import json
- from datetime import UTC, datetime, timedelta
- from urllib.parse import urlencode
- from aiohttp.test_utils import TestClient, TestServer
- def _signed_init_data(
- token: str,
- user_id: int,
- *,
- auth_date: datetime | None = None,
- ) -> str:
- values = {
- "auth_date": str(int((auth_date or datetime.now(UTC)).timestamp())),
- "query_id": "AAE-test-query",
- "user": json.dumps(
- {
- "id": user_id,
- "first_name": "认证",
- "last_name": "技师",
- "username": f"technician{user_id}",
- "language_code": "zh-hans",
- },
- ensure_ascii=False,
- separators=(",", ":"),
- ),
- }
- data_check_string = "\n".join(
- f"{key}={value}" for key, value in sorted(values.items())
- )
- secret_key = hmac.new(
- b"WebAppData",
- token.encode(),
- hashlib.sha256,
- ).digest()
- values["hash"] = hmac.new(
- secret_key,
- data_check_string.encode(),
- hashlib.sha256,
- ).hexdigest()
- return urlencode(values)
- async def _approved_technician(app_modules, user_id: int) -> None:
- directory_db = app_modules.load("wbb.utils.dbdirectory")
- await directory_db.upsert_directory_identity(
- user_id=user_id,
- username=f"technician{user_id}",
- first_name="认证",
- last_name="技师",
- )
- await directory_db.upsert_directory_membership(
- bot_id="primary",
- chat_id=-100200,
- user_id=user_id,
- status="member",
- active=True,
- verified=True,
- )
- await directory_db.submit_teacher_application(
- user_id=user_id,
- source="test",
- bot_id="primary",
- chat_id=-100200,
- )
- await directory_db.decide_teacher_application(
- user_id=user_id,
- action="approve",
- actor_id="admin",
- actor_name="admin",
- source="test",
- )
- async def test_builtin_templates_are_seeded_without_overwriting_admin_changes(
- app_modules,
- ):
- service_db = app_modules.load("wbb.utils.dbservice")
- first = await service_db.ensure_builtin_package_templates()
- assert [item["template_id"] for item in first] == [
- "builtin_quick_at_store",
- "builtin_quick_onsite",
- "builtin_quick_hourly",
- ]
- await app_modules.wbb.control_db.service_package_templates.update_one(
- {"template_id": "builtin_quick_at_store"},
- {"$set": {"name": "管理员修改后的到店模板", "status": "disabled"}},
- )
- await service_db.ensure_builtin_package_templates()
- stored = await app_modules.wbb.control_db.service_package_templates.find_one(
- {"template_id": "builtin_quick_at_store"}
- )
- assert stored["name"] == "管理员修改后的到店模板"
- assert stored["status"] == "disabled"
- async def test_technician_mini_app_uses_signed_identity_without_admin_login(
- app_modules,
- ):
- user_id = 700
- await _approved_technician(app_modules, user_id)
- bot_config = app_modules.load("wbb.admin.bot_config")
- token = "123456:abcdefghijklmnopqrstuvwxyzABCDE"
- bot = bot_config.create_bot_profile(
- app_modules.wbb.BOT_PROFILES_PATH,
- {
- "label": "技师服务 Bot",
- "bot_token": token,
- "enabled": True,
- },
- )
- admin_api = app_modules.load("wbb.admin.api")
- application = admin_api.build_admin_application()
- await application["admin_api"].initialize()
- client = TestClient(TestServer(application))
- await client.start_server()
- headers = {
- "X-Telegram-Bot-Id": bot["bot_id"],
- "X-Telegram-Init-Data": _signed_init_data(token, user_id),
- }
- try:
- invalid = await client.get(
- "/api/technician/v1/bootstrap",
- headers={**headers, "X-Telegram-Init-Data": f"{headers['X-Telegram-Init-Data']}x"},
- )
- assert invalid.status == 401
- assert (await invalid.json())["error"]["code"] == "mini_app_auth_failed"
- expired = await client.get(
- "/api/technician/v1/bootstrap",
- headers={
- **headers,
- "X-Telegram-Init-Data": _signed_init_data(
- token,
- user_id,
- auth_date=datetime.now(UTC) - timedelta(hours=2),
- ),
- },
- )
- assert expired.status == 401
- loaded = await client.get("/api/technician/v1/bootstrap", headers=headers)
- assert loaded.status == 200
- payload = (await loaded.json())["data"]
- assert payload["technician"]["user_id"] == str(user_id)
- assert payload["technician"]["eligibility"]["ready"] is True
- assert [item["name"] for item in payload["templates"]] == [
- "快速到店服务",
- "快速上门服务",
- "快速按小时服务",
- ]
- assert all("admin_note" not in item for item in payload["templates"])
- package = payload["templates"][0]["package"]
- package.update(
- {
- "package_id": "self_service_package",
- "source_template_id": payload["templates"][0]["template_id"],
- "source_template_version": payload["templates"][0]["version"],
- }
- )
- published = await client.put(
- "/api/technician/v1/profile",
- headers=headers,
- json={
- "user_id": "999999",
- "service_profile": {
- "headline": "一分钟发布套餐",
- "bio": package["description"],
- "tags": package["tags"],
- "contact_hours": "请通过 Telegram 私聊确认",
- "public_area_text": "测试城区及附近",
- "venue": {
- "name": "测试工作地点",
- "address_hint": "测试路 100 号",
- },
- "onsite_policy": {"description": ""},
- "packages": [package],
- "accepting_requests": True,
- },
- },
- )
- assert published.status == 200
- published_data = (await published.json())["data"]
- assert published_data["user_id"] == str(user_id)
- assert published_data["service_profile"]["packages"][0]["price_unit"] == "per_service"
- assert (
- await app_modules.wbb.control_db.directory_profiles.find_one(
- {"user_id": 999999}
- )
- ) is None
- paused = await client.patch(
- "/api/technician/v1/availability",
- headers=headers,
- json={"accepting_requests": False},
- )
- assert paused.status == 200
- assert (
- (await paused.json())["data"]["service_profile"]["accepting_requests"]
- is False
- )
- finally:
- await client.close()
|