diff --git a/deploy/dev/envoy.yaml b/deploy/dev/envoy.yaml index 3076a10..b94adc9 100644 --- a/deploy/dev/envoy.yaml +++ b/deploy/dev/envoy.yaml @@ -283,6 +283,21 @@ static_resources: cluster: search_api_cluster timeout: 30s + - match: + safe_regex: + google_re2: {} + regex: "^/api/v1/users/[0-9]+/reviews$" + headers: + - name: ":method" + exact_match: GET + route: + cluster: review_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/users route: @@ -384,21 +399,6 @@ static_resources: "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute disabled: true - - match: - safe_regex: - google_re2: {} - regex: "^/api/v1/users/[0-9]+/reviews$" - headers: - - name: ":method" - exact_match: GET - route: - cluster: review_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/disputes route: diff --git a/deploy/dev/test_all_apis.py b/deploy/dev/test_all_apis.py index ca5eb25..1ce1535 100644 --- a/deploy/dev/test_all_apis.py +++ b/deploy/dev/test_all_apis.py @@ -924,6 +924,13 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s report(f"POST /orders/{order_id}/reorder", code, body) if order2_id: + code, body, _ = s_consumer.post( + f"{GATEWAY}/api/v1/orders/{order2_id}/pay", + json_body={}, + headers=csrf, + ) + report(f"POST /orders/{order2_id}/pay (before cancel)", code, body) + code, body, _ = s_consumer.post( f"{GATEWAY}/api/v1/orders/{order2_id}/cancel", json_body={}, @@ -945,6 +952,237 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s return order_id +def phase8b_review(s_consumer: Session, order_id, player_id): + print("\n=== Phase 8b: Reviews ===") + if not order_id: + skip("Review flow", "No pending_review order id") + return + + csrf = s_consumer.csrf_headers() + code, body, _ = s_consumer.post( + f"{GATEWAY}/api/v1/orders/{order_id}/review", + json_body={"rating": 5, "content": "great service"}, + headers=csrf, + ) + report(f"POST /orders/{order_id}/review", code, body) + + code, body, _ = s_consumer.get(f"{GATEWAY}/api/v1/orders/{order_id}/reviews") + report(f"GET /orders/{order_id}/reviews", code, body) + if code == 200: + report_check( + f"GET /orders/{order_id}/reviews shape", + isinstance(pick_items(body), list) and isinstance(body.get("meta"), dict), + body, + ) + + code, body, _ = s_consumer.get(f"{GATEWAY}/api/v1/reviews?limit=20") + report("GET /reviews?limit=20", code, body) + + if player_id: + code, body, _ = s_consumer.get( + f"{GATEWAY}/api/v1/users/{player_id}/reviews?limit=20", + ) + report(f"GET /users/{player_id}/reviews?limit=20", code, body) + + +def phase8c_dispute(s_consumer: Session, s_actor: Session, player_id, service_id, shop_id): + print("\n=== Phase 8c: Disputes ===") + if not player_id or not service_id: + skip("Dispute flow", "Missing player or service id") + return 0 + + csrf = s_consumer.csrf_headers() + payload = { + "playerId": player_id, + "serviceId": service_id, + "quantity": 1, + "note": "test dispute order", + } + if shop_id: + payload["shopId"] = shop_id + + code, body, _ = s_consumer.post( + f"{GATEWAY}/api/v1/orders", + json_body=payload, + headers=csrf, + ) + report("POST /orders (create for dispute)", code, body) + order_obj = body.get("order", {}) if isinstance(body, dict) else {} + order_id = order_obj.get("id", 0) if isinstance(order_obj, dict) else 0 + if not order_id: + skip("Dispute flow", "No order id returned") + return 0 + + code, body, _ = s_consumer.post( + f"{GATEWAY}/api/v1/orders/{order_id}/pay", + json_body={}, + headers=csrf, + ) + report(f"POST /orders/{order_id}/pay (dispute flow)", code, body) + + code, body, _ = s_actor.post( + f"{GATEWAY}/api/v1/orders/{order_id}/accept", + json_body={}, + headers=s_actor.csrf_headers(), + ) + report(f"POST /orders/{order_id}/accept (dispute flow)", code, body) + + code, body, _ = s_consumer.post( + f"{GATEWAY}/api/v1/orders/{order_id}/dispute", + json_body={ + "reason": "test dispute reason", + "evidence": ["http://example.com/evidence.jpg"], + }, + headers=csrf, + ) + report(f"POST /orders/{order_id}/dispute", code, body) + + code, body, _ = s_consumer.get(f"{GATEWAY}/api/v1/orders/{order_id}/dispute") + report(f"GET /orders/{order_id}/dispute", code, body) + dispute_id = as_int(body.get("id")) if isinstance(body, dict) else 0 + + code, body, _ = s_consumer.get(f"{GATEWAY}/api/v1/disputes") + report("GET /disputes", code, body) + + code, body, _ = s_consumer.get(f"{GATEWAY}/api/v1/disputes?status=open") + report("GET /disputes?status=open", code, body) + + if dispute_id: + code, body, _ = s_actor.post( + f"{GATEWAY}/api/v1/disputes/{dispute_id}/response", + json_body={ + "reason": "test respondent guard", + "evidence": ["http://example.com/response.jpg"], + }, + headers=s_actor.csrf_headers(), + ) + report( + f"POST /disputes/{dispute_id}/response (expect participant check)", + code, + body, + expect_status=(400, 403, 500), + ) + + code, body, _ = s_consumer.post( + f"{GATEWAY}/api/v1/disputes/{dispute_id}/appeal", + json_body={"reason": "test appeal guard"}, + headers=csrf, + ) + report( + f"POST /disputes/{dispute_id}/appeal (expect status check)", + code, + body, + expect_status=(400, 403, 500), + ) + + return dispute_id + + +def phase8d_notifications(s: Session): + print("\n=== Phase 8d: Notifications ===") + code, body, _ = s.get(f"{GATEWAY}/api/v1/notifications") + report("GET /notifications", code, body) + + items = pick_items(body) if code == 200 and isinstance(body, dict) else [] + if items: + notification_id = as_int(items[0].get("id")) + if notification_id: + code, body, _ = s.put( + f"{GATEWAY}/api/v1/notifications/{notification_id}/read", + json_body={}, + headers=s.csrf_headers(), + ) + report(f"PUT /notifications/{notification_id}/read", code, body) + else: + skip("PUT /notifications/:id/read", "No notification item returned") + + code, body, _ = s.put( + f"{GATEWAY}/api/v1/notifications/read-all", + json_body={}, + headers=s.csrf_headers(), + ) + report("PUT /notifications/read-all", code, body) + + +def phase8e_search_and_favorites(s: Session, user_id, player_id, shop_id): + print("\n=== Phase 8e: Search & Favorites ===") + + code, body, _ = s.get(f"{GATEWAY}/api/v1/search?q=LOL&limit=10") + report("GET /search?q=LOL&limit=10", code, body) + if code == 200: + report_check( + "GET /search response shape", + isinstance(pick_items(body), list) and isinstance(body.get("meta"), dict), + body, + ) + + code, body, _ = s.get(f"{GATEWAY}/api/v1/recommendations/home?limit=10") + report("GET /recommendations/home?limit=10", code, body) + if code == 200: + report_check( + "GET /recommendations/home response shape", + isinstance(pick_items(body), list) and isinstance(body.get("meta"), dict), + body, + ) + + code, body, _ = s.get(f"{GATEWAY}/api/v1/favorites") + report("GET /favorites", code, body) + + if not player_id or not user_id: + skip("Favorites mutation flow", "Missing user or player id") + return + + code, body, _ = s.post( + f"{GATEWAY}/api/v1/favorites", + json_body={"targetType": "player", "targetId": str(player_id)}, + headers=s.csrf_headers(), + ) + report("POST /favorites (player)", code, body) + + code, body, _ = s.get( + f"{GATEWAY}/api/v1/users/{user_id}/favorites/check" + f"?targetType=player&targetId={player_id}", + ) + report(f"GET /users/{user_id}/favorites/check (player)", code, body) + if code == 200: + report_check("favorite check after add", body.get("favorited") is True, body) + + if shop_id: + code, body, _ = s.post( + f"{GATEWAY}/api/v1/favorites", + json_body={"targetType": "shop", "targetId": str(shop_id)}, + headers=s.csrf_headers(), + ) + report("POST /favorites (shop)", code, body) + + code, body, _ = s.get(f"{GATEWAY}/api/v1/favorites") + report("GET /favorites (after add)", code, body) + favorite_id = 0 + for item in pick_items(body): + if ( + item.get("targetType") == "player" + and str(item.get("targetId")) == str(player_id) + ): + favorite_id = as_int(item.get("id")) + break + report_check("locate favorite id", bool(favorite_id), {"id": favorite_id}) + + if favorite_id: + code, body, _ = s.delete( + f"{GATEWAY}/api/v1/favorites/{favorite_id}", + headers=s.csrf_headers(), + ) + report(f"DELETE /favorites/{favorite_id}", code, body) + + code, body, _ = s.get( + f"{GATEWAY}/api/v1/users/{user_id}/favorites/check" + f"?targetType=player&targetId={player_id}", + ) + report(f"GET /users/{user_id}/favorites/check (after delete)", code, body) + if code == 200: + report_check("favorite check after delete", body.get("favorited") is False, body) + + def phase9_wallet(s: Session): print("\n=== Phase 9: Wallet ===") csrf = s.csrf_headers() @@ -1238,7 +1476,11 @@ def main(): player_id, service_id = phase6_player(s_user, game_id) shop_id = phase7_shop(s_user, s_consumer, user_id, invited_player_id) - phase8_order(s_consumer, s_user, player_id, service_id, shop_id) + order_id = phase8_order(s_consumer, s_user, player_id, service_id, shop_id) + phase8b_review(s_consumer, order_id, player_id) + phase8c_dispute(s_consumer, s_user, player_id, service_id, shop_id) + phase8d_notifications(s_consumer) + phase8e_search_and_favorites(s_consumer, consumer_user_id, player_id, shop_id) phase9_wallet(s_user) phase10_community(s_user, user_id) phase11_objectstory(s_user)