| import json | |||||
| import uuid | |||||
| from datetime import UTC, datetime, timedelta | |||||
| from unittest.mock import patch | |||||
| import pytest | |||||
| from faker import Faker | |||||
| from models.enums import CreatorUserRole | |||||
| from models.model import ( | |||||
| Message, | |||||
| ) | |||||
| from models.workflow import WorkflowRun | |||||
| from services.account_service import AccountService, TenantService | |||||
| from services.app_service import AppService | |||||
| from services.workflow_run_service import WorkflowRunService | |||||
| class TestWorkflowRunService: | |||||
| """Integration tests for WorkflowRunService using testcontainers.""" | |||||
| @pytest.fixture | |||||
| def mock_external_service_dependencies(self): | |||||
| """Mock setup for external service dependencies.""" | |||||
| with ( | |||||
| patch("services.app_service.FeatureService") as mock_feature_service, | |||||
| patch("services.app_service.EnterpriseService") as mock_enterprise_service, | |||||
| patch("services.app_service.ModelManager") as mock_model_manager, | |||||
| patch("services.account_service.FeatureService") as mock_account_feature_service, | |||||
| ): | |||||
| # Setup default mock returns for app service | |||||
| mock_feature_service.get_system_features.return_value.webapp_auth.enabled = False | |||||
| mock_enterprise_service.WebAppAuth.update_app_access_mode.return_value = None | |||||
| mock_enterprise_service.WebAppAuth.cleanup_webapp.return_value = None | |||||
| # Setup default mock returns for account service | |||||
| mock_account_feature_service.get_system_features.return_value.is_allow_register = True | |||||
| # Mock ModelManager for model configuration | |||||
| mock_model_instance = mock_model_manager.return_value | |||||
| mock_model_instance.get_default_model_instance.return_value = None | |||||
| mock_model_instance.get_default_provider_model_name.return_value = ("openai", "gpt-3.5-turbo") | |||||
| yield { | |||||
| "feature_service": mock_feature_service, | |||||
| "enterprise_service": mock_enterprise_service, | |||||
| "model_manager": mock_model_manager, | |||||
| "account_feature_service": mock_account_feature_service, | |||||
| } | |||||
| def _create_test_app_and_account(self, db_session_with_containers, mock_external_service_dependencies): | |||||
| """ | |||||
| Helper method to create a test app and account for testing. | |||||
| Args: | |||||
| db_session_with_containers: Database session from testcontainers infrastructure | |||||
| mock_external_service_dependencies: Mock dependencies | |||||
| Returns: | |||||
| tuple: (app, account) - Created app and account instances | |||||
| """ | |||||
| fake = Faker() | |||||
| # Setup mocks for account creation | |||||
| mock_external_service_dependencies[ | |||||
| "account_feature_service" | |||||
| ].get_system_features.return_value.is_allow_register = True | |||||
| # Create account and tenant | |||||
| account = AccountService.create_account( | |||||
| email=fake.email(), | |||||
| name=fake.name(), | |||||
| interface_language="en-US", | |||||
| password=fake.password(length=12), | |||||
| ) | |||||
| TenantService.create_owner_tenant_if_not_exist(account, name=fake.company()) | |||||
| tenant = account.current_tenant | |||||
| # Create app with realistic data | |||||
| app_args = { | |||||
| "name": fake.company(), | |||||
| "description": fake.text(max_nb_chars=100), | |||||
| "mode": "chat", | |||||
| "icon_type": "emoji", | |||||
| "icon": "🤖", | |||||
| "icon_background": "#FF6B6B", | |||||
| "api_rph": 100, | |||||
| "api_rpm": 10, | |||||
| } | |||||
| app_service = AppService() | |||||
| app = app_service.create_app(tenant.id, app_args, account) | |||||
| return app, account | |||||
| def _create_test_workflow_run( | |||||
| self, db_session_with_containers, app, account, triggered_from="debugging", offset_minutes=0 | |||||
| ): | |||||
| """ | |||||
| Helper method to create a test workflow run for testing. | |||||
| Args: | |||||
| db_session_with_containers: Database session from testcontainers infrastructure | |||||
| app: App instance | |||||
| account: Account instance | |||||
| triggered_from: Trigger source for workflow run | |||||
| Returns: | |||||
| WorkflowRun: Created workflow run instance | |||||
| """ | |||||
| fake = Faker() | |||||
| from extensions.ext_database import db | |||||
| # Create workflow run with offset timestamp | |||||
| base_time = datetime.now(UTC) | |||||
| created_time = base_time - timedelta(minutes=offset_minutes) | |||||
| workflow_run = WorkflowRun( | |||||
| tenant_id=app.tenant_id, | |||||
| app_id=app.id, | |||||
| workflow_id=str(uuid.uuid4()), | |||||
| type="chat", | |||||
| triggered_from=triggered_from, | |||||
| version="1.0.0", | |||||
| graph=json.dumps({"nodes": [], "edges": []}), | |||||
| inputs=json.dumps({"input": "test"}), | |||||
| status="succeeded", | |||||
| outputs=json.dumps({"output": "test result"}), | |||||
| elapsed_time=1.5, | |||||
| total_tokens=100, | |||||
| total_steps=3, | |||||
| created_by_role=CreatorUserRole.ACCOUNT.value, | |||||
| created_by=account.id, | |||||
| created_at=created_time, | |||||
| finished_at=created_time, | |||||
| ) | |||||
| db.session.add(workflow_run) | |||||
| db.session.commit() | |||||
| return workflow_run | |||||
| def _create_test_message(self, db_session_with_containers, app, account, workflow_run): | |||||
| """ | |||||
| Helper method to create a test message for testing. | |||||
| Args: | |||||
| db_session_with_containers: Database session from testcontainers infrastructure | |||||
| app: App instance | |||||
| account: Account instance | |||||
| workflow_run: WorkflowRun instance | |||||
| Returns: | |||||
| Message: Created message instance | |||||
| """ | |||||
| fake = Faker() | |||||
| from extensions.ext_database import db | |||||
| # Create conversation first (required for message) | |||||
| from models.model import Conversation | |||||
| conversation = Conversation( | |||||
| app_id=app.id, | |||||
| name=fake.sentence(), | |||||
| inputs={}, | |||||
| status="normal", | |||||
| mode="chat", | |||||
| from_source=CreatorUserRole.ACCOUNT.value, | |||||
| from_account_id=account.id, | |||||
| ) | |||||
| db.session.add(conversation) | |||||
| db.session.commit() | |||||
| # Create message | |||||
| message = Message() | |||||
| message.app_id = app.id | |||||
| message.conversation_id = conversation.id | |||||
| message.query = fake.text(max_nb_chars=100) | |||||
| message.message = {"type": "text", "content": fake.text(max_nb_chars=100)} | |||||
| message.answer = fake.text(max_nb_chars=200) | |||||
| message.message_tokens = 50 | |||||
| message.answer_tokens = 100 | |||||
| message.message_unit_price = 0.001 | |||||
| message.answer_unit_price = 0.002 | |||||
| message.message_price_unit = 0.001 | |||||
| message.answer_price_unit = 0.001 | |||||
| message.currency = "USD" | |||||
| message.status = "normal" | |||||
| message.from_source = CreatorUserRole.ACCOUNT.value | |||||
| message.from_account_id = account.id | |||||
| message.workflow_run_id = workflow_run.id | |||||
| message.inputs = {"input": "test input"} | |||||
| db.session.add(message) | |||||
| db.session.commit() | |||||
| return message | |||||
| def test_get_paginate_workflow_runs_success(self, db_session_with_containers, mock_external_service_dependencies): | |||||
| """ | |||||
| Test successful pagination of workflow runs with debugging trigger. | |||||
| This test verifies: | |||||
| - Proper pagination of workflow runs | |||||
| - Correct filtering by triggered_from | |||||
| - Proper limit and last_id handling | |||||
| - Repository method calls | |||||
| """ | |||||
| # Arrange: Create test data | |||||
| fake = Faker() | |||||
| app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies) | |||||
| # Create multiple workflow runs | |||||
| workflow_runs = [] | |||||
| for i in range(5): | |||||
| workflow_run = self._create_test_workflow_run(db_session_with_containers, app, account, "debugging") | |||||
| workflow_runs.append(workflow_run) | |||||
| # Act: Execute the method under test | |||||
| workflow_run_service = WorkflowRunService() | |||||
| args = {"limit": 3, "last_id": None} | |||||
| result = workflow_run_service.get_paginate_workflow_runs(app, args) | |||||
| # Assert: Verify the expected outcomes | |||||
| assert result is not None | |||||
| assert hasattr(result, "data") | |||||
| assert len(result.data) == 3 # Should return 3 items due to limit | |||||
| # Verify pagination properties | |||||
| assert hasattr(result, "has_more") | |||||
| assert hasattr(result, "limit") | |||||
| # Verify all returned items are debugging runs | |||||
| for workflow_run in result.data: | |||||
| assert workflow_run.triggered_from == "debugging" | |||||
| assert workflow_run.app_id == app.id | |||||
| assert workflow_run.tenant_id == app.tenant_id | |||||
| def test_get_paginate_workflow_runs_with_last_id( | |||||
| self, db_session_with_containers, mock_external_service_dependencies | |||||
| ): | |||||
| """ | |||||
| Test pagination of workflow runs with last_id parameter. | |||||
| This test verifies: | |||||
| - Proper pagination with last_id parameter | |||||
| - Correct handling of pagination state | |||||
| - Repository method calls with proper parameters | |||||
| """ | |||||
| # Arrange: Create test data | |||||
| fake = Faker() | |||||
| app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies) | |||||
| # Create multiple workflow runs with different timestamps | |||||
| workflow_runs = [] | |||||
| for i in range(5): | |||||
| workflow_run = self._create_test_workflow_run( | |||||
| db_session_with_containers, app, account, "debugging", offset_minutes=i | |||||
| ) | |||||
| workflow_runs.append(workflow_run) | |||||
| # Act: Execute the method under test with last_id | |||||
| workflow_run_service = WorkflowRunService() | |||||
| args = {"limit": 2, "last_id": workflow_runs[1].id} | |||||
| result = workflow_run_service.get_paginate_workflow_runs(app, args) | |||||
| # Assert: Verify the expected outcomes | |||||
| assert result is not None | |||||
| assert hasattr(result, "data") | |||||
| assert len(result.data) == 2 # Should return 2 items due to limit | |||||
| # Verify pagination properties | |||||
| assert hasattr(result, "has_more") | |||||
| assert hasattr(result, "limit") | |||||
| # Verify all returned items are debugging runs | |||||
| for workflow_run in result.data: | |||||
| assert workflow_run.triggered_from == "debugging" | |||||
| assert workflow_run.app_id == app.id | |||||
| assert workflow_run.tenant_id == app.tenant_id | |||||
| def test_get_paginate_workflow_runs_default_limit( | |||||
| self, db_session_with_containers, mock_external_service_dependencies | |||||
| ): | |||||
| """ | |||||
| Test pagination of workflow runs with default limit. | |||||
| This test verifies: | |||||
| - Default limit of 20 when not specified | |||||
| - Proper handling of missing limit parameter | |||||
| - Repository method calls with default values | |||||
| """ | |||||
| # Arrange: Create test data | |||||
| fake = Faker() | |||||
| app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies) | |||||
| # Create workflow runs | |||||
| workflow_run = self._create_test_workflow_run(db_session_with_containers, app, account, "debugging") | |||||
| # Act: Execute the method under test without limit | |||||
| workflow_run_service = WorkflowRunService() | |||||
| args = {} # No limit specified | |||||
| result = workflow_run_service.get_paginate_workflow_runs(app, args) | |||||
| # Assert: Verify the expected outcomes | |||||
| assert result is not None | |||||
| assert hasattr(result, "data") | |||||
| # Verify pagination properties | |||||
| assert hasattr(result, "has_more") | |||||
| assert hasattr(result, "limit") | |||||
| # Verify the returned workflow run | |||||
| if result.data: | |||||
| workflow_run_result = result.data[0] | |||||
| assert workflow_run_result.triggered_from == "debugging" | |||||
| assert workflow_run_result.app_id == app.id | |||||
| assert workflow_run_result.tenant_id == app.tenant_id | |||||
| def test_get_paginate_advanced_chat_workflow_runs_success( | |||||
| self, db_session_with_containers, mock_external_service_dependencies | |||||
| ): | |||||
| """ | |||||
| Test successful pagination of advanced chat workflow runs with message information. | |||||
| This test verifies: | |||||
| - Proper pagination of advanced chat workflow runs | |||||
| - Correct filtering by triggered_from | |||||
| - Message information enrichment | |||||
| - WorkflowWithMessage wrapper functionality | |||||
| """ | |||||
| # Arrange: Create test data | |||||
| fake = Faker() | |||||
| app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies) | |||||
| # Create workflow runs with messages | |||||
| workflow_runs = [] | |||||
| for i in range(3): | |||||
| workflow_run = self._create_test_workflow_run( | |||||
| db_session_with_containers, app, account, "debugging", offset_minutes=i | |||||
| ) | |||||
| message = self._create_test_message(db_session_with_containers, app, account, workflow_run) | |||||
| workflow_runs.append(workflow_run) | |||||
| # Act: Execute the method under test | |||||
| workflow_run_service = WorkflowRunService() | |||||
| args = {"limit": 2, "last_id": None} | |||||
| result = workflow_run_service.get_paginate_advanced_chat_workflow_runs(app, args) | |||||
| # Assert: Verify the expected outcomes | |||||
| assert result is not None | |||||
| assert hasattr(result, "data") | |||||
| assert len(result.data) == 2 # Should return 2 items due to limit | |||||
| # Verify pagination properties | |||||
| assert hasattr(result, "has_more") | |||||
| assert hasattr(result, "limit") | |||||
| # Verify all returned items have message information | |||||
| for workflow_run in result.data: | |||||
| assert hasattr(workflow_run, "message_id") | |||||
| assert hasattr(workflow_run, "conversation_id") | |||||
| assert workflow_run.app_id == app.id | |||||
| assert workflow_run.tenant_id == app.tenant_id | |||||
| def test_get_workflow_run_success(self, db_session_with_containers, mock_external_service_dependencies): | |||||
| """ | |||||
| Test successful retrieval of workflow run by ID. | |||||
| This test verifies: | |||||
| - Proper workflow run retrieval by ID | |||||
| - Correct tenant and app isolation | |||||
| - Repository method calls with proper parameters | |||||
| """ | |||||
| # Arrange: Create test data | |||||
| fake = Faker() | |||||
| app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies) | |||||
| # Create workflow run | |||||
| workflow_run = self._create_test_workflow_run(db_session_with_containers, app, account, "debugging") | |||||
| # Act: Execute the method under test | |||||
| workflow_run_service = WorkflowRunService() | |||||
| result = workflow_run_service.get_workflow_run(app, workflow_run.id) | |||||
| # Assert: Verify the expected outcomes | |||||
| assert result is not None | |||||
| assert result.id == workflow_run.id | |||||
| assert result.tenant_id == app.tenant_id | |||||
| assert result.app_id == app.id | |||||
| assert result.triggered_from == "debugging" | |||||
| assert result.status == "succeeded" | |||||
| assert result.type == "chat" | |||||
| assert result.version == "1.0.0" | |||||
| def test_get_workflow_run_not_found(self, db_session_with_containers, mock_external_service_dependencies): | |||||
| """ | |||||
| Test workflow run retrieval when run ID does not exist. | |||||
| This test verifies: | |||||
| - Proper handling of non-existent workflow run IDs | |||||
| - Repository method calls with proper parameters | |||||
| - Return value for missing records | |||||
| """ | |||||
| # Arrange: Create test data | |||||
| fake = Faker() | |||||
| app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies) | |||||
| # Use a non-existent UUID | |||||
| non_existent_id = str(uuid.uuid4()) | |||||
| # Act: Execute the method under test | |||||
| workflow_run_service = WorkflowRunService() | |||||
| result = workflow_run_service.get_workflow_run(app, non_existent_id) | |||||
| # Assert: Verify the expected outcomes | |||||
| assert result is None | |||||
| def test_get_workflow_run_node_executions_success( | |||||
| self, db_session_with_containers, mock_external_service_dependencies | |||||
| ): | |||||
| """ | |||||
| Test successful retrieval of workflow run node executions. | |||||
| This test verifies: | |||||
| - Proper node execution retrieval for workflow run | |||||
| - Correct tenant and app isolation | |||||
| - Repository method calls with proper parameters | |||||
| - Context setup for plugin tool providers | |||||
| """ | |||||
| # Arrange: Create test data | |||||
| fake = Faker() | |||||
| app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies) | |||||
| # Create workflow run | |||||
| workflow_run = self._create_test_workflow_run(db_session_with_containers, app, account, "debugging") | |||||
| # Create node executions | |||||
| from extensions.ext_database import db | |||||
| from models.workflow import WorkflowNodeExecutionModel | |||||
| node_executions = [] | |||||
| for i in range(3): | |||||
| node_execution = WorkflowNodeExecutionModel( | |||||
| tenant_id=app.tenant_id, | |||||
| app_id=app.id, | |||||
| workflow_id=workflow_run.workflow_id, | |||||
| triggered_from="workflow-run", | |||||
| workflow_run_id=workflow_run.id, | |||||
| index=i, | |||||
| node_id=f"node_{i}", | |||||
| node_type="llm" if i == 0 else "tool", | |||||
| title=f"Node {i}", | |||||
| inputs=json.dumps({"input": f"test_input_{i}"}), | |||||
| process_data=json.dumps({"process": f"test_process_{i}"}), | |||||
| status="succeeded", | |||||
| elapsed_time=0.5, | |||||
| execution_metadata=json.dumps({"tokens": 50}), | |||||
| created_by_role=CreatorUserRole.ACCOUNT.value, | |||||
| created_by=account.id, | |||||
| created_at=datetime.now(UTC), | |||||
| ) | |||||
| db.session.add(node_execution) | |||||
| node_executions.append(node_execution) | |||||
| db.session.commit() | |||||
| # Act: Execute the method under test | |||||
| workflow_run_service = WorkflowRunService() | |||||
| result = workflow_run_service.get_workflow_run_node_executions(app, workflow_run.id, account) | |||||
| # Assert: Verify the expected outcomes | |||||
| assert result is not None | |||||
| assert len(result) == 3 | |||||
| # Verify node execution properties | |||||
| for node_execution in result: | |||||
| assert node_execution.tenant_id == app.tenant_id | |||||
| assert node_execution.app_id == app.id | |||||
| assert node_execution.workflow_run_id == workflow_run.id | |||||
| assert node_execution.index in [0, 1, 2] # Check that index is one of the expected values | |||||
| assert node_execution.node_id.startswith("node_") # Check that node_id starts with "node_" | |||||
| assert node_execution.status == "succeeded" | |||||
| def test_get_workflow_run_node_executions_empty( | |||||
| self, db_session_with_containers, mock_external_service_dependencies | |||||
| ): | |||||
| """ | |||||
| Test getting node executions for a workflow run with no executions. | |||||
| This test verifies: | |||||
| - Empty result when no node executions exist | |||||
| - Proper handling of empty data | |||||
| - No errors when querying non-existent executions | |||||
| """ | |||||
| # Arrange: Setup test data | |||||
| account_service = AccountService() | |||||
| tenant_service = TenantService() | |||||
| app_service = AppService() | |||||
| workflow_run_service = WorkflowRunService() | |||||
| # Create account and tenant | |||||
| account = account_service.create_account( | |||||
| email="test@example.com", | |||||
| name="Test User", | |||||
| password="password123", | |||||
| interface_language="en-US", | |||||
| ) | |||||
| TenantService.create_owner_tenant_if_not_exist(account, name="test_tenant") | |||||
| tenant = account.current_tenant | |||||
| # Create app | |||||
| app_args = { | |||||
| "name": "Test App", | |||||
| "mode": "chat", | |||||
| "icon_type": "emoji", | |||||
| "icon": "🚀", | |||||
| "icon_background": "#4ECDC4", | |||||
| } | |||||
| app = app_service.create_app(tenant.id, app_args, account) | |||||
| # Create workflow run without node executions | |||||
| workflow_run = self._create_test_workflow_run(db_session_with_containers, app, account, "debugging") | |||||
| # Act: Get node executions | |||||
| result = workflow_run_service.get_workflow_run_node_executions( | |||||
| app_model=app, | |||||
| run_id=workflow_run.id, | |||||
| user=account, | |||||
| ) | |||||
| # Assert: Verify empty result | |||||
| assert result is not None | |||||
| assert len(result) == 0 | |||||
| def test_get_workflow_run_node_executions_invalid_workflow_run_id( | |||||
| self, db_session_with_containers, mock_external_service_dependencies | |||||
| ): | |||||
| """ | |||||
| Test getting node executions with invalid workflow run ID. | |||||
| This test verifies: | |||||
| - Proper handling of invalid workflow run ID | |||||
| - Empty result when workflow run doesn't exist | |||||
| - No errors when querying with invalid ID | |||||
| """ | |||||
| # Arrange: Setup test data | |||||
| account_service = AccountService() | |||||
| tenant_service = TenantService() | |||||
| app_service = AppService() | |||||
| workflow_run_service = WorkflowRunService() | |||||
| # Create account and tenant | |||||
| account = account_service.create_account( | |||||
| email="test@example.com", | |||||
| name="Test User", | |||||
| password="password123", | |||||
| interface_language="en-US", | |||||
| ) | |||||
| TenantService.create_owner_tenant_if_not_exist(account, name="test_tenant") | |||||
| tenant = account.current_tenant | |||||
| # Create app | |||||
| app_args = { | |||||
| "name": "Test App", | |||||
| "mode": "chat", | |||||
| "icon_type": "emoji", | |||||
| "icon": "🚀", | |||||
| "icon_background": "#4ECDC4", | |||||
| } | |||||
| app = app_service.create_app(tenant.id, app_args, account) | |||||
| # Use invalid workflow run ID | |||||
| invalid_workflow_run_id = str(uuid.uuid4()) | |||||
| # Act: Get node executions with invalid ID | |||||
| result = workflow_run_service.get_workflow_run_node_executions( | |||||
| app_model=app, | |||||
| run_id=invalid_workflow_run_id, | |||||
| user=account, | |||||
| ) | |||||
| # Assert: Verify empty result | |||||
| assert result is not None | |||||
| assert len(result) == 0 | |||||
| def test_get_workflow_run_node_executions_database_error( | |||||
| self, db_session_with_containers, mock_external_service_dependencies | |||||
| ): | |||||
| """ | |||||
| Test getting node executions when database encounters an error. | |||||
| This test verifies: | |||||
| - Proper error handling when database operations fail | |||||
| - Graceful degradation in error scenarios | |||||
| - Error propagation to calling code | |||||
| """ | |||||
| # Arrange: Setup test data | |||||
| account_service = AccountService() | |||||
| tenant_service = TenantService() | |||||
| app_service = AppService() | |||||
| workflow_run_service = WorkflowRunService() | |||||
| # Create account and tenant | |||||
| account = account_service.create_account( | |||||
| email="test@example.com", | |||||
| name="Test User", | |||||
| password="password123", | |||||
| interface_language="en-US", | |||||
| ) | |||||
| TenantService.create_owner_tenant_if_not_exist(account, name="test_tenant") | |||||
| tenant = account.current_tenant | |||||
| # Create app | |||||
| app_args = { | |||||
| "name": "Test App", | |||||
| "mode": "chat", | |||||
| "icon_type": "emoji", | |||||
| "icon": "🚀", | |||||
| "icon_background": "#4ECDC4", | |||||
| } | |||||
| app = app_service.create_app(tenant.id, app_args, account) | |||||
| # Create workflow run | |||||
| workflow_run = self._create_test_workflow_run(db_session_with_containers, app, account, "debugging") | |||||
| # Mock database error by closing the session | |||||
| db_session_with_containers.close() | |||||
| # Act & Assert: Verify error handling | |||||
| with pytest.raises((Exception, RuntimeError)): | |||||
| workflow_run_service.get_workflow_run_node_executions( | |||||
| app_model=app, | |||||
| run_id=workflow_run.id, | |||||
| user=account, | |||||
| ) | |||||
| def test_get_workflow_run_node_executions_end_user( | |||||
| self, db_session_with_containers, mock_external_service_dependencies | |||||
| ): | |||||
| """ | |||||
| Test node execution retrieval for end user. | |||||
| This test verifies: | |||||
| - Proper handling of end user vs account user | |||||
| - Correct tenant ID extraction for end users | |||||
| - Repository method calls with proper parameters | |||||
| """ | |||||
| # Arrange: Create test data | |||||
| fake = Faker() | |||||
| app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies) | |||||
| # Create workflow run | |||||
| workflow_run = self._create_test_workflow_run(db_session_with_containers, app, account, "debugging") | |||||
| # Create end user | |||||
| from extensions.ext_database import db | |||||
| from models.model import EndUser | |||||
| end_user = EndUser( | |||||
| tenant_id=app.tenant_id, | |||||
| app_id=app.id, | |||||
| type="web_app", | |||||
| is_anonymous=False, | |||||
| session_id=str(uuid.uuid4()), | |||||
| external_user_id=str(uuid.uuid4()), | |||||
| name=fake.name(), | |||||
| ) | |||||
| db.session.add(end_user) | |||||
| db.session.commit() | |||||
| # Create node execution | |||||
| from models.workflow import WorkflowNodeExecutionModel | |||||
| node_execution = WorkflowNodeExecutionModel( | |||||
| tenant_id=app.tenant_id, | |||||
| app_id=app.id, | |||||
| workflow_id=workflow_run.workflow_id, | |||||
| triggered_from="workflow-run", | |||||
| workflow_run_id=workflow_run.id, | |||||
| index=0, | |||||
| node_id="node_0", | |||||
| node_type="llm", | |||||
| title="Node 0", | |||||
| inputs=json.dumps({"input": "test_input"}), | |||||
| process_data=json.dumps({"process": "test_process"}), | |||||
| status="succeeded", | |||||
| elapsed_time=0.5, | |||||
| execution_metadata=json.dumps({"tokens": 50}), | |||||
| created_by_role=CreatorUserRole.END_USER.value, | |||||
| created_by=end_user.id, | |||||
| created_at=datetime.now(UTC), | |||||
| ) | |||||
| db.session.add(node_execution) | |||||
| db.session.commit() | |||||
| # Act: Execute the method under test | |||||
| workflow_run_service = WorkflowRunService() | |||||
| result = workflow_run_service.get_workflow_run_node_executions(app, workflow_run.id, end_user) | |||||
| # Assert: Verify the expected outcomes | |||||
| assert result is not None | |||||
| assert len(result) == 1 | |||||
| # Verify node execution properties | |||||
| node_exec = result[0] | |||||
| assert node_exec.tenant_id == app.tenant_id | |||||
| assert node_exec.app_id == app.id | |||||
| assert node_exec.workflow_run_id == workflow_run.id | |||||
| assert node_exec.created_by == end_user.id | |||||
| assert node_exec.created_by_role == CreatorUserRole.END_USER.value |