""" Integration tests for client-hub messaging using Selenium. Tests that client doesn't receive its own Join message back. """ import pytest import time from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC pytestmark = [pytest.mark.nondestructive] @pytest.mark.integration def test_client_does_not_receive_own_join_message(ready_app, wait): """ Test that when a client sends a Join message to the hub, it doesn't receive it back through its own message handler. This test: 1. Clicks "Host" to create a hub and connect host client 2. Injects JS to monitor if the host client receives a Join message 3. Verifies the host client does NOT receive its own Join message """ driver = ready_app # Inject monitoring script before clicking Host driver.execute_script(""" // Track if client receives Join message window.clientReceivedOwnJoin = false; // Monkey-patch Client.handle_message to detect Join messages (function() { // Wait for modules to load var checkInterval = setInterval(function() { try { var clientModule = require('client'); var Client = clientModule.Client; if (Client && Client.prototype && Client.prototype.handle_message) { var originalHandleMessage = Client.prototype.handle_message; Client.prototype.handle_message = function(callback_id, message_data) { // Check if this is a Join message if (message_data && Array.isArray(message_data) && message_data[0] === 'Join') { console.log('[TEST] Client received Join message!'); window.clientReceivedOwnJoin = true; } // Call original method return originalHandleMessage.call(this, callback_id, message_data); }; console.log('[TEST] Successfully patched Client.handle_message'); clearInterval(checkInterval); } } catch (e) { // Modules not loaded yet, keep trying } }, 100); // Give up after 10 seconds setTimeout(function() { clearInterval(checkInterval); }, 10000); })(); """) # Wait for Host button to be available time.sleep(1) # Click Host button try: # Try to find and click Host button via JavaScript clicked = driver.execute_script(""" // Look for Host button in the UI var buttons = document.querySelectorAll('canvas'); if (buttons.length > 0) { // Simulate click on canvas where Host button would be var canvas = buttons[0]; var event = new MouseEvent('click', { view: window, bubbles: true, cancelable: true, clientX: canvas.width / 2, clientY: canvas.height / 2 - 64 // Approximate position of Host button }); canvas.dispatchEvent(event); return true; } return false; """) if not clicked: # Fallback: Try clicking the canvas directly canvas = driver.find_element(By.ID, "canvas") canvas.click() except Exception as e: pytest.fail(f"Failed to click Host button: {e}") # Wait for connection to establish time.sleep(3) # Check if client received its own Join message received_own_join = driver.execute_script("return window.clientReceivedOwnJoin || false;") # Get console logs for debugging logs = driver.get_log("browser") test_logs = [log['message'] for log in logs if '[TEST]' in log.get('message', '')] print(f"\n=== Test Debug Info ===") print(f"Client received own Join: {received_own_join}") print(f"Test logs: {test_logs}") # Assert client did NOT receive its own Join message assert not received_own_join, "Client should not receive its own Join message back from hub" @pytest.mark.integration def test_hub_receives_join_message(ready_app, wait): """ Test that the hub correctly receives the Join message from a connecting client. This test: 1. Clicks "Host" to create a hub 2. Monitors hub.handle_message for Join messages 3. Verifies the hub receives the Join message """ driver = ready_app # Inject monitoring script driver.execute_script(""" // Track if hub receives Join message window.hubReceivedJoin = false; window.joinMessageData = null; // Monkey-patch Hub.handle_message to detect Join messages (function() { var checkInterval = setInterval(function() { try { var hubModule = require('hub'); var Hub = hubModule.Hub; if (Hub && Hub.prototype && Hub.prototype.handle_message) { var originalHandleMessage = Hub.prototype.handle_message; Hub.prototype.handle_message = function(from_client, msgname, data) { // Normalise arguments: depending on the Lua/JS bridge, // the message array may arrive as `msgname` or `data`, // and may be 0- or 1-indexed from JS. var message = null; if (Array.isArray(msgname)) { message = msgname; } else if (Array.isArray(data)) { message = data; } var msg_type = null; var msg_data = null; if (message) { msg_type = message[0] || message[1] || null; msg_data = message[1] || message[2] || null; } if (msg_type === 'Join') { console.log('[TEST] Hub received Join message from:', from_client); window.hubReceivedJoin = true; window.joinMessageData = msg_data; } // Call original method return originalHandleMessage.call(this, from_client, msgname, data); }; console.log('[TEST] Successfully patched Hub.handle_message'); clearInterval(checkInterval); } } catch (e) { // Modules not loaded yet, keep trying } }, 100); setTimeout(function() { clearInterval(checkInterval); }, 10000); })(); """) # Wait for modules to load time.sleep(1) # Click Host button (same approach as previous test) try: clicked = driver.execute_script(""" var canvas = document.getElementById('canvas'); if (canvas) { var event = new MouseEvent('click', { view: window, bubbles: true, cancelable: true, clientX: canvas.width / 2, clientY: canvas.height / 2 - 64 }); canvas.dispatchEvent(event); return true; } return false; """) if not clicked: canvas = driver.find_element(By.ID, "canvas") canvas.click() except Exception as e: pytest.fail(f"Failed to click Host button: {e}") # Wait for connection and message processing time.sleep(3) # Debug world/hub state before assertions world_debug = driver.execute_script(""" try { var world = typeof require === 'function' ? require('world') : null; return { hasWorld: !!world, hasHub: !!(world && world.hub), hasNetwork: !!(world && world.network) }; } catch (e) { return { error: String(e) }; } """) print(f"World debug: {world_debug}") js_flags = driver.execute_script(""" return { clientConnected: !!window._clientConnectedToHub, clientJoinPayload: window._clientJoinPayload || null, hubJoinReceived: !!window._hubJoinReceived, hubJoinData: window._hubJoinData || null }; """) print(f"JS flags: {js_flags}") # Check if hub received Join message (Lua side exposes this via js_bridge) hub_received_join = driver.execute_script("return (window.hubReceivedJoin || window._hubJoinReceived) || false;") join_data = driver.execute_script("return window.joinMessageData || window._hubJoinData || null;") # Get console logs for debugging logs = driver.get_log("browser") test_logs = [log['message'] for log in logs if '[TEST]' in log.get('message', '')] print(f"\n=== Test Debug Info ===") print(f"Hub received Join: {hub_received_join}") print(f"Join data: {join_data}") print(f"Test logs: {test_logs}") # Assert hub received the Join message assert hub_received_join, "Hub should receive Join message from connecting client" assert join_data is not None, "Join message should contain data" # Check that join data has a 'name' field if join_data: assert 'name' in join_data, "Join message data should contain 'name' field" @pytest.mark.integration def test_message_flow_integrity(ready_app, wait): """ End-to-end test of message flow: client sends, hub receives, but client doesn't get echo. This combines both previous tests to verify the complete message flow. """ driver = ready_app # Inject comprehensive monitoring driver.execute_script(""" window.testResults = { clientReceivedJoin: false, hubReceivedJoin: false, joinData: null }; (function() { var checkInterval = setInterval(function() { try { // Patch Client var clientModule = require('client'); var hubModule = require('hub'); if (clientModule && clientModule.Client && hubModule && hubModule.Hub) { var Client = clientModule.Client; var Hub = hubModule.Hub; // Patch client if (Client.prototype.handle_message) { var origClientHandle = Client.prototype.handle_message; Client.prototype.handle_message = function(cid, mdata) { if (mdata && mdata[0] === 'Join') { console.log('[TEST] CLIENT RECEIVED JOIN - THIS IS THE BUG!'); window.testResults.clientReceivedJoin = true; } return origClientHandle.call(this, cid, mdata); }; } // Patch hub if (Hub.prototype.handle_message) { var origHubHandle = Hub.prototype.handle_message; Hub.prototype.handle_message = function(from, msgname, data) { // Normalise message array from Lua/JS bridge. var message = null; if (Array.isArray(msgname)) { message = msgname; } else if (Array.isArray(data)) { message = data; } var msg_type = null; var msg_data = null; if (message) { msg_type = message[0] || message[1] || null; msg_data = message[1] || message[2] || null; } if (msg_type === 'Join') { console.log('[TEST] Hub received Join - CORRECT!'); window.testResults.hubReceivedJoin = true; window.testResults.joinData = msg_data; } return origHubHandle.call(this, from, msgname, data); }; } console.log('[TEST] Patching complete'); clearInterval(checkInterval); } } catch (e) { // Keep trying } }, 100); setTimeout(function() { clearInterval(checkInterval); }, 10000); })(); """) time.sleep(1) # Click Host canvas = driver.find_element(By.ID, "canvas") canvas.click() # Wait for messages to flow time.sleep(3) # Get results, augmenting from Lua-side hub instrumentation if present results = driver.execute_script(""" if (typeof window.testResults === 'undefined') { window.testResults = { clientReceivedJoin: false, hubReceivedJoin: false, joinData: null }; } if (window._hubJoinReceived) { window.testResults.hubReceivedJoin = true; } if (window._hubJoinData) { window.testResults.joinData = window._hubJoinData; } return window.testResults; """) print(f"\n=== Complete Test Results ===") print(f"Results: {results}") # Assertions assert results['hubReceivedJoin'], "Hub must receive Join message" assert not results['clientReceivedJoin'], "Client must NOT receive its own Join message (bug fixed!)" assert results['joinData'] is not None, "Join message must have data"