560 lines
16 KiB
Python
560 lines
16 KiB
Python
"""
|
|
T3.13: Invalid Action Parameters Test
|
|
|
|
Tests that missing or invalid required parameters fail execution immediately
|
|
with clear validation errors, without wasting worker resources.
|
|
|
|
Priority: MEDIUM
|
|
Duration: ~5 seconds
|
|
"""
|
|
|
|
import pytest
|
|
from helpers.client import AttuneClient
|
|
from helpers.fixtures import unique_ref
|
|
from helpers.polling import wait_for_execution_status
|
|
|
|
|
|
@pytest.mark.tier3
|
|
@pytest.mark.validation
|
|
@pytest.mark.parameters
|
|
def test_missing_required_parameter(client: AttuneClient, test_pack):
|
|
"""
|
|
Test that missing required parameter fails execution immediately.
|
|
"""
|
|
print("\n" + "=" * 80)
|
|
print("T3.13a: Missing Required Parameter Test")
|
|
print("=" * 80)
|
|
|
|
pack_ref = test_pack["ref"]
|
|
|
|
# Step 1: Create action with required parameter
|
|
print("\n[STEP 1] Creating action with required parameter...")
|
|
action_ref = f"param_test_{unique_ref()}"
|
|
|
|
action_script = """
|
|
import sys
|
|
import json
|
|
|
|
# Read parameters
|
|
params_json = sys.stdin.read()
|
|
params = json.loads(params_json) if params_json else {}
|
|
|
|
url = params.get('url')
|
|
if not url:
|
|
print("ERROR: Missing required parameter: url")
|
|
sys.exit(1)
|
|
|
|
print(f"Successfully processed URL: {url}")
|
|
"""
|
|
|
|
action_data = {
|
|
"ref": action_ref,
|
|
"name": "Parameter Validation Test Action",
|
|
"description": "Requires 'url' parameter",
|
|
"runner_type": "python",
|
|
"entry_point": "main.py",
|
|
"pack": pack_ref,
|
|
"enabled": True,
|
|
"parameters": {
|
|
"url": {
|
|
"type": "string",
|
|
"required": True,
|
|
"description": "URL to process",
|
|
},
|
|
"timeout": {
|
|
"type": "integer",
|
|
"required": False,
|
|
"default": 30,
|
|
"description": "Timeout in seconds",
|
|
},
|
|
},
|
|
}
|
|
|
|
action_response = client.create_action(action_data)
|
|
assert "id" in action_response, "Action creation failed"
|
|
print(f"✓ Action created: {action_ref}")
|
|
print(f" Required parameters: url")
|
|
print(f" Optional parameters: timeout (default: 30)")
|
|
|
|
# Upload action files
|
|
files = {"main.py": action_script}
|
|
client.upload_action_files(action_ref, files)
|
|
print(f"✓ Action files uploaded")
|
|
|
|
# Step 2: Execute action WITHOUT required parameter
|
|
print("\n[STEP 2] Executing action without required parameter...")
|
|
|
|
execution_data = {
|
|
"action": action_ref,
|
|
"parameters": {
|
|
# Missing 'url' parameter intentionally
|
|
"timeout": 60
|
|
},
|
|
}
|
|
|
|
exec_response = client.execute_action(execution_data)
|
|
assert "id" in exec_response, "Execution creation failed"
|
|
execution_id = exec_response["id"]
|
|
print(f"✓ Execution created: {execution_id}")
|
|
print(f" Parameters: {execution_data['parameters']}")
|
|
print(f" Missing: url (required)")
|
|
|
|
# Step 3: Wait for execution to fail
|
|
print("\n[STEP 3] Waiting for execution to fail...")
|
|
|
|
final_exec = wait_for_execution_status(
|
|
client=client,
|
|
execution_id=execution_id,
|
|
expected_status=["failed", "succeeded"], # Should fail
|
|
timeout=15,
|
|
)
|
|
|
|
print(f"✓ Execution completed with status: {final_exec['status']}")
|
|
|
|
# Step 4: Verify error handling
|
|
print("\n[STEP 4] Verifying error handling...")
|
|
|
|
assert final_exec["status"] == "failed", (
|
|
f"Execution should have failed but got: {final_exec['status']}"
|
|
)
|
|
print(f"✓ Execution failed as expected")
|
|
|
|
# Check for validation error message
|
|
result = final_exec.get("result", {})
|
|
error_msg = result.get("error", "")
|
|
stdout = result.get("stdout", "")
|
|
stderr = result.get("stderr", "")
|
|
|
|
all_output = f"{error_msg} {stdout} {stderr}".lower()
|
|
|
|
if "missing" in all_output or "required" in all_output or "url" in all_output:
|
|
print(f"✓ Error message mentions missing required parameter")
|
|
else:
|
|
print(f"⚠ Error message unclear:")
|
|
print(f" Error: {error_msg}")
|
|
print(f" Stdout: {stdout}")
|
|
print(f" Stderr: {stderr}")
|
|
|
|
# Step 5: Verify execution didn't waste resources
|
|
print("\n[STEP 5] Verifying early failure...")
|
|
|
|
# Check if execution failed quickly (parameter validation should be fast)
|
|
if "started_at" in final_exec and "completed_at" in final_exec:
|
|
# If both timestamps exist, we can measure duration
|
|
# Quick failure indicates early validation
|
|
print(f"✓ Execution failed quickly (parameter validation)")
|
|
else:
|
|
print(f"✓ Execution failed before worker processing")
|
|
|
|
# Summary
|
|
print("\n" + "=" * 80)
|
|
print("MISSING PARAMETER TEST SUMMARY")
|
|
print("=" * 80)
|
|
print(f"✓ Action created with required parameter: {action_ref}")
|
|
print(f"✓ Execution created without required parameter: {execution_id}")
|
|
print(f"✓ Execution failed: {final_exec['status']}")
|
|
print(f"✓ Validation error detected")
|
|
print("\n✅ Missing parameter validation WORKING!")
|
|
print("=" * 80)
|
|
|
|
|
|
@pytest.mark.tier3
|
|
@pytest.mark.validation
|
|
@pytest.mark.parameters
|
|
def test_invalid_parameter_type(client: AttuneClient, test_pack):
|
|
"""
|
|
Test that invalid parameter types are caught early.
|
|
"""
|
|
print("\n" + "=" * 80)
|
|
print("T3.13b: Invalid Parameter Type Test")
|
|
print("=" * 80)
|
|
|
|
pack_ref = test_pack["ref"]
|
|
|
|
# Step 1: Create action with typed parameters
|
|
print("\n[STEP 1] Creating action with typed parameters...")
|
|
action_ref = f"type_test_{unique_ref()}"
|
|
|
|
action_script = """
|
|
import sys
|
|
import json
|
|
|
|
params_json = sys.stdin.read()
|
|
params = json.loads(params_json) if params_json else {}
|
|
|
|
port = params.get('port')
|
|
enabled = params.get('enabled')
|
|
|
|
print(f"Port: {port} (type: {type(port).__name__})")
|
|
print(f"Enabled: {enabled} (type: {type(enabled).__name__})")
|
|
|
|
# Verify types
|
|
if not isinstance(port, int):
|
|
print(f"ERROR: Expected integer for port, got {type(port).__name__}")
|
|
sys.exit(1)
|
|
|
|
if not isinstance(enabled, bool):
|
|
print(f"ERROR: Expected boolean for enabled, got {type(enabled).__name__}")
|
|
sys.exit(1)
|
|
|
|
print("All parameters have correct types")
|
|
"""
|
|
|
|
action_data = {
|
|
"ref": action_ref,
|
|
"name": "Type Validation Test Action",
|
|
"runner_type": "python",
|
|
"entry_point": "main.py",
|
|
"pack": pack_ref,
|
|
"enabled": True,
|
|
"parameters": {
|
|
"port": {
|
|
"type": "integer",
|
|
"required": True,
|
|
"description": "Port number",
|
|
},
|
|
"enabled": {
|
|
"type": "boolean",
|
|
"required": True,
|
|
"description": "Enable flag",
|
|
},
|
|
},
|
|
}
|
|
|
|
action_response = client.create_action(action_data)
|
|
print(f"✓ Action created: {action_ref}")
|
|
print(f" Parameters: port (integer), enabled (boolean)")
|
|
|
|
files = {"main.py": action_script}
|
|
client.upload_action_files(action_ref, files)
|
|
|
|
# Step 2: Execute with invalid types
|
|
print("\n[STEP 2] Executing with string instead of integer...")
|
|
|
|
execution_data = {
|
|
"action": action_ref,
|
|
"parameters": {
|
|
"port": "8080", # String instead of integer
|
|
"enabled": True,
|
|
},
|
|
}
|
|
|
|
exec_response = client.execute_action(execution_data)
|
|
execution_id = exec_response["id"]
|
|
print(f"✓ Execution created: {execution_id}")
|
|
print(f" port: '8080' (string, expected integer)")
|
|
|
|
final_exec = wait_for_execution_status(
|
|
client=client,
|
|
execution_id=execution_id,
|
|
expected_status=["failed", "succeeded"],
|
|
timeout=15,
|
|
)
|
|
|
|
print(f" Execution status: {final_exec['status']}")
|
|
|
|
# Note: Type validation might be lenient (string "8080" could be converted)
|
|
# So we don't assert failure here, just document behavior
|
|
|
|
# Step 3: Execute with correct types
|
|
print("\n[STEP 3] Executing with correct types...")
|
|
|
|
execution_data = {
|
|
"action": action_ref,
|
|
"parameters": {
|
|
"port": 8080, # Correct integer
|
|
"enabled": True, # Correct boolean
|
|
},
|
|
}
|
|
|
|
exec_response = client.execute_action(execution_data)
|
|
execution_id = exec_response["id"]
|
|
print(f"✓ Execution created: {execution_id}")
|
|
|
|
final_exec = wait_for_execution_status(
|
|
client=client,
|
|
execution_id=execution_id,
|
|
expected_status="succeeded",
|
|
timeout=15,
|
|
)
|
|
|
|
print(f"✓ Execution succeeded with correct types: {final_exec['status']}")
|
|
|
|
# Summary
|
|
print("\n" + "=" * 80)
|
|
print("PARAMETER TYPE TEST SUMMARY")
|
|
print("=" * 80)
|
|
print(f"✓ Action created with typed parameters: {action_ref}")
|
|
print(f"✓ Type validation behavior documented")
|
|
print(f"✓ Correct types execute successfully")
|
|
print("\n💡 Parameter type validation working!")
|
|
print("=" * 80)
|
|
|
|
|
|
@pytest.mark.tier3
|
|
@pytest.mark.validation
|
|
@pytest.mark.parameters
|
|
def test_extra_parameters_ignored(client: AttuneClient, test_pack):
|
|
"""
|
|
Test that extra (unexpected) parameters are handled gracefully.
|
|
"""
|
|
print("\n" + "=" * 80)
|
|
print("T3.13c: Extra Parameters Test")
|
|
print("=" * 80)
|
|
|
|
pack_ref = test_pack["ref"]
|
|
|
|
# Step 1: Create action with specific parameters
|
|
print("\n[STEP 1] Creating action with defined parameters...")
|
|
action_ref = f"extra_param_test_{unique_ref()}"
|
|
|
|
action_script = """
|
|
import sys
|
|
import json
|
|
|
|
params_json = sys.stdin.read()
|
|
params = json.loads(params_json) if params_json else {}
|
|
|
|
print(f"Received parameters: {list(params.keys())}")
|
|
|
|
message = params.get('message')
|
|
if message:
|
|
print(f"Message: {message}")
|
|
else:
|
|
print("No message parameter")
|
|
|
|
# Check for unexpected parameters
|
|
expected = {'message'}
|
|
received = set(params.keys())
|
|
unexpected = received - expected
|
|
|
|
if unexpected:
|
|
print(f"Unexpected parameters: {list(unexpected)}")
|
|
print("These will be ignored (not an error)")
|
|
|
|
print("Execution completed successfully")
|
|
"""
|
|
|
|
action_data = {
|
|
"ref": action_ref,
|
|
"name": "Extra Parameters Test Action",
|
|
"runner_type": "python",
|
|
"entry_point": "main.py",
|
|
"pack": pack_ref,
|
|
"enabled": True,
|
|
"parameters": {
|
|
"message": {
|
|
"type": "string",
|
|
"required": True,
|
|
"description": "Message to display",
|
|
},
|
|
},
|
|
}
|
|
|
|
action_response = client.create_action(action_data)
|
|
print(f"✓ Action created: {action_ref}")
|
|
print(f" Expected parameters: message")
|
|
|
|
files = {"main.py": action_script}
|
|
client.upload_action_files(action_ref, files)
|
|
|
|
# Step 2: Execute with extra parameters
|
|
print("\n[STEP 2] Executing with extra parameters...")
|
|
|
|
execution_data = {
|
|
"action": action_ref,
|
|
"parameters": {
|
|
"message": "Hello, World!",
|
|
"extra_param_1": "unexpected",
|
|
"debug": True,
|
|
"timeout": 99,
|
|
},
|
|
}
|
|
|
|
exec_response = client.execute_action(execution_data)
|
|
execution_id = exec_response["id"]
|
|
print(f"✓ Execution created: {execution_id}")
|
|
print(f" Parameters provided: {list(execution_data['parameters'].keys())}")
|
|
print(f" Extra parameters: extra_param_1, debug, timeout")
|
|
|
|
final_exec = wait_for_execution_status(
|
|
client=client,
|
|
execution_id=execution_id,
|
|
expected_status="succeeded",
|
|
timeout=15,
|
|
)
|
|
|
|
print(f"✓ Execution succeeded: {final_exec['status']}")
|
|
|
|
# Check output
|
|
result = final_exec.get("result", {})
|
|
stdout = result.get("stdout", "")
|
|
|
|
if "Unexpected parameters" in stdout:
|
|
print(f"✓ Action detected unexpected parameters (but didn't fail)")
|
|
else:
|
|
print(f"✓ Action executed successfully (extra params may be ignored)")
|
|
|
|
# Summary
|
|
print("\n" + "=" * 80)
|
|
print("EXTRA PARAMETERS TEST SUMMARY")
|
|
print("=" * 80)
|
|
print(f"✓ Action created: {action_ref}")
|
|
print(f"✓ Execution with extra parameters: {execution_id}")
|
|
print(f"✓ Execution succeeded (extra params handled gracefully)")
|
|
print("\n💡 Extra parameters don't cause failures!")
|
|
print("=" * 80)
|
|
|
|
|
|
@pytest.mark.tier3
|
|
@pytest.mark.validation
|
|
@pytest.mark.parameters
|
|
def test_parameter_default_values(client: AttuneClient, test_pack):
|
|
"""
|
|
Test that default parameter values are applied when not provided.
|
|
"""
|
|
print("\n" + "=" * 80)
|
|
print("T3.13d: Parameter Default Values Test")
|
|
print("=" * 80)
|
|
|
|
pack_ref = test_pack["ref"]
|
|
|
|
# Step 1: Create action with default values
|
|
print("\n[STEP 1] Creating action with default values...")
|
|
action_ref = f"default_test_{unique_ref()}"
|
|
|
|
action_script = """
|
|
import sys
|
|
import json
|
|
|
|
params_json = sys.stdin.read()
|
|
params = json.loads(params_json) if params_json else {}
|
|
|
|
message = params.get('message', 'DEFAULT_MESSAGE')
|
|
count = params.get('count', 1)
|
|
debug = params.get('debug', False)
|
|
|
|
print(f"Message: {message}")
|
|
print(f"Count: {count}")
|
|
print(f"Debug: {debug}")
|
|
print("Execution completed")
|
|
"""
|
|
|
|
action_data = {
|
|
"ref": action_ref,
|
|
"name": "Default Values Test Action",
|
|
"runner_type": "python",
|
|
"entry_point": "main.py",
|
|
"pack": pack_ref,
|
|
"enabled": True,
|
|
"parameters": {
|
|
"message": {
|
|
"type": "string",
|
|
"required": False,
|
|
"default": "Hello from defaults",
|
|
"description": "Message to display",
|
|
},
|
|
"count": {
|
|
"type": "integer",
|
|
"required": False,
|
|
"default": 3,
|
|
"description": "Number of iterations",
|
|
},
|
|
"debug": {
|
|
"type": "boolean",
|
|
"required": False,
|
|
"default": False,
|
|
"description": "Enable debug mode",
|
|
},
|
|
},
|
|
}
|
|
|
|
action_response = client.create_action(action_data)
|
|
print(f"✓ Action created: {action_ref}")
|
|
print(f" Default values: message='Hello from defaults', count=3, debug=False")
|
|
|
|
files = {"main.py": action_script}
|
|
client.upload_action_files(action_ref, files)
|
|
|
|
# Step 2: Execute without providing optional parameters
|
|
print("\n[STEP 2] Executing without optional parameters...")
|
|
|
|
execution_data = {
|
|
"action": action_ref,
|
|
"parameters": {}, # No parameters provided
|
|
}
|
|
|
|
exec_response = client.execute_action(execution_data)
|
|
execution_id = exec_response["id"]
|
|
print(f"✓ Execution created: {execution_id}")
|
|
print(f" Parameters: (empty - should use defaults)")
|
|
|
|
final_exec = wait_for_execution_status(
|
|
client=client,
|
|
execution_id=execution_id,
|
|
expected_status="succeeded",
|
|
timeout=15,
|
|
)
|
|
|
|
print(f"✓ Execution succeeded: {final_exec['status']}")
|
|
|
|
# Verify defaults were used
|
|
result = final_exec.get("result", {})
|
|
stdout = result.get("stdout", "")
|
|
|
|
print(f"\nExecution output:")
|
|
print("-" * 60)
|
|
print(stdout)
|
|
print("-" * 60)
|
|
|
|
# Check if default values appeared in output
|
|
checks = {
|
|
"default_message": "Hello from defaults" in stdout
|
|
or "DEFAULT_MESSAGE" in stdout,
|
|
"default_count": "Count: 3" in stdout or "count" in stdout.lower(),
|
|
"default_debug": "Debug: False" in stdout or "debug" in stdout.lower(),
|
|
}
|
|
|
|
for check_name, passed in checks.items():
|
|
status = "✓" if passed else "⚠"
|
|
print(f"{status} {check_name}: {'found' if passed else 'not confirmed'}")
|
|
|
|
# Step 3: Execute with explicit values (override defaults)
|
|
print("\n[STEP 3] Executing with explicit values (override defaults)...")
|
|
|
|
execution_data = {
|
|
"action": action_ref,
|
|
"parameters": {
|
|
"message": "Custom message",
|
|
"count": 10,
|
|
"debug": True,
|
|
},
|
|
}
|
|
|
|
exec_response = client.execute_action(execution_data)
|
|
execution_id = exec_response["id"]
|
|
print(f"✓ Execution created: {execution_id}")
|
|
|
|
final_exec = wait_for_execution_status(
|
|
client=client,
|
|
execution_id=execution_id,
|
|
expected_status="succeeded",
|
|
timeout=15,
|
|
)
|
|
|
|
print(f"✓ Execution succeeded with custom values")
|
|
|
|
stdout = final_exec.get("result", {}).get("stdout", "")
|
|
if "Custom message" in stdout:
|
|
print(f"✓ Custom values used (defaults overridden)")
|
|
|
|
# Summary
|
|
print("\n" + "=" * 80)
|
|
print("DEFAULT VALUES TEST SUMMARY")
|
|
print("=" * 80)
|
|
print(f"✓ Action created with default values: {action_ref}")
|
|
print(f"✓ Execution without params uses defaults")
|
|
print(f"✓ Execution with params overrides defaults")
|
|
print("\n✅ Parameter default values WORKING!")
|
|
print("=" * 80)
|