Files
attune/tests/e2e/tier2/test_t2_13_nodejs_execution.py
2026-02-04 17:46:30 -06:00

575 lines
19 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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")