From 91260bbd8577e4af91ef585b94cd03bdb3999d40 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 11:35:44 +0000 Subject: [PATCH 1/3] Initial plan From 420e90adbd0599b786df6ba6c09cb4f849b204f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 11:42:16 +0000 Subject: [PATCH 2/3] Fix check_win: Add server ID to request ID mapping for order tracking Co-authored-by: theshadow76 <59869868+theshadow76@users.noreply.github.com> --- pocketoptionapi_async/client.py | 35 +++-- tests/test_check_win.py | 239 ++++++++++++++++++++++++++++++++ 2 files changed, 266 insertions(+), 8 deletions(-) create mode 100644 tests/test_check_win.py diff --git a/pocketoptionapi_async/client.py b/pocketoptionapi_async/client.py index 8c2b335..a79d169 100644 --- a/pocketoptionapi_async/client.py +++ b/pocketoptionapi_async/client.py @@ -93,6 +93,7 @@ def __init__( self._orders: Dict[str, OrderResult] = {} self._active_orders: Dict[str, OrderResult] = {} self._order_results: Dict[str, OrderResult] = {} + self._server_id_to_request_id: Dict[str, str] = {} # Maps server deal IDs to client request IDs self._candles_cache: Dict[str, List[Candle]] = {} self._server_time: Optional[ServerTime] = None self._event_callbacks: Dict[str, List[Callable]] = defaultdict(list) @@ -1035,6 +1036,13 @@ async def _on_json_data(self, data: Dict[str, Any]) -> None: if "requestId" in data and "asset" in data and "amount" in data: request_id = str(data["requestId"]) + # Store mapping from server ID to request ID if server ID is present + if "id" in data: + server_id = str(data["id"]) + self._server_id_to_request_id[server_id] = request_id + if self.enable_logging: + logger.debug(f"Mapped server ID {server_id} to request ID {request_id}") + # If this is a new order, add it to tracking if ( request_id not in self._active_orders @@ -1069,10 +1077,17 @@ async def _on_json_data(self, data: Dict[str, Any]) -> None: elif "deals" in data and isinstance(data["deals"], list): for deal in data["deals"]: if isinstance(deal, dict) and "id" in deal: - order_id = str(deal["id"]) - - if order_id in self._active_orders: - active_order = self._active_orders[order_id] + server_deal_id = str(deal["id"]) + + # Try to find the request_id for this server deal ID + request_id = self._server_id_to_request_id.get(server_deal_id) + + # If we have a mapping, use request_id to find the order + # Otherwise, fall back to trying server_deal_id directly + lookup_id = request_id if request_id else server_deal_id + + if lookup_id in self._active_orders: + active_order = self._active_orders[lookup_id] profit = float(deal.get("profit", 0)) # Determine status @@ -1096,13 +1111,17 @@ async def _on_json_data(self, data: Dict[str, Any]) -> None: payout=deal.get("payout"), ) - # Move from active to completed - self._order_results[order_id] = result - del self._active_orders[order_id] + # Move from active to completed - use the original order_id (request_id) + self._order_results[active_order.order_id] = result + del self._active_orders[lookup_id] + + # Clean up the server ID mapping + if request_id and server_deal_id in self._server_id_to_request_id: + del self._server_id_to_request_id[server_deal_id] if self.enable_logging: logger.success( - f" Order {order_id} completed via JSON data: {status.value} - Profit: ${profit:.2f}" + f" Order {active_order.order_id} completed via JSON data: {status.value} - Profit: ${profit:.2f}" ) await self._emit_event("order_closed", result) diff --git a/tests/test_check_win.py b/tests/test_check_win.py new file mode 100644 index 0000000..26fbe81 --- /dev/null +++ b/tests/test_check_win.py @@ -0,0 +1,239 @@ +""" +Test script to verify the check_win functionality +Tests that the server ID to request ID mapping works correctly +""" + +import asyncio +from datetime import datetime, timedelta +from pocketoptionapi_async import AsyncPocketOptionClient, OrderDirection +from pocketoptionapi_async.models import OrderResult, OrderStatus + + +async def test_check_win_id_mapping(): + """Test that server deal IDs are properly mapped to client request IDs""" + + # Create a client with a dummy SSID (we won't connect, just test internal logic) + dummy_ssid = r'42["auth",{"session":"test_session","isDemo":1,"uid":12345,"platform":1}]' + client = AsyncPocketOptionClient(ssid=dummy_ssid, is_demo=True, enable_logging=False) + + print("Testing check_win ID mapping fix") + print("=" * 50) + + # Test 1: Verify the mapping dictionary exists + print("\nTest 1: Verify _server_id_to_request_id mapping exists") + assert hasattr(client, '_server_id_to_request_id'), "Client should have _server_id_to_request_id dict" + assert isinstance(client._server_id_to_request_id, dict), "Should be a dictionary" + print(" ✅ PASS: _server_id_to_request_id mapping exists") + + # Test 2: Simulate order creation with server ID mapping + print("\nTest 2: Simulate order data with both requestId and server id") + + client_request_id = "abc-123-def-456" # Our client-generated UUID + server_deal_id = "98765432" # Server-assigned ID + + # Simulate the server response that includes both IDs + order_data = { + "requestId": client_request_id, + "id": server_deal_id, + "asset": "EURUSD_otc", + "amount": 10.0, + "command": 0, # CALL + "time": 60 + } + + # Call the handler that processes order data + await client._on_json_data(order_data) + + # Verify the order was added to active orders with request_id + assert client_request_id in client._active_orders, "Order should be in active orders with request_id" + print(f" ✅ PASS: Order added to active orders with request_id: {client_request_id}") + + # Verify the server ID mapping was created + assert server_deal_id in client._server_id_to_request_id, "Server ID should be mapped to request_id" + assert client._server_id_to_request_id[server_deal_id] == client_request_id, "Mapping should point to request_id" + print(f" ✅ PASS: Server ID {server_deal_id} mapped to request_id {client_request_id}") + + # Test 3: Simulate deal completion using server's deal ID + print("\nTest 3: Simulate deal completion with server's deal ID") + + deal_data = { + "deals": [ + { + "id": server_deal_id, # Server uses its own ID + "profit": 8.5, + "payout": 85.0 + } + ] + } + + # Call the handler that processes deal completion + await client._on_json_data(deal_data) + + # Verify the order was moved from active to completed + assert client_request_id not in client._active_orders, "Order should be removed from active orders" + assert client_request_id in client._order_results, "Order should be in order_results with request_id" + print(f" ✅ PASS: Order moved from active to completed using request_id") + + # Verify the result data is correct + result = client._order_results[client_request_id] + assert result.order_id == client_request_id, "Result order_id should match request_id" + assert result.profit == 8.5, f"Profit should be 8.5, got {result.profit}" + assert result.status == OrderStatus.WIN, f"Status should be WIN, got {result.status}" + print(f" ✅ PASS: Order result has correct profit ({result.profit}) and status ({result.status})") + + # Verify the server ID mapping was cleaned up + assert server_deal_id not in client._server_id_to_request_id, "Server ID mapping should be cleaned up" + print(" ✅ PASS: Server ID mapping was cleaned up after order completion") + + # Test 4: Test check_win function can find the completed order + print("\nTest 4: Verify check_win finds the completed order") + + check_result = await client.check_win(client_request_id, max_wait_time=1.0) + + assert check_result is not None, "check_win should return a result" + assert check_result["completed"] == True, "Order should be completed" + assert check_result["result"] == "win", f"Result should be 'win', got {check_result['result']}" + assert check_result["profit"] == 8.5, f"Profit should be 8.5, got {check_result['profit']}" + print(f" ✅ PASS: check_win returned correct result: {check_result}") + + # Test 5: Test check_order_result function + print("\nTest 5: Verify check_order_result finds the completed order") + + order_result = await client.check_order_result(client_request_id) + + assert order_result is not None, "check_order_result should return a result" + assert order_result.order_id == client_request_id, "Order ID should match" + assert order_result.profit == 8.5, "Profit should be correct" + print(f" ✅ PASS: check_order_result returned correct result") + + print("\n" + "=" * 50) + print("🎉 ALL TESTS PASSED! check_win ID mapping fix is working!") + + return True + + +async def test_check_win_loss_scenario(): + """Test that loss orders are correctly handled""" + + dummy_ssid = r'42["auth",{"session":"test_session","isDemo":1,"uid":12345,"platform":1}]' + client = AsyncPocketOptionClient(ssid=dummy_ssid, is_demo=True, enable_logging=False) + + print("\nTesting check_win with loss scenario") + print("=" * 50) + + client_request_id = "loss-order-123" + server_deal_id = "88888888" + + # Create order + order_data = { + "requestId": client_request_id, + "id": server_deal_id, + "asset": "EURUSD_otc", + "amount": 10.0, + "command": 1, # PUT + "time": 60 + } + await client._on_json_data(order_data) + + # Complete order with a loss + deal_data = { + "deals": [ + { + "id": server_deal_id, + "profit": -10.0, # Lost the trade + "payout": 0 + } + ] + } + await client._on_json_data(deal_data) + + # Verify check_win returns loss + check_result = await client.check_win(client_request_id, max_wait_time=1.0) + + assert check_result["result"] == "loss", f"Result should be 'loss', got {check_result['result']}" + assert check_result["profit"] == -10.0, f"Profit should be -10.0, got {check_result['profit']}" + print(f" ✅ PASS: check_win correctly identifies loss: {check_result}") + + return True + + +async def test_check_win_fallback_without_mapping(): + """Test that check_win still works if server ID happens to match request ID (backward compatibility)""" + + dummy_ssid = r'42["auth",{"session":"test_session","isDemo":1,"uid":12345,"platform":1}]' + client = AsyncPocketOptionClient(ssid=dummy_ssid, is_demo=True, enable_logging=False) + + print("\nTesting check_win fallback (no mapping scenario)") + print("=" * 50) + + # Simulate a case where the order was added directly with the server ID + # (e.g., if server returns request matching what we sent) + order_id = "direct-order-789" + + # Directly add to active orders (simulating order placement) + order_result = OrderResult( + order_id=order_id, + asset="GBPUSD_otc", + amount=5.0, + direction=OrderDirection.CALL, + duration=60, + status=OrderStatus.ACTIVE, + placed_at=datetime.now(), + expires_at=datetime.now() + timedelta(seconds=60), + ) + client._active_orders[order_id] = order_result + + # Complete the order using the same ID (no mapping needed) + deal_data = { + "deals": [ + { + "id": order_id, # Using the same ID + "profit": 4.25, + "payout": 85.0 + } + ] + } + await client._on_json_data(deal_data) + + # Verify order was completed + check_result = await client.check_win(order_id, max_wait_time=1.0) + + assert check_result["result"] == "win", f"Result should be 'win', got {check_result['result']}" + print(f" ✅ PASS: Fallback without mapping works correctly") + + return True + + +async def run_all_tests(): + """Run all check_win tests""" + all_passed = True + + try: + all_passed = await test_check_win_id_mapping() and all_passed + except Exception as e: + print(f"❌ FAIL: test_check_win_id_mapping - {e}") + all_passed = False + + try: + all_passed = await test_check_win_loss_scenario() and all_passed + except Exception as e: + print(f"❌ FAIL: test_check_win_loss_scenario - {e}") + all_passed = False + + try: + all_passed = await test_check_win_fallback_without_mapping() and all_passed + except Exception as e: + print(f"❌ FAIL: test_check_win_fallback_without_mapping - {e}") + all_passed = False + + print("\n" + "=" * 50) + if all_passed: + print("🎉 ALL CHECK_WIN TESTS PASSED!") + else: + print("❌ SOME TESTS FAILED") + + return all_passed + + +if __name__ == "__main__": + asyncio.run(run_all_tests()) From 2829a81d0c3d467074379331cda39ef6b31bbba5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 11:44:53 +0000 Subject: [PATCH 3/3] Address code review feedback: improve boolean comparison and null checks Co-authored-by: theshadow76 <59869868+theshadow76@users.noreply.github.com> --- pocketoptionapi_async/client.py | 13 +++++++------ tests/test_check_win.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pocketoptionapi_async/client.py b/pocketoptionapi_async/client.py index a79d169..51ca19a 100644 --- a/pocketoptionapi_async/client.py +++ b/pocketoptionapi_async/client.py @@ -1036,12 +1036,13 @@ async def _on_json_data(self, data: Dict[str, Any]) -> None: if "requestId" in data and "asset" in data and "amount" in data: request_id = str(data["requestId"]) - # Store mapping from server ID to request ID if server ID is present - if "id" in data: + # Store mapping from server ID to request ID if server ID is present and valid + if "id" in data and data["id"]: server_id = str(data["id"]) - self._server_id_to_request_id[server_id] = request_id - if self.enable_logging: - logger.debug(f"Mapped server ID {server_id} to request ID {request_id}") + if server_id: # Ensure string is not empty + self._server_id_to_request_id[server_id] = request_id + if self.enable_logging: + logger.debug(f"Mapped server ID {server_id} to request ID {request_id}") # If this is a new order, add it to tracking if ( @@ -1084,7 +1085,7 @@ async def _on_json_data(self, data: Dict[str, Any]) -> None: # If we have a mapping, use request_id to find the order # Otherwise, fall back to trying server_deal_id directly - lookup_id = request_id if request_id else server_deal_id + lookup_id = request_id or server_deal_id if lookup_id in self._active_orders: active_order = self._active_orders[lookup_id] diff --git a/tests/test_check_win.py b/tests/test_check_win.py index 26fbe81..0651c59 100644 --- a/tests/test_check_win.py +++ b/tests/test_check_win.py @@ -91,7 +91,7 @@ async def test_check_win_id_mapping(): check_result = await client.check_win(client_request_id, max_wait_time=1.0) assert check_result is not None, "check_win should return a result" - assert check_result["completed"] == True, "Order should be completed" + assert check_result["completed"], "Order should be completed" assert check_result["result"] == "win", f"Result should be 'win', got {check_result['result']}" assert check_result["profit"] == 8.5, f"Profit should be 8.5, got {check_result['profit']}" print(f" ✅ PASS: check_win returned correct result: {check_result}")