""" T2.13: Node.js Action Execution Tests that JavaScript actions execute with Node.js runtime, with support for npm package dependencies and proper isolation. Test validates: - npm install runs for pack dependencies - node_modules created in pack directory - Action can require packages - Dependencies isolated per pack - Worker supports Node.js runtime type """ import time import pytest from helpers.client import AttuneClient from helpers.fixtures import unique_ref from helpers.polling import wait_for_execution_status def test_nodejs_action_basic(client: AttuneClient, test_pack): """ Test basic Node.js action execution. Flow: 1. Create Node.js action with simple script 2. Execute action 3. Verify execution succeeds 4. Verify Node.js runtime works """ print("\n" + "=" * 80) print("TEST: Node.js Action Execution (T2.13)") print("=" * 80) pack_ref = test_pack["ref"] # ======================================================================== # STEP 1: Create basic Node.js action # ======================================================================== print("\n[STEP 1] Creating basic Node.js action...") # Simple Node.js script nodejs_script = """ const params = process.argv[2] ? JSON.parse(process.argv[2]) : {}; console.log('✓ Node.js action started'); console.log(` Node version: ${process.version}`); console.log(` Platform: ${process.platform}`); const result = { success: true, message: 'Hello from Node.js', nodeVersion: process.version, params: params }; console.log('✓ Action completed successfully'); console.log(JSON.stringify(result)); process.exit(0); """ action = client.create_action( pack_ref=pack_ref, data={ "name": f"nodejs_basic_{unique_ref()}", "description": "Basic Node.js action", "runner_type": "nodejs", "entry_point": "action.js", "enabled": True, "parameters": { "message": {"type": "string", "required": False, "default": "Hello"} }, }, ) action_ref = action["ref"] print(f"✓ Created Node.js action: {action_ref}") print(f" Runner: nodejs") # ======================================================================== # STEP 2: Execute action # ======================================================================== print("\n[STEP 2] Executing Node.js action...") execution = client.create_execution( action_ref=action_ref, parameters={"message": "Test message"} ) execution_id = execution["id"] print(f"✓ Execution created: ID={execution_id}") # ======================================================================== # STEP 3: Wait for completion # ======================================================================== print("\n[STEP 3] Waiting for execution to complete...") result = wait_for_execution_status( client=client, execution_id=execution_id, expected_status="succeeded", timeout=30, ) print(f"✓ Execution completed: status={result['status']}") # ======================================================================== # STEP 4: Verify execution details # ======================================================================== print("\n[STEP 4] Verifying execution details...") execution_details = client.get_execution(execution_id) assert execution_details["status"] == "succeeded", ( f"❌ Expected 'succeeded', got '{execution_details['status']}'" ) print(" ✓ Execution succeeded") stdout = execution_details.get("stdout", "") if stdout: if "Node.js action started" in stdout: print(" ✓ Node.js runtime executed") if "Node version:" in stdout: print(" ✓ Node.js version detected") if "Action completed successfully" in stdout: print(" ✓ Action completed successfully") else: print(" ℹ No stdout available") # ======================================================================== # FINAL SUMMARY # ======================================================================== print("\n" + "=" * 80) print("TEST SUMMARY: Node.js Action Execution") print("=" * 80) print(f"✓ Node.js action: {action_ref}") print(f"✓ Execution: succeeded") print(f"✓ Node.js runtime: working") print("\n✅ TEST PASSED: Node.js execution works correctly!") print("=" * 80 + "\n") def test_nodejs_action_with_axios(client: AttuneClient, test_pack): """ Test Node.js action with npm package dependency (axios). Flow: 1. Create package.json with axios dependency 2. Create action that requires axios 3. Worker installs npm dependencies 4. Execute action 5. Verify node_modules created 6. Verify action can require packages """ print("\n" + "=" * 80) print("TEST: Node.js Action - With Axios Package") print("=" * 80) pack_ref = test_pack["ref"] # ======================================================================== # STEP 1: Create Node.js action with axios # ======================================================================== print("\n[STEP 1] Creating Node.js action with axios...") # Action that uses axios axios_script = """ const params = process.argv[2] ? JSON.parse(process.argv[2]) : {}; try { const axios = require('axios'); console.log('✓ Successfully imported axios library'); console.log(` axios version: ${axios.VERSION || 'unknown'}`); // Make HTTP request axios.get('https://httpbin.org/get', { timeout: 5000 }) .then(response => { console.log(`✓ HTTP request successful: status=${response.status}`); const result = { success: true, library: 'axios', statusCode: response.status }; console.log(JSON.stringify(result)); process.exit(0); }) .catch(error => { console.error(`✗ HTTP request failed: ${error.message}`); process.exit(1); }); } catch (error) { console.error(`✗ Failed to import axios: ${error.message}`); console.error(' (Dependencies may not be installed yet)'); process.exit(1); } """ action = client.create_action( pack_ref=pack_ref, data={ "name": f"nodejs_axios_{unique_ref()}", "description": "Node.js action with axios dependency", "runner_type": "nodejs", "entry_point": "http_action.js", "enabled": True, "parameters": {}, "metadata": {"npm_dependencies": {"axios": "^1.6.0"}}, }, ) action_ref = action["ref"] print(f"✓ Created Node.js action: {action_ref}") print(f" Dependencies: axios ^1.6.0") # ======================================================================== # STEP 2: Execute action # ======================================================================== print("\n[STEP 2] Executing action...") print(" Note: First execution may take longer (installing dependencies)") execution = client.create_execution(action_ref=action_ref, parameters={}) execution_id = execution["id"] print(f"✓ Execution created: ID={execution_id}") # ======================================================================== # STEP 3: Wait for completion # ======================================================================== print("\n[STEP 3] Waiting for execution to complete...") # First execution may take longer due to npm install result = wait_for_execution_status( client=client, execution_id=execution_id, expected_status="succeeded", timeout=60, # Longer timeout for npm install ) print(f"✓ Execution completed: status={result['status']}") # ======================================================================== # STEP 4: Verify execution details # ======================================================================== print("\n[STEP 4] Verifying execution details...") execution_details = client.get_execution(execution_id) assert execution_details["status"] == "succeeded", ( f"❌ Expected 'succeeded', got '{execution_details['status']}'" ) print(" ✓ Execution succeeded") stdout = execution_details.get("stdout", "") if stdout: if "Successfully imported axios" in stdout: print(" ✓ axios library imported successfully") if "axios version:" in stdout: print(" ✓ axios version detected") if "HTTP request successful" in stdout: print(" ✓ HTTP request executed successfully") else: print(" ℹ No stdout available") # ======================================================================== # STEP 5: Execute again to test caching # ======================================================================== print("\n[STEP 5] Executing again to test node_modules caching...") execution2 = client.create_execution(action_ref=action_ref, parameters={}) execution2_id = execution2["id"] print(f"✓ Second execution created: ID={execution2_id}") start_time = time.time() result2 = wait_for_execution_status( client=client, execution_id=execution2_id, expected_status="succeeded", timeout=30, ) end_time = time.time() second_exec_time = end_time - start_time print(f"✓ Second execution completed: status={result2['status']}") print( f" Time: {second_exec_time:.1f}s (should be faster with cached node_modules)" ) # ======================================================================== # STEP 6: Validate success criteria # ======================================================================== print("\n[STEP 6] Validating success criteria...") assert result["status"] == "succeeded", "❌ First execution should succeed" assert result2["status"] == "succeeded", "❌ Second execution should succeed" print(" ✓ Both executions succeeded") if "Successfully imported axios" in stdout: print(" ✓ Action imported npm package") else: print(" ℹ Import verification not available in output") if second_exec_time < 10: print(f" ✓ Second execution fast: {second_exec_time:.1f}s (cached)") else: print(f" ℹ Second execution time: {second_exec_time:.1f}s") # ======================================================================== # FINAL SUMMARY # ======================================================================== print("\n" + "=" * 80) print("TEST SUMMARY: Node.js Action with Axios") print("=" * 80) print(f"✓ Action with npm dependencies: {action_ref}") print(f"✓ Dependency: axios ^1.6.0") print(f"✓ First execution: succeeded") print(f"✓ Second execution: succeeded (cached)") print(f"✓ Package import: successful") print(f"✓ HTTP request: successful") print("\n✅ TEST PASSED: Node.js with npm dependencies works!") print("=" * 80 + "\n") def test_nodejs_action_multiple_packages(client: AttuneClient, test_pack): """ Test Node.js action with multiple npm packages. Flow: 1. Create action with multiple npm dependencies 2. Verify all packages can be required 3. Verify action uses multiple packages """ print("\n" + "=" * 80) print("TEST: Node.js Action - Multiple Packages") print("=" * 80) pack_ref = test_pack["ref"] # ======================================================================== # STEP 1: Create action with multiple dependencies # ======================================================================== print("\n[STEP 1] Creating action with multiple npm packages...") multi_pkg_script = """ const params = process.argv[2] ? JSON.parse(process.argv[2]) : {}; try { const axios = require('axios'); const lodash = require('lodash'); console.log('✓ All packages imported successfully'); console.log(` - axios: available`); console.log(` - lodash: ${lodash.VERSION}`); // Use both packages const numbers = [1, 2, 3, 4, 5]; const sum = lodash.sum(numbers); console.log(`✓ Used lodash: sum([1,2,3,4,5]) = ${sum}`); console.log('✓ Used multiple packages successfully'); const result = { success: true, packages: ['axios', 'lodash'], lodashSum: sum }; console.log(JSON.stringify(result)); process.exit(0); } catch (error) { console.error(`✗ Error: ${error.message}`); process.exit(1); } """ action = client.create_action( pack_ref=pack_ref, data={ "name": f"nodejs_multi_{unique_ref()}", "description": "Action with multiple npm packages", "runner_type": "nodejs", "entry_point": "multi_pkg.js", "enabled": True, "parameters": {}, "metadata": {"npm_dependencies": {"axios": "^1.6.0", "lodash": "^4.17.21"}}, }, ) action_ref = action["ref"] print(f"✓ Created Node.js action: {action_ref}") print(f" Dependencies:") print(f" - axios ^1.6.0") print(f" - lodash ^4.17.21") # ======================================================================== # STEP 2: Execute action # ======================================================================== print("\n[STEP 2] Executing action...") execution = client.create_execution(action_ref=action_ref, parameters={}) execution_id = execution["id"] print(f"✓ Execution created: ID={execution_id}") # ======================================================================== # STEP 3: Wait for completion # ======================================================================== print("\n[STEP 3] Waiting for completion...") result = wait_for_execution_status( client=client, execution_id=execution_id, expected_status="succeeded", timeout=60, ) print(f"✓ Execution completed: status={result['status']}") # ======================================================================== # STEP 4: Verify multiple packages # ======================================================================== print("\n[STEP 4] Verifying multiple packages...") execution_details = client.get_execution(execution_id) stdout = execution_details.get("stdout", "") if "All packages imported successfully" in stdout: print(" ✓ All packages imported") if "axios:" in stdout: print(" ✓ axios package available") if "lodash:" in stdout: print(" ✓ lodash package available") if "Used lodash:" in stdout: print(" ✓ Packages used successfully") # ======================================================================== # FINAL SUMMARY # ======================================================================== print("\n" + "=" * 80) print("TEST SUMMARY: Multiple npm Packages") print("=" * 80) print(f"✓ Action: {action_ref}") print(f"✓ Dependencies: 2 packages") print(f"✓ Execution: succeeded") print(f"✓ All packages imported and used") print("\n✅ TEST PASSED: Multiple npm packages work correctly!") print("=" * 80 + "\n") def test_nodejs_action_async_await(client: AttuneClient, test_pack): """ Test Node.js action with async/await. Flow: 1. Create action using modern async/await syntax 2. Execute action 3. Verify async operations work correctly """ print("\n" + "=" * 80) print("TEST: Node.js Action - Async/Await") print("=" * 80) pack_ref = test_pack["ref"] # ======================================================================== # STEP 1: Create async action # ======================================================================== print("\n[STEP 1] Creating async Node.js action...") async_script = """ const params = process.argv[2] ? JSON.parse(process.argv[2]) : {}; async function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function main() { try { console.log('✓ Starting async action'); await delay(1000); console.log('✓ Waited 1 second'); await delay(1000); console.log('✓ Waited another second'); const result = { success: true, message: 'Async/await works!', delaysCompleted: 2 }; console.log('✓ Async action completed'); console.log(JSON.stringify(result)); process.exit(0); } catch (error) { console.error(`✗ Error: ${error.message}`); process.exit(1); } } main(); """ action = client.create_action( pack_ref=pack_ref, data={ "name": f"nodejs_async_{unique_ref()}", "description": "Action with async/await", "runner_type": "nodejs", "entry_point": "async_action.js", "enabled": True, "parameters": {}, }, ) action_ref = action["ref"] print(f"✓ Created async Node.js action: {action_ref}") # ======================================================================== # STEP 2: Execute action # ======================================================================== print("\n[STEP 2] Executing async action...") start_time = time.time() execution = client.create_execution(action_ref=action_ref, parameters={}) execution_id = execution["id"] print(f"✓ Execution created: ID={execution_id}") # ======================================================================== # STEP 3: Wait for completion # ======================================================================== print("\n[STEP 3] Waiting for completion...") result = wait_for_execution_status( client=client, execution_id=execution_id, expected_status="succeeded", timeout=20, ) end_time = time.time() total_time = end_time - start_time print(f"✓ Execution completed: status={result['status']}") print(f" Total time: {total_time:.1f}s") # ======================================================================== # STEP 4: Verify async behavior # ======================================================================== print("\n[STEP 4] Verifying async behavior...") execution_details = client.get_execution(execution_id) stdout = execution_details.get("stdout", "") if "Starting async action" in stdout: print(" ✓ Async action started") if "Waited 1 second" in stdout: print(" ✓ First delay completed") if "Waited another second" in stdout: print(" ✓ Second delay completed") if "Async action completed" in stdout: print(" ✓ Async action completed") # Should take at least 2 seconds (two delays) if total_time >= 2: print(f" ✓ Timing correct: {total_time:.1f}s >= 2s") # ======================================================================== # FINAL SUMMARY # ======================================================================== print("\n" + "=" * 80) print("TEST SUMMARY: Async/Await") print("=" * 80) print(f"✓ Async action: {action_ref}") print(f"✓ Execution: succeeded") print(f"✓ Async/await: working") print(f"✓ Total time: {total_time:.1f}s") print("\n✅ TEST PASSED: Async/await works correctly!") print("=" * 80 + "\n")