* chore: action params naming * chore: remove comments * chore: replaced MessageTextInput field with StrInput * feat: add google calendar component * feat: replaced loops with hardcoded display-name action-enum mapping to improve performance * chore: format * fix: add type ignore for action_key in getattr call * feat: add google sheets component * fix: format google calendar utils * feat: add google meet Component * chore: minor improvement * chore: format & lint * fix: google meet component * feat: add GitHub component * fix: format * fix: lint * fix: typo * feat: add Slack Component * fix: format * fix: rest bool value to None * chore: disabled slack tools temporarily * fix: add condition to set list variables to None in when action is changed * chore: capitalise display names * fix: update list issues field to MessateTextInput * fix: format/lint in slack component * fix: google calendar logo * fix: revert setting bool field to None * feat: composio-core & composio-core version bump to 0.7.10 * fix: minor bugs * feat: add accepted values to AccessType field in google meet component * feat: add accepted values for entry point access field in Google meet component * fix: Google Calendar display names * feat: replace list with nested list for batch update field in Google sheets * fix: display name in Google sheets * fix: format * fix: titlecase display name in google meet component * feat: set advaced to true for advanced fields * feat: add condition to skip empty list fields in execute_action * chore: improve display names GitHub Component * fix: slack component display names & minor enhancements * feat: update condition to skip empty fields while executing action * feat: fix google calendar field description * feat: update googlemeet component to use new inputs & composio base class * chore: update googlemeet component filename * feat: update github component to use new inputs & composio base class * feat: update google calendar to use new inputs & composio base class * feat: update google sheets component to use new inputs & Composio base class * feat: update slack component to use new inputs & Composio base class * fix: format * chore: cleanup un-used code * chore: format * feat: add missing fields & actions * chore: fix typo * feat: rm other components * feat: improve error message format & revert composio libs bump * chore: revert uv.lock file * update tests * fix: remove duplicate action field in GMAIL_FETCH_EMAILS * fix: remove unused code * fix: add ignore statement * feat: add github Component * feat: delete Gmail component relates files to keep PR clean * add gmail component & test file to keep PR clean * clean pr * clean PR * fix: lint/format * fix: typo in testcase file * fix: remove component name * fix: replace separator in field names * fix: add app_name * fix: minor bugs & improved response format * chore: empty commit * chore: improve input field info * fix: format/lint * fix: Composio GitHub component unit tests --------- Co-authored-by: Edwin Jose <edwin.jose@datastax.com>
226 lines
9.1 KiB
Python
226 lines
9.1 KiB
Python
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from composio import Action
|
|
from langflow.components.composio.github_composio import ComposioGitHubAPIComponent
|
|
from langflow.schema.dataframe import DataFrame
|
|
|
|
from tests.base import DID_NOT_EXIST, ComponentTestBaseWithoutClient
|
|
|
|
from .test_base import MockComposioToolSet
|
|
|
|
|
|
class MockAction:
|
|
GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER = "GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER"
|
|
GITHUB_LIST_BRANCHES = "GITHUB_LIST_BRANCHES"
|
|
GITHUB_LIST_REPOSITORY_ISSUES = "GITHUB_LIST_REPOSITORY_ISSUES"
|
|
|
|
|
|
class TestGitHubComponent(ComponentTestBaseWithoutClient):
|
|
@pytest.fixture(autouse=True)
|
|
def mock_composio_toolset(self):
|
|
with patch("langflow.base.composio.composio_base.ComposioToolSet", MockComposioToolSet):
|
|
yield
|
|
|
|
@pytest.fixture
|
|
def component_class(self):
|
|
return ComposioGitHubAPIComponent
|
|
|
|
@pytest.fixture
|
|
def default_kwargs(self):
|
|
return {
|
|
"api_key": "",
|
|
"entity_id": "default",
|
|
"action": None,
|
|
}
|
|
|
|
@pytest.fixture
|
|
def file_names_mapping(self):
|
|
# Component not yet released, mark all versions as non-existent
|
|
return [
|
|
{"version": "1.0.17", "module": "composio", "file_name": DID_NOT_EXIST},
|
|
{"version": "1.0.18", "module": "composio", "file_name": DID_NOT_EXIST},
|
|
{"version": "1.0.19", "module": "composio", "file_name": DID_NOT_EXIST},
|
|
{"version": "1.1.0", "module": "composio", "file_name": DID_NOT_EXIST},
|
|
{"version": "1.1.1", "module": "composio", "file_name": DID_NOT_EXIST},
|
|
]
|
|
|
|
def test_init(self, component_class, default_kwargs):
|
|
component = component_class(**default_kwargs)
|
|
assert component.display_name == "GitHub"
|
|
assert component.app_name == "github"
|
|
assert "GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER" in component._actions_data
|
|
assert "GITHUB_LIST_BRANCHES" in component._actions_data
|
|
|
|
def test_execute_action_star_a_repo(self, component_class, default_kwargs, monkeypatch):
|
|
# Mock Action enum
|
|
monkeypatch.setattr(
|
|
Action,
|
|
"GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER",
|
|
MockAction.GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER,
|
|
)
|
|
|
|
# Setup component
|
|
component = component_class(**default_kwargs)
|
|
component.api_key = "test_key"
|
|
component.action = [{"name": "Star A Repository"}]
|
|
component.GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER_owner = "langflow-ai"
|
|
component.GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER_repo = "langflow"
|
|
|
|
# For this specific test, customize the _actions_data to not use get_result_field
|
|
component._actions_data = {
|
|
"GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER": {
|
|
"display_name": "Star A Repository",
|
|
"action_fields": [
|
|
"GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER_owner",
|
|
"GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER_repo",
|
|
],
|
|
},
|
|
}
|
|
|
|
# Execute action
|
|
result = component.execute_action()
|
|
assert result == {"result": "mocked response"}
|
|
|
|
def test_execute_action_list_branches(self, component_class, default_kwargs, monkeypatch):
|
|
# Mock Action enum
|
|
monkeypatch.setattr(Action, "GITHUB_LIST_BRANCHES", MockAction.GITHUB_LIST_BRANCHES)
|
|
|
|
# Setup component
|
|
component = component_class(**default_kwargs)
|
|
component.api_key = "test_key"
|
|
component.action = [{"name": "List Branches"}]
|
|
component.GITHUB_LIST_BRANCHES_owner = "langflow-ai"
|
|
component.GITHUB_LIST_BRANCHES_repo = "langflow"
|
|
|
|
# For this specific test, customize the _actions_data to not use get_result_field
|
|
component._actions_data = {
|
|
"GITHUB_LIST_BRANCHES": {
|
|
"display_name": "List Branches",
|
|
"action_fields": [
|
|
"GITHUB_LIST_BRANCHES_owner",
|
|
"GITHUB_LIST_BRANCHES_repo",
|
|
"GITHUB_LIST_BRANCHES_protected",
|
|
"GITHUB_LIST_BRANCHES_per_page",
|
|
"GITHUB_LIST_BRANCHES_page",
|
|
],
|
|
},
|
|
}
|
|
|
|
# Execute action
|
|
result = component.execute_action()
|
|
assert result == {"result": "mocked response"}
|
|
|
|
def test_execute_action_list_repo_issues(self, component_class, default_kwargs, monkeypatch):
|
|
# Mock Action enum
|
|
monkeypatch.setattr(Action, "GITHUB_LIST_REPOSITORY_ISSUES", MockAction.GITHUB_LIST_REPOSITORY_ISSUES)
|
|
|
|
# Setup component
|
|
component = component_class(**default_kwargs)
|
|
component.api_key = "test_key"
|
|
component.action = [{"name": "List Repository Issues"}]
|
|
component.GITHUB_LIST_REPOSITORY_ISSUES_owner = "langflow-ai"
|
|
component.GITHUB_LIST_REPOSITORY_ISSUES_repo = "langflow"
|
|
|
|
# For this specific test, customize the _actions_data to not use get_result_field
|
|
component._actions_data = {
|
|
"GITHUB_LIST_REPOSITORY_ISSUES": {
|
|
"display_name": "List Repository Issues",
|
|
"action_fields": [
|
|
"GITHUB_LIST_REPOSITORY_ISSUES_owner",
|
|
"GITHUB_LIST_REPOSITORY_ISSUES_repo",
|
|
"GITHUB_LIST_REPOSITORY_ISSUES_milestone",
|
|
"GITHUB_LIST_REPOSITORY_ISSUES_state",
|
|
"GITHUB_LIST_REPOSITORY_ISSUES_assignee",
|
|
"GITHUB_LIST_REPOSITORY_ISSUES_creator",
|
|
"GITHUB_LIST_REPOSITORY_ISSUES_mentioned",
|
|
"GITHUB_LIST_REPOSITORY_ISSUES_labels",
|
|
"GITHUB_LIST_REPOSITORY_ISSUES_sort",
|
|
"GITHUB_LIST_REPOSITORY_ISSUES_direction",
|
|
"GITHUB_LIST_REPOSITORY_ISSUES_since",
|
|
"GITHUB_LIST_REPOSITORY_ISSUES_per_page",
|
|
"GITHUB_LIST_REPOSITORY_ISSUES_page",
|
|
],
|
|
},
|
|
}
|
|
|
|
# Execute action
|
|
result = component.execute_action()
|
|
assert result == {"result": "mocked response"}
|
|
|
|
def test_execute_action_invalid_action(self, component_class, default_kwargs):
|
|
# Setup component
|
|
component = component_class(**default_kwargs)
|
|
component.api_key = "test_key"
|
|
component.action = [{"name": "Invalid Action"}]
|
|
|
|
# Execute action should raise ValueError
|
|
with pytest.raises(ValueError, match="Invalid action: Invalid Action"):
|
|
component.execute_action()
|
|
|
|
def test_as_dataframe(self, component_class, default_kwargs, monkeypatch):
|
|
# Mock Action enum
|
|
monkeypatch.setattr(Action, "GITHUB_LIST_REPOSITORY_ISSUES", MockAction.GITHUB_LIST_REPOSITORY_ISSUES)
|
|
|
|
# Setup component
|
|
component = component_class(**default_kwargs)
|
|
component.api_key = "test_key"
|
|
component.action = [{"name": "List Repository Issues"}]
|
|
component.max_results = 10
|
|
|
|
# Create mock email data that would be returned by execute_action
|
|
mock_issues = [
|
|
{
|
|
"url": "url1",
|
|
"repository_url": "repository_url1",
|
|
"id": "id1",
|
|
"title": "test issue",
|
|
"state": "open",
|
|
},
|
|
{
|
|
"url": "url2",
|
|
"repository_url": "repository_url2",
|
|
"id": "id2",
|
|
"title": "test issue",
|
|
"state": "open",
|
|
},
|
|
]
|
|
|
|
# Mock the execute_action method to return our mock data
|
|
with patch.object(component, "execute_action", return_value=mock_issues):
|
|
# Test as_dataframe method
|
|
result = component.as_dataframe()
|
|
|
|
# Verify the result is a DataFrame
|
|
assert isinstance(result, DataFrame)
|
|
|
|
# Verify the DataFrame is not empty
|
|
assert not result.empty
|
|
|
|
# Check for expected content in the DataFrame string representation
|
|
data_str = str(result)
|
|
assert "test issue" in data_str
|
|
|
|
def test_update_build_config(self, component_class, default_kwargs):
|
|
# Test that the GitHub component properly inherits and uses the base component's
|
|
# update_build_config method
|
|
component = component_class(**default_kwargs)
|
|
build_config = {
|
|
"auth_link": {"value": "", "auth_tooltip": ""},
|
|
"action": {
|
|
"options": [],
|
|
"helper_text": "",
|
|
"helper_text_metadata": {},
|
|
},
|
|
}
|
|
|
|
# Test with empty API key
|
|
result = component.update_build_config(build_config, "", "api_key")
|
|
assert result["auth_link"]["value"] == ""
|
|
assert "Please provide a valid Composio API Key" in result["auth_link"]["auth_tooltip"]
|
|
assert result["action"]["options"] == []
|
|
|
|
# Test with valid API key
|
|
component.api_key = "test_key"
|
|
result = component.update_build_config(build_config, "test_key", "api_key")
|
|
assert len(result["action"]["options"]) > 0 # Should have GitHub actions
|