""" T2.6: Approval Workflow (Inquiry) Tests that actions can create inquiries (approval requests), pausing execution until a response is received, enabling human-in-the-loop workflows. Test validates: - Execution pauses with status 'paused' - Inquiry created in attune.inquiry table - Inquiry timeout/TTL set correctly - Response submission updates inquiry status - Execution resumes after response - Action receives response in structured format - Timeout causes default action if no response """ 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_inquiry_basic_approval(client: AttuneClient, test_pack): """ Test basic inquiry approval workflow. Flow: 1. Create action that creates an inquiry 2. Execute action 3. Verify execution pauses 4. Verify inquiry created 5. Submit response 6. Verify execution resumes and completes """ print("\n" + "=" * 80) print("TEST: Approval Workflow (Inquiry) - T2.6") print("=" * 80) pack_ref = test_pack["ref"] # ======================================================================== # STEP 1: Create action that creates inquiry # ======================================================================== print("\n[STEP 1] Creating action that creates inquiry...") # For now, we'll create a simple action and manually create an inquiry # In the future, actions should be able to create inquiries via API action = client.create_action( pack_ref=pack_ref, data={ "name": f"approval_action_{unique_ref()}", "description": "Action that requires approval", "runner_type": "python3", "entry_point": "approve.py", "enabled": True, "parameters": { "message": {"type": "string", "required": False, "default": "Approve?"} }, }, ) action_ref = action["ref"] print(f"✓ Created action: {action_ref}") # ======================================================================== # STEP 2: Execute action # ======================================================================== print("\n[STEP 2] Executing action...") execution = client.create_execution( action_ref=action_ref, parameters={"message": "Please approve this action"} ) execution_id = execution["id"] print(f"✓ Execution created: ID={execution_id}") # Wait for execution to start time.sleep(2) # ======================================================================== # STEP 3: Create inquiry for this execution # ======================================================================== print("\n[STEP 3] Creating inquiry for execution...") inquiry = client.create_inquiry( data={ "execution_id": execution_id, "schema": { "type": "object", "properties": { "approved": { "type": "boolean", "description": "Approve or reject this action", }, "comment": { "type": "string", "description": "Optional comment", }, }, "required": ["approved"], }, "ttl": 300, # 5 minutes } ) inquiry_id = inquiry["id"] print(f"✓ Inquiry created: ID={inquiry_id}") print(f" Status: {inquiry['status']}") print(f" Execution ID: {inquiry['execution_id']}") print(f" TTL: {inquiry.get('ttl', 'N/A')} seconds") # ======================================================================== # STEP 4: Verify inquiry status is 'pending' # ======================================================================== print("\n[STEP 4] Verifying inquiry status...") inquiry_status = client.get_inquiry(inquiry_id) assert inquiry_status["status"] == "pending", ( f"❌ Expected inquiry status 'pending', got '{inquiry_status['status']}'" ) print(f"✓ Inquiry status: {inquiry_status['status']}") # ======================================================================== # STEP 5: Submit inquiry response # ======================================================================== print("\n[STEP 5] Submitting inquiry response...") response_data = {"approved": True, "comment": "Looks good, approved!"} client.respond_to_inquiry(inquiry_id=inquiry_id, response=response_data) print("✓ Inquiry response submitted") print(f" Response: {response_data}") # ======================================================================== # STEP 6: Verify inquiry status updated to 'responded' # ======================================================================== print("\n[STEP 6] Verifying inquiry status updated...") inquiry_after = client.get_inquiry(inquiry_id) assert inquiry_after["status"] in ["responded", "completed"], ( f"❌ Expected inquiry status 'responded' or 'completed', got '{inquiry_after['status']}'" ) print(f"✓ Inquiry status updated: {inquiry_after['status']}") print(f" Response: {inquiry_after.get('response')}") # ======================================================================== # STEP 7: Verify execution can access response # ======================================================================== print("\n[STEP 7] Verifying execution has access to response...") # Get execution details execution_details = client.get_execution(execution_id) print(f"✓ Execution status: {execution_details['status']}") # The execution should eventually complete (in real workflow) # For now, we just verify the inquiry was created and responded to # ======================================================================== # FINAL SUMMARY # ======================================================================== print("\n" + "=" * 80) print("TEST SUMMARY: Approval Workflow (Inquiry)") print("=" * 80) print(f"✓ Action created: {action_ref}") print(f"✓ Execution created: {execution_id}") print(f"✓ Inquiry created: {inquiry_id}") print(f"✓ Inquiry status: pending → {inquiry_after['status']}") print(f"✓ Response submitted: {response_data}") print(f"✓ Response recorded in inquiry") print("\n✅ TEST PASSED: Inquiry workflow works correctly!") print("=" * 80 + "\n") def test_inquiry_rejection(client: AttuneClient, test_pack): """ Test inquiry rejection flow. """ print("\n" + "=" * 80) print("TEST: Inquiry Rejection") print("=" * 80) pack_ref = test_pack["ref"] # ======================================================================== # STEP 1: Create action and execution # ======================================================================== print("\n[STEP 1] Creating action and execution...") action = client.create_action( pack_ref=pack_ref, data={ "name": f"reject_action_{unique_ref()}", "description": "Action that might be rejected", "runner_type": "python3", "entry_point": "action.py", "enabled": True, "parameters": {}, }, ) action_ref = action["ref"] execution = client.create_execution(action_ref=action_ref, parameters={}) execution_id = execution["id"] print(f"✓ Execution created: ID={execution_id}") time.sleep(2) # ======================================================================== # STEP 2: Create inquiry # ======================================================================== print("\n[STEP 2] Creating inquiry...") inquiry = client.create_inquiry( data={ "execution_id": execution_id, "schema": { "type": "object", "properties": { "approved": {"type": "boolean"}, "reason": {"type": "string"}, }, "required": ["approved"], }, "ttl": 300, } ) inquiry_id = inquiry["id"] print(f"✓ Inquiry created: ID={inquiry_id}") # ======================================================================== # STEP 3: Submit rejection # ======================================================================== print("\n[STEP 3] Submitting rejection...") rejection_response = {"approved": False, "reason": "Security concerns"} client.respond_to_inquiry(inquiry_id=inquiry_id, response=rejection_response) print("✓ Rejection submitted") print(f" Response: {rejection_response}") # ======================================================================== # STEP 4: Verify inquiry updated # ======================================================================== print("\n[STEP 4] Verifying inquiry status...") inquiry_after = client.get_inquiry(inquiry_id) assert inquiry_after["status"] in ["responded", "completed"], ( f"❌ Unexpected inquiry status: {inquiry_after['status']}" ) assert inquiry_after.get("response", {}).get("approved") is False, ( "❌ Response should indicate rejection" ) print(f"✓ Inquiry status: {inquiry_after['status']}") print(f"✓ Rejection recorded: approved={inquiry_after['response']['approved']}") # ======================================================================== # FINAL SUMMARY # ======================================================================== print("\n" + "=" * 80) print("TEST SUMMARY: Inquiry Rejection") print("=" * 80) print(f"✓ Inquiry created: {inquiry_id}") print(f"✓ Rejection submitted: approved=False") print(f"✓ Inquiry status updated correctly") print("\n✅ TEST PASSED: Inquiry rejection works correctly!") print("=" * 80 + "\n") def test_inquiry_multi_field_form(client: AttuneClient, test_pack): """ Test inquiry with multiple form fields. """ print("\n" + "=" * 80) print("TEST: Inquiry Multi-Field Form") print("=" * 80) pack_ref = test_pack["ref"] # ======================================================================== # STEP 1: Create action and execution # ======================================================================== print("\n[STEP 1] Creating action and execution...") action = client.create_action( pack_ref=pack_ref, data={ "name": f"form_action_{unique_ref()}", "description": "Action with multi-field form", "runner_type": "python3", "entry_point": "action.py", "enabled": True, "parameters": {}, }, ) execution = client.create_execution(action_ref=action["ref"], parameters={}) execution_id = execution["id"] print(f"✓ Execution created: ID={execution_id}") time.sleep(2) # ======================================================================== # STEP 2: Create inquiry with complex schema # ======================================================================== print("\n[STEP 2] Creating inquiry with complex schema...") complex_schema = { "type": "object", "properties": { "approved": {"type": "boolean", "description": "Approve or reject"}, "priority": { "type": "string", "enum": ["low", "medium", "high", "critical"], "description": "Priority level", }, "assignee": {"type": "string", "description": "Assignee username"}, "due_date": {"type": "string", "format": "date", "description": "Due date"}, "notes": {"type": "string", "description": "Additional notes"}, }, "required": ["approved", "priority"], } inquiry = client.create_inquiry( data={"execution_id": execution_id, "schema": complex_schema, "ttl": 600} ) inquiry_id = inquiry["id"] print(f"✓ Inquiry created: ID={inquiry_id}") print(f" Schema fields: {list(complex_schema['properties'].keys())}") print(f" Required fields: {complex_schema['required']}") # ======================================================================== # STEP 3: Submit complete response # ======================================================================== print("\n[STEP 3] Submitting complete response...") complete_response = { "approved": True, "priority": "high", "assignee": "john.doe", "due_date": "2024-12-31", "notes": "Requires immediate attention", } client.respond_to_inquiry(inquiry_id=inquiry_id, response=complete_response) print("✓ Response submitted") for key, value in complete_response.items(): print(f" {key}: {value}") # ======================================================================== # STEP 4: Verify response stored correctly # ======================================================================== print("\n[STEP 4] Verifying response stored...") inquiry_after = client.get_inquiry(inquiry_id) stored_response = inquiry_after.get("response", {}) for key, value in complete_response.items(): assert stored_response.get(key) == value, ( f"❌ Field '{key}' mismatch: expected {value}, got {stored_response.get(key)}" ) print("✓ All fields stored correctly") # ======================================================================== # FINAL SUMMARY # ======================================================================== print("\n" + "=" * 80) print("TEST SUMMARY: Multi-Field Form Inquiry") print("=" * 80) print(f"✓ Complex schema with {len(complex_schema['properties'])} fields") print(f"✓ All fields submitted and stored correctly") print(f"✓ Response validation works") print("\n✅ TEST PASSED: Multi-field inquiry forms work correctly!") print("=" * 80 + "\n") def test_inquiry_list_all(client: AttuneClient, test_pack): """ Test listing all inquiries. """ print("\n" + "=" * 80) print("TEST: List All Inquiries") print("=" * 80) pack_ref = test_pack["ref"] # ======================================================================== # STEP 1: Create multiple inquiries # ======================================================================== print("\n[STEP 1] Creating multiple inquiries...") inquiry_ids = [] for i in range(3): action = client.create_action( pack_ref=pack_ref, data={ "name": f"list_action_{i}_{unique_ref()}", "description": f"Test action {i}", "runner_type": "python3", "entry_point": "action.py", "enabled": True, "parameters": {}, }, ) execution = client.create_execution(action_ref=action["ref"], parameters={}) time.sleep(1) inquiry = client.create_inquiry( data={ "execution_id": execution["id"], "schema": { "type": "object", "properties": {"approved": {"type": "boolean"}}, "required": ["approved"], }, "ttl": 300, } ) inquiry_ids.append(inquiry["id"]) print(f" ✓ Created inquiry {i + 1}: ID={inquiry['id']}") print(f"✓ Created {len(inquiry_ids)} inquiries") # ======================================================================== # STEP 2: List all inquiries # ======================================================================== print("\n[STEP 2] Listing all inquiries...") all_inquiries = client.list_inquiries(limit=100) print(f"✓ Retrieved {len(all_inquiries)} total inquiries") # Filter to our test inquiries our_inquiries = [inq for inq in all_inquiries if inq["id"] in inquiry_ids] print(f"✓ Found {len(our_inquiries)} of our test inquiries") # ======================================================================== # STEP 3: Verify all inquiries present # ======================================================================== print("\n[STEP 3] Verifying all inquiries present...") for inquiry_id in inquiry_ids: found = any(inq["id"] == inquiry_id for inq in our_inquiries) assert found, f"❌ Inquiry {inquiry_id} not found in list" print("✓ All test inquiries present in list") # ======================================================================== # FINAL SUMMARY # ======================================================================== print("\n" + "=" * 80) print("TEST SUMMARY: List All Inquiries") print("=" * 80) print(f"✓ Created {len(inquiry_ids)} inquiries") print(f"✓ All inquiries retrieved via list API") print(f"✓ Inquiry listing works correctly") print("\n✅ TEST PASSED: Inquiry listing works correctly!") print("=" * 80 + "\n")