fix: 修复 player 详情 completionRate 和 k8s 公开路由

同步 k8s Envoy 中已放行公开接口的实际路由,避免落到泛用 user API;同时规范 dev API 测试脚本的响应体解析,使新增负向断言通过静态检查。
This commit is contained in:
zetaloop
2026-04-29 23:40:17 +08:00
parent 2341284f6c
commit 41890ddd33
3 changed files with 313 additions and 101 deletions
@@ -45,15 +45,20 @@ func (l *GetPlayerLogic) GetPlayer(req *types.GetPlayerReq) (resp *types.PlayerP
if player == nil { if player == nil {
return nil, errors.New("player not found") return nil, errors.New("player not found")
} }
completionRate := 0.0
if player.TotalOrders > 0 {
completionRate = float64(player.CompletedOrders) / float64(player.TotalOrders)
}
resp = &types.PlayerProfile{ resp = &types.PlayerProfile{
Id: player.Id, Id: player.Id,
Rating: player.Rating, Rating: player.Rating,
TotalOrders: player.TotalOrders, TotalOrders: player.TotalOrders,
Status: player.Status, CompletionRate: completionRate,
Gender: player.Gender, Status: player.Status,
Services: []types.PlayerService{}, Gender: player.Gender,
Tags: append([]string{}, player.Tags...), Services: []types.PlayerService{},
Tags: append([]string{}, player.Tags...),
} }
games := make([]string, 0, len(player.Games)) games := make([]string, 0, len(player.Games))
+209 -94
View File
@@ -16,7 +16,12 @@ import urllib.parse
import http.cookiejar import http.cookiejar
import os import os
import subprocess import subprocess
from collections.abc import Callable
from decimal import Decimal, InvalidOperation from decimal import Decimal, InvalidOperation
from typing import Any
Body = dict[str, Any]
StatusExpectation = int | tuple[int, ...] | list[int] | set[int]
GATEWAY = "http://127.0.0.1:18080" GATEWAY = "http://127.0.0.1:18080"
ADMIN_USERNAME = os.getenv("ADMIN_USERNAME", "admin") ADMIN_USERNAME = os.getenv("ADMIN_USERNAME", "admin")
@@ -50,7 +55,19 @@ def same_id(left, right):
return str(left) == str(right) return str(left) == str(right)
def body_text(body): def response_body(data: str) -> Body:
if not data:
return {}
try:
parsed = json.loads(data)
except json.JSONDecodeError:
return {"_raw": data}
if isinstance(parsed, dict):
return parsed
return {"_raw": parsed}
def body_text(body: object):
if not isinstance(body, dict): if not isinstance(body, dict):
return str(body) return str(body)
if "_raw" in body: if "_raw" in body:
@@ -72,15 +89,17 @@ def report_rejected(name, status_code, body, expect_status=(400, 403, 500)):
report_check(f"{name} returns error body", bool(body_text(body)), body) report_check(f"{name} returns error body", bool(body_text(body)), body)
def pick_items(body): def pick_items(body: object) -> list[Body]:
if not isinstance(body, dict):
return []
if isinstance(body.get("items"), list): if isinstance(body.get("items"), list):
return body["items"] return [item for item in body["items"] if isinstance(item, dict)]
if isinstance(body.get("list"), list): if isinstance(body.get("list"), list):
return body["list"] return [item for item in body["list"] if isinstance(item, dict)]
return [] return []
def find_item(items, predicate): def find_item(items: list[Body], predicate: Callable[[Body], bool]) -> Body | None:
for item in items: for item in items:
if predicate(item): if predicate(item):
return item return item
@@ -91,7 +110,7 @@ def find_item_by_id(items, item_id):
return find_item(items, lambda item: same_id(item.get("id"), item_id)) return find_item(items, lambda item: same_id(item.get("id"), item_id))
def user_from(body): def user_from(body: Body) -> Body:
user = body.get("user") if isinstance(body, dict) else None user = body.get("user") if isinstance(body, dict) else None
return user if isinstance(user, dict) else {} return user if isinstance(user, dict) else {}
@@ -186,7 +205,7 @@ class Session:
headers=None, headers=None,
form_data=None, form_data=None,
raw_body=None, raw_body=None,
): ) -> tuple[int, Body, dict[str, str]]:
hdrs = headers or {} hdrs = headers or {}
body = None body = None
if json_body is not None: if json_body is not None:
@@ -202,35 +221,31 @@ class Session:
try: try:
resp = self.opener.open(req, timeout=15) resp = self.opener.open(req, timeout=15)
data = resp.read().decode() data = resp.read().decode()
try: return resp.status, response_body(data), dict(resp.headers)
return resp.status, json.loads(data) if data else {}, dict(resp.headers)
except json.JSONDecodeError:
return resp.status, {"_raw": data}, dict(resp.headers)
except urllib.error.HTTPError as e: except urllib.error.HTTPError as e:
data = e.read().decode() if e.fp else "" data = e.read().decode() if e.fp else ""
try: return e.code, response_body(data), dict(e.headers)
return e.code, json.loads(data) if data else {}, dict(e.headers)
except json.JSONDecodeError:
return e.code, {"_raw": data}, dict(e.headers)
except Exception as e: except Exception as e:
return 0, {"_error": str(e)}, {} return 0, {"_error": str(e)}, {}
def get(self, url, **kw): def get(self, url, **kw) -> tuple[int, Body, dict[str, str]]:
return self.request("GET", url, **kw) return self.request("GET", url, **kw)
def post(self, url, **kw): def post(self, url, **kw) -> tuple[int, Body, dict[str, str]]:
return self.request("POST", url, **kw) return self.request("POST", url, **kw)
def post_multipart(self, url, fields, files, headers=None): def post_multipart(
self, url, fields, files, headers=None
) -> tuple[int, Body, dict[str, str]]:
hdrs = dict(headers or {}) hdrs = dict(headers or {})
content_type, body = build_multipart_form(fields, files) content_type, body = build_multipart_form(fields, files)
hdrs["Content-Type"] = content_type hdrs["Content-Type"] = content_type
return self.post(url, headers=hdrs, raw_body=body) return self.post(url, headers=hdrs, raw_body=body)
def put(self, url, **kw): def put(self, url, **kw) -> tuple[int, Body, dict[str, str]]:
return self.request("PUT", url, **kw) return self.request("PUT", url, **kw)
def delete(self, url, **kw): def delete(self, url, **kw) -> tuple[int, Body, dict[str, str]]:
return self.request("DELETE", url, **kw) return self.request("DELETE", url, **kw)
def csrf_headers(self): def csrf_headers(self):
@@ -238,7 +253,7 @@ class Session:
return {"xsrf-token": token} if token else {} return {"xsrf-token": token} if token else {}
def report(name, status_code, body, expect_status=200): def report(name, status_code, body, expect_status: StatusExpectation = 200):
global passed, failed global passed, failed
if isinstance(expect_status, (list, tuple, set)): if isinstance(expect_status, (list, tuple, set)):
ok = status_code in expect_status ok = status_code in expect_status
@@ -276,7 +291,7 @@ def skip(name, reason):
print(f" [SKIP] {name}: {reason}") print(f" [SKIP] {name}: {reason}")
def check_order_status(session, order_id, expected_status, label): def check_order_status(session, order_id, expected_status, label) -> Body:
code, body, _ = session.get(f"{GATEWAY}/api/v1/orders/{order_id}") code, body, _ = session.get(f"{GATEWAY}/api/v1/orders/{order_id}")
report(f"GET /orders/{order_id} ({label})", code, body) report(f"GET /orders/{order_id} ({label})", code, body)
report_check( report_check(
@@ -521,8 +536,9 @@ def phase3_admin_and_verification(
all( all(
find_item( find_item(
pick_items(body), pick_items(body),
lambda item, role=role: item.get("role") == role lambda item, role=role: (
and item.get("status") == "pending", item.get("role") == role and item.get("status") == "pending"
),
) )
for role in ("player", "owner") for role in ("player", "owner")
), ),
@@ -539,8 +555,9 @@ def phase3_admin_and_verification(
bool( bool(
find_item( find_item(
pick_items(body), pick_items(body),
lambda item: item.get("role") == "owner" lambda item: (
and item.get("status") == "pending", item.get("role") == "owner" and item.get("status") == "pending"
),
) )
), ),
body, body,
@@ -586,9 +603,11 @@ def phase3_admin_and_verification(
bool( bool(
find_item( find_item(
pick_items(body), pick_items(body),
lambda item: item.get("role") == "owner" lambda item: (
and item.get("status") == "rejected" item.get("role") == "owner"
and item.get("rejectReason") == "test reject flow", and item.get("status") == "rejected"
and item.get("rejectReason") == "test reject flow"
),
) )
), ),
body, body,
@@ -671,8 +690,9 @@ def phase3b_secondary_player(s_admin: Session, s_player: Session):
bool( bool(
find_item( find_item(
pick_items(body), pick_items(body),
lambda item: item.get("role") == "player" lambda item: (
and item.get("status") == "pending", item.get("role") == "player" and item.get("status") == "pending"
),
) )
), ),
body, body,
@@ -698,7 +718,9 @@ def phase3b_secondary_player(s_admin: Session, s_player: Session):
report("POST /users/me/switch-role (invited user player)", code, body) report("POST /users/me/switch-role (invited user player)", code, body)
code, body, _ = s_player.get(f"{GATEWAY}/api/v1/users/me") code, body, _ = s_player.get(f"{GATEWAY}/api/v1/users/me")
report("GET /users/me (invited user player role)", code, body) report("GET /users/me (invited user player role)", code, body)
report_check("invited user current role is player", body.get("role") == "player", body) report_check(
"invited user current role is player", body.get("role") == "player", body
)
code, body, _ = s_player.post( code, body, _ = s_player.post(
f"{GATEWAY}/api/v1/players/me", f"{GATEWAY}/api/v1/players/me",
@@ -739,7 +761,9 @@ def phase5_games(s: Session, s_admin: Session):
code, body, _ = s.get(f"{GATEWAY}/api/v1/games") code, body, _ = s.get(f"{GATEWAY}/api/v1/games")
report("GET /games", code, body) report("GET /games", code, body)
report_check("GET /games returns list shape", isinstance(pick_items(body), list), body) report_check(
"GET /games returns list shape", isinstance(pick_items(body), list), body
)
csrf_admin = s_admin.csrf_headers() csrf_admin = s_admin.csrf_headers()
game_name = f"TestGame_{rand_str(4)}" game_name = f"TestGame_{rand_str(4)}"
@@ -820,7 +844,9 @@ def phase6_player(s: Session, game_id):
code, body, _ = s.get(f"{GATEWAY}/api/v1/players") code, body, _ = s.get(f"{GATEWAY}/api/v1/players")
report("GET /players", code, body) report("GET /players", code, body)
report_check("GET /players returns list shape", isinstance(pick_items(body), list), body) report_check(
"GET /players returns list shape", isinstance(pick_items(body), list), body
)
if player_id: if player_id:
code, body, _ = s.get(f"{GATEWAY}/api/v1/players/{player_id}") code, body, _ = s.get(f"{GATEWAY}/api/v1/players/{player_id}")
@@ -866,7 +892,9 @@ def phase6_player(s: Session, game_id):
code, body, _ = s.get(f"{GATEWAY}/api/v1/services") code, body, _ = s.get(f"{GATEWAY}/api/v1/services")
report("GET /services", code, body) report("GET /services", code, body)
report_check("GET /services returns list shape", isinstance(pick_items(body), list), body) report_check(
"GET /services returns list shape", isinstance(pick_items(body), list), body
)
service_id = svc_body.get("id", 0) if svc_body else 0 service_id = svc_body.get("id", 0) if svc_body else 0
if service_id: if service_id:
@@ -1058,7 +1086,8 @@ def phase7_shop(
report(f"GET /shops/{shop_id} (after announcement delete)", code, body) report(f"GET /shops/{shop_id} (after announcement delete)", code, body)
report_check( report_check(
"deleted announcement is absent", "deleted announcement is absent",
announcement not in [str(item) for item in body.get("announcements") or []], announcement
not in [str(item) for item in body.get("announcements") or []],
body, body,
) )
@@ -1155,7 +1184,9 @@ def phase7_shop(
code, body, _ = s_owner.get( code, body, _ = s_owner.get(
f"{GATEWAY}/api/v1/shops/{shop_id}/invitations", f"{GATEWAY}/api/v1/shops/{shop_id}/invitations",
) )
report(f"GET /shops/{shop_id}/invitations (after wrong accept)", code, body) report(
f"GET /shops/{shop_id}/invitations (after wrong accept)", code, body
)
pending_invitation = find_item_by_id(pick_items(body), invitation_id) pending_invitation = find_item_by_id(pick_items(body), invitation_id)
report_check( report_check(
"wrong invitation accept keeps status pending", "wrong invitation accept keeps status pending",
@@ -1248,7 +1279,9 @@ def phase7_shop(
code, body, _ = s_owner.get( code, body, _ = s_owner.get(
f"{GATEWAY}/api/v1/shops/{shop_id}/invitations", f"{GATEWAY}/api/v1/shops/{shop_id}/invitations",
) )
report(f"GET /shops/{shop_id}/invitations (after reject)", code, body) report(
f"GET /shops/{shop_id}/invitations (after reject)", code, body
)
rejected = find_item_by_id(pick_items(body), reinvite_id) rejected = find_item_by_id(pick_items(body), reinvite_id)
report_check( report_check(
"rejected invitation has rejected status", "rejected invitation has rejected status",
@@ -1260,7 +1293,9 @@ def phase7_shop(
code, body, _ = s_owner.get(f"{GATEWAY}/api/v1/shops/mine") code, body, _ = s_owner.get(f"{GATEWAY}/api/v1/shops/mine")
report("GET /shops/mine", code, body) report("GET /shops/mine", code, body)
report_check("GET /shops/mine returns owned shop", same_id(body.get("id"), shop_id), body) report_check(
"GET /shops/mine returns owned shop", same_id(body.get("id"), shop_id), body
)
return shop_id return shop_id
@@ -1331,7 +1366,9 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s
) )
if order_id: if order_id:
body = check_order_status(s_consumer, order_id, "pending_payment", "after create") body = check_order_status(
s_consumer, order_id, "pending_payment", "after create"
)
report_check( report_check(
"created order detail matches participants", "created order detail matches participants",
same_id(body.get("consumerId"), consumer_user_id) same_id(body.get("consumerId"), consumer_user_id)
@@ -1348,15 +1385,21 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s
headers=s_actor.csrf_headers(), headers=s_actor.csrf_headers(),
) )
report_rejected(f"POST /orders/{order_id}/accept (before pay)", code, body) report_rejected(f"POST /orders/{order_id}/accept (before pay)", code, body)
check_order_status(s_consumer, order_id, "pending_payment", "after early accept") check_order_status(
s_consumer, order_id, "pending_payment", "after early accept"
)
code, body, _ = s_consumer.post( code, body, _ = s_consumer.post(
f"{GATEWAY}/api/v1/orders/{order_id}/confirm-close", f"{GATEWAY}/api/v1/orders/{order_id}/confirm-close",
json_body={}, json_body={},
headers=csrf, headers=csrf,
) )
report_rejected(f"POST /orders/{order_id}/confirm-close (before close request)", code, body) report_rejected(
check_order_status(s_consumer, order_id, "pending_payment", "after early confirm-close") f"POST /orders/{order_id}/confirm-close (before close request)", code, body
)
check_order_status(
s_consumer, order_id, "pending_payment", "after early confirm-close"
)
code, body, _ = s_consumer.post( code, body, _ = s_consumer.post(
f"{GATEWAY}/api/v1/orders/{order_id}/pay", f"{GATEWAY}/api/v1/orders/{order_id}/pay",
@@ -1371,8 +1414,12 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s
json_body={}, json_body={},
headers=s_actor.csrf_headers(), headers=s_actor.csrf_headers(),
) )
report_rejected(f"POST /orders/{order_id}/request-close (before accept)", code, body) report_rejected(
check_order_status(s_consumer, order_id, "pending_accept", "after early request-close") f"POST /orders/{order_id}/request-close (before accept)", code, body
)
check_order_status(
s_consumer, order_id, "pending_accept", "after early request-close"
)
code, body, _ = s_consumer.post( code, body, _ = s_consumer.post(
f"{GATEWAY}/api/v1/orders/{order_id}/pay", f"{GATEWAY}/api/v1/orders/{order_id}/pay",
@@ -1380,7 +1427,9 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s
headers=csrf, headers=csrf,
) )
report_rejected(f"POST /orders/{order_id}/pay (second time)", code, body) report_rejected(f"POST /orders/{order_id}/pay (second time)", code, body)
check_order_status(s_consumer, order_id, "pending_accept", "after duplicate pay") check_order_status(
s_consumer, order_id, "pending_accept", "after duplicate pay"
)
code, body, _ = s_actor.post( code, body, _ = s_actor.post(
f"{GATEWAY}/api/v1/orders/{order_id}/accept", f"{GATEWAY}/api/v1/orders/{order_id}/accept",
@@ -1389,15 +1438,21 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s
) )
report(f"POST /orders/{order_id}/accept", code, body) report(f"POST /orders/{order_id}/accept", code, body)
body = check_order_status(s_consumer, order_id, "in_progress", "after accept") body = check_order_status(s_consumer, order_id, "in_progress", "after accept")
report_check("accepted order has acceptedAt", bool(body.get("acceptedAt")), body) report_check(
"accepted order has acceptedAt", bool(body.get("acceptedAt")), body
)
code, body, _ = s_consumer.post( code, body, _ = s_consumer.post(
f"{GATEWAY}/api/v1/orders/{order_id}/confirm-close", f"{GATEWAY}/api/v1/orders/{order_id}/confirm-close",
json_body={}, json_body={},
headers=csrf, headers=csrf,
) )
report_rejected(f"POST /orders/{order_id}/confirm-close (before request-close)", code, body) report_rejected(
check_order_status(s_consumer, order_id, "in_progress", "after early confirm-close") f"POST /orders/{order_id}/confirm-close (before request-close)", code, body
)
check_order_status(
s_consumer, order_id, "in_progress", "after early confirm-close"
)
code, body, _ = s_actor.post( code, body, _ = s_actor.post(
f"{GATEWAY}/api/v1/orders/{order_id}/request-close", f"{GATEWAY}/api/v1/orders/{order_id}/request-close",
@@ -1413,7 +1468,9 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s
headers=csrf, headers=csrf,
) )
report(f"POST /orders/{order_id}/confirm-close", code, body) report(f"POST /orders/{order_id}/confirm-close", code, body)
check_order_status(s_consumer, order_id, "pending_review", "after confirm-close") check_order_status(
s_consumer, order_id, "pending_review", "after confirm-close"
)
code, body, _ = s_consumer.post( code, body, _ = s_consumer.post(
f"{GATEWAY}/api/v1/orders", f"{GATEWAY}/api/v1/orders",
@@ -1438,8 +1495,12 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s
json_body={"rating": 5, "content": "too early"}, json_body={"rating": 5, "content": "too early"},
headers=csrf, headers=csrf,
) )
report_rejected(f"POST /orders/{order2_id}/review (before completion)", code, body) report_rejected(
check_order_status(s_consumer, order2_id, "pending_payment", "after early review") f"POST /orders/{order2_id}/review (before completion)", code, body
)
check_order_status(
s_consumer, order2_id, "pending_payment", "after early review"
)
if order_id: if order_id:
code, body, _ = s_consumer.post( code, body, _ = s_consumer.post(
@@ -1465,7 +1526,9 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s
headers=csrf, headers=csrf,
) )
report(f"POST /orders/{order2_id}/pay (before cancel)", code, body) report(f"POST /orders/{order2_id}/pay (before cancel)", code, body)
check_order_status(s_consumer, order2_id, "pending_accept", "second order after pay") check_order_status(
s_consumer, order2_id, "pending_accept", "second order after pay"
)
code, body, _ = s_consumer.post( code, body, _ = s_consumer.post(
f"{GATEWAY}/api/v1/orders/{order2_id}/cancel", f"{GATEWAY}/api/v1/orders/{order2_id}/cancel",
@@ -1473,7 +1536,9 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s
headers=csrf, headers=csrf,
) )
report(f"POST /orders/{order2_id}/cancel", code, body) report(f"POST /orders/{order2_id}/cancel", code, body)
check_order_status(s_consumer, order2_id, "cancelled", "second order after cancel") check_order_status(
s_consumer, order2_id, "cancelled", "second order after cancel"
)
code, body, _ = s_consumer.post( code, body, _ = s_consumer.post(
f"{GATEWAY}/api/v1/orders/{order2_id}/pay", f"{GATEWAY}/api/v1/orders/{order2_id}/pay",
@@ -1547,8 +1612,14 @@ def phase8b_review(
) )
report(f"POST /orders/{order_id}/review (counterparty)", code, body) report(f"POST /orders/{order_id}/review (counterparty)", code, body)
order_body = check_order_status(s_consumer, order_id, "completed", "after two reviews") order_body = check_order_status(
report_check("completed order has completedAt", bool(order_body.get("completedAt")), order_body) s_consumer, order_id, "completed", "after two reviews"
)
report_check(
"completed order has completedAt",
bool(order_body.get("completedAt")),
order_body,
)
code, body, _ = s_consumer.get(f"{GATEWAY}/api/v1/orders/{order_id}/reviews") code, body, _ = s_consumer.get(f"{GATEWAY}/api/v1/orders/{order_id}/reviews")
report(f"GET /orders/{order_id}/reviews (after two reviews)", code, body) report(f"GET /orders/{order_id}/reviews (after two reviews)", code, body)
@@ -1558,8 +1629,12 @@ def phase8b_review(
len(reviews) == 2 len(reviews) == 2
and all(same_id(item.get("orderId"), order_id) for item in reviews) and all(same_id(item.get("orderId"), order_id) for item in reviews)
and all(item.get("sealed") is False for item in reviews) and all(item.get("sealed") is False for item in reviews)
and bool(find_item(reviews, lambda item: item.get("content") == "great service")) and bool(
and bool(find_item(reviews, lambda item: item.get("content") == "smooth buyer")), find_item(reviews, lambda item: item.get("content") == "great service")
)
and bool(
find_item(reviews, lambda item: item.get("content") == "smooth buyer")
),
body, body,
) )
@@ -1570,8 +1645,10 @@ def phase8b_review(
bool( bool(
find_item( find_item(
pick_items(body), pick_items(body),
lambda item: same_id(item.get("orderId"), order_id) lambda item: (
and item.get("sealed") is False, same_id(item.get("orderId"), order_id)
and item.get("sealed") is False
),
) )
), ),
body, body,
@@ -1587,16 +1664,20 @@ def phase8b_review(
bool( bool(
find_item( find_item(
pick_items(body), pick_items(body),
lambda item: same_id(item.get("orderId"), order_id) lambda item: (
and same_id(item.get("fromUserId"), consumer_user_id) same_id(item.get("orderId"), order_id)
and item.get("content") == "great service", and same_id(item.get("fromUserId"), consumer_user_id)
and item.get("content") == "great service"
),
) )
), ),
body, body,
) )
def phase8c_dispute(s_consumer: Session, s_actor: Session, player_id, service_id, shop_id): def phase8c_dispute(
s_consumer: Session, s_actor: Session, player_id, service_id, shop_id
):
print("\n=== Phase 8c: Disputes ===") print("\n=== Phase 8c: Disputes ===")
if not player_id or not service_id: if not player_id or not service_id:
skip("Dispute flow", "Missing player or service id") skip("Dispute flow", "Missing player or service id")
@@ -1643,7 +1724,9 @@ def phase8c_dispute(s_consumer: Session, s_actor: Session, player_id, service_id
headers=csrf, headers=csrf,
) )
report(f"POST /orders/{order_id}/pay (dispute flow)", code, body) report(f"POST /orders/{order_id}/pay (dispute flow)", code, body)
check_order_status(s_consumer, order_id, "pending_accept", "dispute order after pay") check_order_status(
s_consumer, order_id, "pending_accept", "dispute order after pay"
)
code, body, _ = s_actor.post( code, body, _ = s_actor.post(
f"{GATEWAY}/api/v1/orders/{order_id}/accept", f"{GATEWAY}/api/v1/orders/{order_id}/accept",
@@ -1651,7 +1734,9 @@ def phase8c_dispute(s_consumer: Session, s_actor: Session, player_id, service_id
headers=s_actor.csrf_headers(), headers=s_actor.csrf_headers(),
) )
report(f"POST /orders/{order_id}/accept (dispute flow)", code, body) report(f"POST /orders/{order_id}/accept (dispute flow)", code, body)
check_order_status(s_consumer, order_id, "in_progress", "dispute order after accept") check_order_status(
s_consumer, order_id, "in_progress", "dispute order after accept"
)
code, body, _ = s_consumer.post( code, body, _ = s_consumer.post(
f"{GATEWAY}/api/v1/orders/{order_id}/dispute", f"{GATEWAY}/api/v1/orders/{order_id}/dispute",
@@ -1694,8 +1779,9 @@ def phase8c_dispute(s_consumer: Session, s_actor: Session, player_id, service_id
bool( bool(
find_item( find_item(
pick_items(body), pick_items(body),
lambda item: same_id(item.get("id"), dispute_id) lambda item: (
and item.get("status") == "open", same_id(item.get("id"), dispute_id) and item.get("status") == "open"
),
) )
), ),
body, body,
@@ -1746,7 +1832,9 @@ def phase8d_notifications(s: Session):
report("GET /notifications", code, body) report("GET /notifications", code, body)
report_check( report_check(
"GET /notifications returns list shape", "GET /notifications returns list shape",
code == 200 and isinstance(pick_items(body), list) and isinstance(body.get("meta"), dict), code == 200
and isinstance(pick_items(body), list)
and isinstance(body.get("meta"), dict),
body, body,
) )
@@ -1812,7 +1900,9 @@ def phase8e_search_and_favorites(s: Session, user_id, player_id, shop_id):
report("GET /favorites", code, body) report("GET /favorites", code, body)
report_check( report_check(
"GET /favorites returns list shape", "GET /favorites returns list shape",
code == 200 and isinstance(pick_items(body), list) and isinstance(body.get("meta"), dict), code == 200
and isinstance(pick_items(body), list)
and isinstance(body.get("meta"), dict),
body, body,
) )
@@ -1833,8 +1923,10 @@ def phase8e_search_and_favorites(s: Session, user_id, player_id, shop_id):
not bool( not bool(
find_item( find_item(
pick_items(body), pick_items(body),
lambda item: item.get("targetType") == "player" lambda item: (
and same_id(item.get("targetId"), "not-a-snowflake"), item.get("targetType") == "player"
and same_id(item.get("targetId"), "not-a-snowflake")
),
) )
), ),
body, body,
@@ -1871,9 +1963,11 @@ def phase8e_search_and_favorites(s: Session, user_id, player_id, shop_id):
bool( bool(
find_item( find_item(
favorite_items, favorite_items,
lambda item: item.get("targetType") == "player" lambda item: (
and same_id(item.get("targetId"), player_id) item.get("targetType") == "player"
and same_id(item.get("userId"), user_id), and same_id(item.get("targetId"), player_id)
and same_id(item.get("userId"), user_id)
),
) )
), ),
body, body,
@@ -1884,18 +1978,19 @@ def phase8e_search_and_favorites(s: Session, user_id, player_id, shop_id):
bool( bool(
find_item( find_item(
favorite_items, favorite_items,
lambda item: item.get("targetType") == "shop" lambda item: (
and same_id(item.get("targetId"), shop_id) item.get("targetType") == "shop"
and same_id(item.get("userId"), user_id), and same_id(item.get("targetId"), shop_id)
and same_id(item.get("userId"), user_id)
),
) )
), ),
body, body,
) )
favorite_id = 0 favorite_id = 0
for item in favorite_items: for item in favorite_items:
if ( if item.get("targetType") == "player" and str(item.get("targetId")) == str(
item.get("targetType") == "player" player_id
and str(item.get("targetId")) == str(player_id)
): ):
favorite_id = as_int(item.get("id")) favorite_id = as_int(item.get("id"))
break break
@@ -1914,7 +2009,9 @@ def phase8e_search_and_favorites(s: Session, user_id, player_id, shop_id):
) )
report(f"GET /users/{user_id}/favorites/check (after delete)", code, body) report(f"GET /users/{user_id}/favorites/check (after delete)", code, body)
if code == 200: if code == 200:
report_check("favorite check after delete", body.get("favorited") is False, body) report_check(
"favorite check after delete", body.get("favorited") is False, body
)
code, body, _ = s.get(f"{GATEWAY}/api/v1/favorites") code, body, _ = s.get(f"{GATEWAY}/api/v1/favorites")
report("GET /favorites (after delete)", code, body) report("GET /favorites (after delete)", code, body)
report_check( report_check(
@@ -1922,8 +2019,10 @@ def phase8e_search_and_favorites(s: Session, user_id, player_id, shop_id):
not bool( not bool(
find_item( find_item(
pick_items(body), pick_items(body),
lambda item: item.get("targetType") == "player" lambda item: (
and same_id(item.get("targetId"), player_id), item.get("targetType") == "player"
and same_id(item.get("targetId"), player_id)
),
) )
), ),
body, body,
@@ -2011,17 +2110,21 @@ def phase9_wallet(s: Session):
bool( bool(
find_item( find_item(
transactions, transactions,
lambda item: item.get("type") == "topup" lambda item: (
and as_decimal(item.get("amount")) == Decimal("100") item.get("type") == "topup"
and item.get("description") == "topup via alipay", and as_decimal(item.get("amount")) == Decimal("100")
and item.get("description") == "topup via alipay"
),
) )
) )
and bool( and bool(
find_item( find_item(
transactions, transactions,
lambda item: item.get("type") == "withdrawal" lambda item: (
and as_decimal(item.get("amount")) == Decimal("10") item.get("type") == "withdrawal"
and item.get("description") == "withdraw via alipay", and as_decimal(item.get("amount")) == Decimal("10")
and item.get("description") == "withdraw via alipay"
),
) )
), ),
body, body,
@@ -2060,7 +2163,11 @@ def phase10_community(s: Session, user_id):
code, body, _ = s.get(f"{GATEWAY}/api/v1/posts") code, body, _ = s.get(f"{GATEWAY}/api/v1/posts")
report("GET /posts", code, body) report("GET /posts", code, body)
report_check("created post appears in list", bool(find_item_by_id(pick_items(body), post_id)), body) report_check(
"created post appears in list",
bool(find_item_by_id(pick_items(body), post_id)),
body,
)
if post_id: if post_id:
code, body, _ = s.get(f"{GATEWAY}/api/v1/posts/{post_id}") code, body, _ = s.get(f"{GATEWAY}/api/v1/posts/{post_id}")
@@ -2181,7 +2288,11 @@ def phase10_community(s: Session, user_id):
code, body, _ = s.get(f"{GATEWAY}/api/v1/users/{user_id}/posts") code, body, _ = s.get(f"{GATEWAY}/api/v1/users/{user_id}/posts")
report(f"GET /users/{user_id}/posts", code, body) report(f"GET /users/{user_id}/posts", code, body)
report_check("user posts contain created post", bool(find_item_by_id(pick_items(body), post_id)), body) report_check(
"user posts contain created post",
bool(find_item_by_id(pick_items(body), post_id)),
body,
)
return post_id return post_id
@@ -2241,7 +2352,9 @@ def phase12_email(s: Session):
report("POST /email/verification-code/send (gateway)", code, body) report("POST /email/verification-code/send (gateway)", code, body)
report_check( report_check(
"email verification send returns request id", "email verification send returns request id",
code == 200 and bool(body.get("requestId")) and as_int(body.get("expireInSec")) > 0, code == 200
and bool(body.get("requestId"))
and as_int(body.get("expireInSec")) > 0,
body, body,
) )
@@ -2253,7 +2366,9 @@ def phase12_email(s: Session):
report("POST /auth/forgot-password/send (gateway)", code, body) report("POST /auth/forgot-password/send (gateway)", code, body)
report_check( report_check(
"forgot password send returns request id", "forgot password send returns request id",
code == 200 and bool(body.get("requestId")) and as_int(body.get("expireInSec")) > 0, code == 200
and bool(body.get("requestId"))
and as_int(body.get("expireInSec")) > 0,
body, body,
) )
+92
View File
@@ -141,6 +141,98 @@ data:
route: route:
cluster: player_api_cluster cluster: player_api_cluster
timeout: 30s timeout: 30s
- match:
prefix: /api/v1/services
headers:
- name: ":method"
exact_match: "GET"
route:
cluster: player_api_cluster
timeout: 30s
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
- match:
path: /api/v1/shops
headers:
- name: ":method"
exact_match: "GET"
route:
cluster: shop_api_cluster
timeout: 30s
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
- match:
safe_regex:
google_re2: {}
regex: "^/api/v1/shops/[^/]+$"
headers:
- name: ":method"
exact_match: "GET"
route:
cluster: shop_api_cluster
timeout: 30s
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
- match:
safe_regex:
google_re2: {}
regex: "^/api/v1/shops/[^/]+/players$"
headers:
- name: ":method"
exact_match: "GET"
route:
cluster: shop_api_cluster
timeout: 30s
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
- match:
safe_regex:
google_re2: {}
regex: "^/api/v1/users/[^/]+/posts$"
headers:
- name: ":method"
exact_match: "GET"
route:
cluster: community_api_cluster
timeout: 30s
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
- match:
safe_regex:
google_re2: {}
regex: "^/api/v1/users/[^/]+/shop$"
headers:
- name: ":method"
exact_match: "GET"
route:
cluster: shop_api_cluster
timeout: 30s
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
- match:
prefix: /api/v1/posts
headers:
- name: ":method"
exact_match: "GET"
route:
cluster: community_api_cluster
timeout: 30s
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
# - match: # - match:
# prefix: /api/v1/shop # prefix: /api/v1/shop
# route: # route: