diff --git a/api/tests/unit_tests/core/app/apps/test_workflow_app_generator.py b/api/tests/unit_tests/core/app/apps/test_workflow_app_generator.py index 44c34a01421..de5d18a8be6 100644 --- a/api/tests/unit_tests/core/app/apps/test_workflow_app_generator.py +++ b/api/tests/unit_tests/core/app/apps/test_workflow_app_generator.py @@ -25,6 +25,45 @@ def test_should_prepare_user_inputs_keeps_validation_when_flag_false(): assert WorkflowAppGenerator()._should_prepare_user_inputs(args) +def test_ensure_snippet_start_node_in_worker_returns_standard_workflow_without_lookup(): + session = MagicMock() + workflow = SimpleNamespace(kind_or_standard="standard") + + result = WorkflowAppGenerator._ensure_snippet_start_node_in_worker(session=session, workflow=workflow) + + assert result is workflow + session.scalar.assert_not_called() + + +def test_ensure_snippet_start_node_in_worker_returns_snippet_workflow_when_snippet_missing(): + session = MagicMock() + session.scalar.return_value = None + workflow = SimpleNamespace(kind_or_standard="snippet", app_id="snippet-1", tenant_id="tenant-1") + + result = WorkflowAppGenerator._ensure_snippet_start_node_in_worker(session=session, workflow=workflow) + + assert result is workflow + session.scalar.assert_called_once() + + +def test_ensure_snippet_start_node_in_worker_applies_snippet_start_injection(mocker: MockerFixture): + session = MagicMock() + snippet = SimpleNamespace(id="snippet-1", tenant_id="tenant-1") + session.scalar.return_value = snippet + workflow = SimpleNamespace(kind_or_standard="snippet", app_id="snippet-1", tenant_id="tenant-1") + updated_workflow = SimpleNamespace(name="updated-workflow") + ensure_start_node = mocker.patch( + "services.snippet_generate_service.SnippetGenerateService.ensure_start_node_for_worker", + return_value=updated_workflow, + ) + + result = WorkflowAppGenerator._ensure_snippet_start_node_in_worker(session=session, workflow=workflow) + + assert result is updated_workflow + session.scalar.assert_called_once() + ensure_start_node.assert_called_once_with(workflow, snippet) + + def test_generate_includes_parent_trace_context_in_extras(monkeypatch): generator = WorkflowAppGenerator() diff --git a/api/tests/unit_tests/services/test_tag_service.py b/api/tests/unit_tests/services/test_tag_service.py new file mode 100644 index 00000000000..771924ea363 --- /dev/null +++ b/api/tests/unit_tests/services/test_tag_service.py @@ -0,0 +1,73 @@ +from types import SimpleNamespace + +import pytest +from werkzeug.exceptions import NotFound + +from models.enums import TagType +from services.tag_service import TagBindingCreatePayload, TagBindingDeletePayload, TagService + + +@pytest.fixture +def current_user(mocker): + user = SimpleNamespace(id="user-1", current_tenant_id="tenant-1") + mocker.patch("services.tag_service.current_user", user) + return user + + +@pytest.fixture +def db_session(mocker): + mock_db = mocker.patch("services.tag_service.db") + return mock_db.session + + +def test_save_tag_binding_only_creates_bindings_for_valid_snippet_tags(mocker, current_user, db_session): + mocker.patch("services.tag_service.TagService.check_target_exists") + db_session.scalars.return_value.all.return_value = ["tag-1"] + db_session.scalar.return_value = None + + TagService.save_tag_binding( + TagBindingCreatePayload( + tag_ids=["tag-1", "tag-from-other-tenant"], + target_id="snippet-1", + type=TagType.SNIPPET, + ) + ) + + db_session.add.assert_called_once() + tag_binding = db_session.add.call_args.args[0] + assert tag_binding.tag_id == "tag-1" + assert tag_binding.target_id == "snippet-1" + assert tag_binding.tenant_id == current_user.current_tenant_id + assert tag_binding.created_by == current_user.id + db_session.commit.assert_called_once() + + +def test_delete_tag_binding_limits_deletion_to_valid_snippet_tags(mocker, current_user, db_session): + mocker.patch("services.tag_service.TagService.check_target_exists") + db_session.execute.return_value = SimpleNamespace(rowcount=1) + + TagService.delete_tag_binding( + TagBindingDeletePayload( + tag_ids=["tag-1", "tag-from-other-tenant"], + target_id="snippet-1", + type=TagType.SNIPPET, + ) + ) + + db_session.execute.assert_called_once() + db_session.commit.assert_called_once() + + +def test_check_target_exists_accepts_existing_snippet(current_user, db_session): + db_session.scalar.return_value = SimpleNamespace(id="snippet-1") + + TagService.check_target_exists("snippet", "snippet-1") + + db_session.scalar.assert_called_once() + + +def test_check_target_exists_raises_when_snippet_missing(current_user, db_session): + db_session.scalar.return_value = None + + with pytest.raises(NotFound, match="Snippet not found"): + TagService.check_target_exists("snippet", "missing-snippet") diff --git a/api/tests/unit_tests/services/workflow/test_workflow_draft_variable_service.py b/api/tests/unit_tests/services/workflow/test_workflow_draft_variable_service.py index b5b9f0bd976..dc93d645297 100644 --- a/api/tests/unit_tests/services/workflow/test_workflow_draft_variable_service.py +++ b/api/tests/unit_tests/services/workflow/test_workflow_draft_variable_service.py @@ -343,6 +343,36 @@ class TestWorkflowDraftVariableService: rag_pipeline_variables=[], ) + def test_list_variables_without_values_excludes_node_ids(self, mock_session): + service = WorkflowDraftVariableService(mock_session) + variable = WorkflowDraftVariable.new_node_variable( + app_id="app-1", + node_id="node-1", + name="output", + value=StringSegment(value="value"), + node_execution_id="execution-1", + ) + mock_session.scalar.return_value = 1 + mock_session.scalars.return_value = [variable] + + result = service.list_variables_without_values( + app_id="app-1", + page=1, + limit=20, + user_id="user-1", + exclude_node_ids={SYSTEM_VARIABLE_NODE_ID, CONVERSATION_VARIABLE_NODE_ID}, + ) + + assert result.total == 1 + assert result.variables == [variable] + + stmt = mock_session.scalars.call_args.args[0] + compiled = stmt.compile() + excluded_node_ids = next( + value for value in compiled.params.values() if isinstance(value, (list, tuple)) + ) + assert set(excluded_node_ids) == {SYSTEM_VARIABLE_NODE_ID, CONVERSATION_VARIABLE_NODE_ID} + def test_reset_conversation_variable(self, mock_session): """Test resetting a conversation variable""" service = WorkflowDraftVariableService(mock_session)