mirror of
https://github.com/langgenius/dify.git
synced 2026-03-23 15:27:53 +08:00
1250 lines
44 KiB
Python
1250 lines
44 KiB
Python
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import json
|
|
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
from pytest_mock import MockerFixture
|
|
|
|
from constants import HIDDEN_VALUE
|
|
from core.plugin.entities.plugin_daemon import CredentialType
|
|
from models.provider_ids import TriggerProviderID
|
|
from services.trigger.trigger_provider_service import TriggerProviderService
|
|
|
|
|
|
def _patch_redis_lock(mocker: MockerFixture) -> None:
|
|
mock_redis = mocker.patch("services.trigger.trigger_provider_service.redis_client")
|
|
mock_redis.lock.return_value = contextlib.nullcontext()
|
|
|
|
|
|
def _mock_get_trigger_provider(mocker: MockerFixture, provider: object | None) -> None:
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.TriggerManager.get_trigger_provider",
|
|
return_value=provider,
|
|
)
|
|
|
|
|
|
def _encrypter_mock(
|
|
*,
|
|
decrypted: dict | None = None,
|
|
encrypted: dict | None = None,
|
|
masked: dict | None = None,
|
|
) -> MagicMock:
|
|
enc = MagicMock()
|
|
enc.decrypt.return_value = decrypted or {}
|
|
enc.encrypt.return_value = encrypted or {}
|
|
enc.mask_credentials.return_value = masked or {}
|
|
enc.mask_plugin_credentials.return_value = masked or {}
|
|
return enc
|
|
|
|
|
|
@pytest.fixture
|
|
def provider_id() -> TriggerProviderID:
|
|
# Arrange
|
|
return TriggerProviderID("langgenius/github/github")
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def mock_db_engine(mocker: MockerFixture) -> SimpleNamespace:
|
|
# Arrange
|
|
mocked_db = SimpleNamespace(engine=object())
|
|
mocker.patch("services.trigger.trigger_provider_service.db", mocked_db)
|
|
return mocked_db
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_session(mocker: MockerFixture) -> MagicMock:
|
|
"""Mocks the database session context manager used by TriggerProviderService."""
|
|
# Arrange
|
|
mock_session_instance = MagicMock()
|
|
mock_session_cm = MagicMock()
|
|
mock_session_cm.__enter__.return_value = mock_session_instance
|
|
mock_session_cm.__exit__.return_value = False
|
|
mocker.patch("services.trigger.trigger_provider_service.Session", return_value=mock_session_cm)
|
|
return mock_session_instance
|
|
|
|
|
|
@pytest.fixture
|
|
def provider_controller() -> MagicMock:
|
|
# Arrange
|
|
controller = MagicMock()
|
|
controller.get_credential_schema_config.return_value = []
|
|
controller.get_properties_schema.return_value = []
|
|
controller.get_oauth_client_schema.return_value = []
|
|
controller.plugin_unique_identifier = "langgenius/github:0.0.1"
|
|
return controller
|
|
|
|
|
|
def test_get_trigger_provider_should_return_api_entity_from_manager(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
) -> None:
|
|
# Arrange
|
|
provider = MagicMock()
|
|
provider.to_api_entity.return_value = {"provider": "ok"}
|
|
_mock_get_trigger_provider(mocker, provider)
|
|
|
|
# Act
|
|
result = TriggerProviderService.get_trigger_provider("tenant-1", provider_id)
|
|
|
|
# Assert
|
|
assert result == {"provider": "ok"}
|
|
|
|
|
|
def test_list_trigger_providers_should_return_api_entities_from_manager(mocker: MockerFixture) -> None:
|
|
# Arrange
|
|
provider_a = MagicMock()
|
|
provider_b = MagicMock()
|
|
provider_a.to_api_entity.return_value = {"id": "a"}
|
|
provider_b.to_api_entity.return_value = {"id": "b"}
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.TriggerManager.list_all_trigger_providers",
|
|
return_value=[provider_a, provider_b],
|
|
)
|
|
|
|
# Act
|
|
result = TriggerProviderService.list_trigger_providers("tenant-1")
|
|
|
|
# Assert
|
|
assert result == [{"id": "a"}, {"id": "b"}]
|
|
|
|
|
|
def test_list_trigger_provider_subscriptions_should_return_empty_list_when_no_subscriptions(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
) -> None:
|
|
# Arrange
|
|
query = MagicMock()
|
|
query.filter_by.return_value.order_by.return_value.all.return_value = []
|
|
mock_session.query.return_value = query
|
|
|
|
# Act
|
|
result = TriggerProviderService.list_trigger_provider_subscriptions("tenant-1", provider_id)
|
|
|
|
# Assert
|
|
assert result == []
|
|
|
|
|
|
def test_list_trigger_provider_subscriptions_should_mask_fields_and_attach_workflow_counts(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
api_sub = SimpleNamespace(
|
|
id="sub-1",
|
|
credentials={"token": "enc"},
|
|
properties={"hook": "enc"},
|
|
parameters={"event": "push"},
|
|
workflows_in_use=0,
|
|
)
|
|
db_sub = SimpleNamespace(to_api_entity=lambda: api_sub)
|
|
usage_row = SimpleNamespace(subscription_id="sub-1", app_count=2)
|
|
|
|
query_subs = MagicMock()
|
|
query_subs.filter_by.return_value.order_by.return_value.all.return_value = [db_sub]
|
|
query_usage = MagicMock()
|
|
query_usage.filter.return_value.group_by.return_value.all.return_value = [usage_row]
|
|
mock_session.query.side_effect = [query_subs, query_usage]
|
|
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
cred_enc = _encrypter_mock(decrypted={"token": "plain"}, masked={"token": "****"})
|
|
prop_enc = _encrypter_mock(decrypted={"hook": "plain"}, masked={"hook": "****"})
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.create_trigger_provider_encrypter_for_subscription",
|
|
return_value=(cred_enc, MagicMock()),
|
|
)
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.create_trigger_provider_encrypter_for_properties",
|
|
return_value=(prop_enc, MagicMock()),
|
|
)
|
|
|
|
# Act
|
|
result = TriggerProviderService.list_trigger_provider_subscriptions("tenant-1", provider_id)
|
|
|
|
# Assert
|
|
assert len(result) == 1
|
|
assert result[0].credentials == {"token": "****"}
|
|
assert result[0].properties == {"hook": "****"}
|
|
assert result[0].workflows_in_use == 2
|
|
|
|
|
|
def test_add_trigger_subscription_should_create_subscription_successfully_for_api_key(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
_patch_redis_lock(mocker)
|
|
query_count = MagicMock()
|
|
query_count.filter_by.return_value.count.return_value = 0
|
|
query_existing = MagicMock()
|
|
query_existing.filter_by.return_value.first.return_value = None
|
|
mock_session.query.side_effect = [query_count, query_existing]
|
|
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
cred_enc = _encrypter_mock(encrypted={"api_key": "enc"})
|
|
prop_enc = _encrypter_mock(encrypted={"project": "enc"})
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.create_provider_encrypter",
|
|
side_effect=[(cred_enc, MagicMock()), (prop_enc, MagicMock())],
|
|
)
|
|
|
|
# Act
|
|
result = TriggerProviderService.add_trigger_subscription(
|
|
tenant_id="tenant-1",
|
|
user_id="user-1",
|
|
name="main",
|
|
provider_id=provider_id,
|
|
endpoint_id="endpoint-1",
|
|
credential_type=CredentialType.API_KEY,
|
|
parameters={"event": "push"},
|
|
properties={"project": "demo"},
|
|
credentials={"api_key": "plain"},
|
|
)
|
|
|
|
# Assert
|
|
assert result["result"] == "success"
|
|
mock_session.add.assert_called_once()
|
|
mock_session.commit.assert_called_once()
|
|
|
|
|
|
def test_add_trigger_subscription_should_store_empty_credentials_for_unauthorized_type(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
_patch_redis_lock(mocker)
|
|
query_count = MagicMock()
|
|
query_count.filter_by.return_value.count.return_value = 0
|
|
query_existing = MagicMock()
|
|
query_existing.filter_by.return_value.first.return_value = None
|
|
mock_session.query.side_effect = [query_count, query_existing]
|
|
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
prop_enc = _encrypter_mock(encrypted={"p": "enc"})
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.create_provider_encrypter",
|
|
return_value=(prop_enc, MagicMock()),
|
|
)
|
|
|
|
# Act
|
|
result = TriggerProviderService.add_trigger_subscription(
|
|
tenant_id="tenant-1",
|
|
user_id="user-1",
|
|
name="main",
|
|
provider_id=provider_id,
|
|
endpoint_id="endpoint-1",
|
|
credential_type=CredentialType.UNAUTHORIZED,
|
|
parameters={},
|
|
properties={"p": "v"},
|
|
credentials={},
|
|
subscription_id="sub-fixed",
|
|
)
|
|
|
|
# Assert
|
|
assert result == {"result": "success", "id": "sub-fixed"}
|
|
|
|
|
|
def test_add_trigger_subscription_should_raise_error_when_provider_limit_reached(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
_patch_redis_lock(mocker)
|
|
query_count = MagicMock()
|
|
query_count.filter_by.return_value.count.return_value = TriggerProviderService.__MAX_TRIGGER_PROVIDER_COUNT__
|
|
mock_session.query.return_value = query_count
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
mock_logger = mocker.patch("services.trigger.trigger_provider_service.logger")
|
|
|
|
# Act + Assert
|
|
with pytest.raises(ValueError, match="Maximum number of providers"):
|
|
TriggerProviderService.add_trigger_subscription(
|
|
tenant_id="tenant-1",
|
|
user_id="user-1",
|
|
name="main",
|
|
provider_id=provider_id,
|
|
endpoint_id="endpoint-1",
|
|
credential_type=CredentialType.API_KEY,
|
|
parameters={},
|
|
properties={},
|
|
credentials={},
|
|
)
|
|
mock_logger.exception.assert_called_once()
|
|
|
|
|
|
def test_add_trigger_subscription_should_raise_error_when_name_exists(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
_patch_redis_lock(mocker)
|
|
query_count = MagicMock()
|
|
query_count.filter_by.return_value.count.return_value = 0
|
|
query_existing = MagicMock()
|
|
query_existing.filter_by.return_value.first.return_value = object()
|
|
mock_session.query.side_effect = [query_count, query_existing]
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
|
|
# Act + Assert
|
|
with pytest.raises(ValueError, match="Credential name 'main' already exists"):
|
|
TriggerProviderService.add_trigger_subscription(
|
|
tenant_id="tenant-1",
|
|
user_id="user-1",
|
|
name="main",
|
|
provider_id=provider_id,
|
|
endpoint_id="endpoint-1",
|
|
credential_type=CredentialType.API_KEY,
|
|
parameters={},
|
|
properties={},
|
|
credentials={},
|
|
)
|
|
|
|
|
|
def test_update_trigger_subscription_should_raise_error_when_subscription_not_found(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
_patch_redis_lock(mocker)
|
|
query_sub = MagicMock()
|
|
query_sub.filter_by.return_value.first.return_value = None
|
|
mock_session.query.return_value = query_sub
|
|
|
|
# Act + Assert
|
|
with pytest.raises(ValueError, match="not found"):
|
|
TriggerProviderService.update_trigger_subscription("tenant-1", "sub-1")
|
|
|
|
|
|
def test_update_trigger_subscription_should_raise_error_when_name_conflicts(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
_patch_redis_lock(mocker)
|
|
subscription = SimpleNamespace(
|
|
id="sub-1",
|
|
name="old",
|
|
provider_id="langgenius/github/github",
|
|
credential_type=CredentialType.API_KEY.value,
|
|
)
|
|
query_sub = MagicMock()
|
|
query_sub.filter_by.return_value.first.return_value = subscription
|
|
query_existing = MagicMock()
|
|
query_existing.filter_by.return_value.first.return_value = object()
|
|
mock_session.query.side_effect = [query_sub, query_existing]
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
|
|
# Act + Assert
|
|
with pytest.raises(ValueError, match="already exists"):
|
|
TriggerProviderService.update_trigger_subscription("tenant-1", "sub-1", name="new-name")
|
|
|
|
|
|
def test_update_trigger_subscription_should_update_fields_and_clear_cache(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
_patch_redis_lock(mocker)
|
|
subscription = SimpleNamespace(
|
|
id="sub-1",
|
|
name="old",
|
|
tenant_id="tenant-1",
|
|
provider_id="langgenius/github/github",
|
|
properties={"project": "enc-old"},
|
|
parameters={"event": "old"},
|
|
credentials={"api_key": "enc-old"},
|
|
credential_type=CredentialType.API_KEY.value,
|
|
credential_expires_at=0,
|
|
expires_at=0,
|
|
)
|
|
query_sub = MagicMock()
|
|
query_sub.filter_by.return_value.first.return_value = subscription
|
|
query_existing = MagicMock()
|
|
query_existing.filter_by.return_value.first.return_value = None
|
|
mock_session.query.side_effect = [query_sub, query_existing]
|
|
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
prop_enc = _encrypter_mock(decrypted={"project": "old-value"}, encrypted={"project": "new-value"})
|
|
cred_enc = _encrypter_mock(encrypted={"api_key": "new-key"})
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.create_provider_encrypter",
|
|
side_effect=[(prop_enc, MagicMock()), (cred_enc, MagicMock())],
|
|
)
|
|
mock_delete_cache = mocker.patch("services.trigger.trigger_provider_service.delete_cache_for_subscription")
|
|
|
|
# Act
|
|
TriggerProviderService.update_trigger_subscription(
|
|
tenant_id="tenant-1",
|
|
subscription_id="sub-1",
|
|
name="new",
|
|
properties={"project": HIDDEN_VALUE, "region": "us"},
|
|
parameters={"event": "new"},
|
|
credentials={"api_key": "plain-key"},
|
|
credential_expires_at=100,
|
|
expires_at=200,
|
|
)
|
|
|
|
# Assert
|
|
assert subscription.name == "new"
|
|
assert subscription.parameters == {"event": "new"}
|
|
assert subscription.credentials == {"api_key": "new-key"}
|
|
assert subscription.credential_expires_at == 100
|
|
assert subscription.expires_at == 200
|
|
mock_session.commit.assert_called_once()
|
|
mock_delete_cache.assert_called_once()
|
|
|
|
|
|
def test_get_subscription_by_id_should_return_none_when_missing(mocker: MockerFixture, mock_session: MagicMock) -> None:
|
|
# Arrange
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = None
|
|
|
|
# Act
|
|
result = TriggerProviderService.get_subscription_by_id("tenant-1", "sub-1")
|
|
|
|
# Assert
|
|
assert result is None
|
|
|
|
|
|
def test_get_subscription_by_id_should_decrypt_credentials_and_properties(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
subscription = SimpleNamespace(
|
|
id="sub-1",
|
|
tenant_id="tenant-1",
|
|
provider_id="langgenius/github/github",
|
|
credentials={"token": "enc"},
|
|
properties={"project": "enc"},
|
|
)
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = subscription
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
cred_enc = _encrypter_mock(decrypted={"token": "plain"})
|
|
prop_enc = _encrypter_mock(decrypted={"project": "plain"})
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.create_trigger_provider_encrypter_for_subscription",
|
|
return_value=(cred_enc, MagicMock()),
|
|
)
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.create_trigger_provider_encrypter_for_properties",
|
|
return_value=(prop_enc, MagicMock()),
|
|
)
|
|
|
|
# Act
|
|
result = TriggerProviderService.get_subscription_by_id("tenant-1", "sub-1")
|
|
|
|
# Assert
|
|
assert result is subscription
|
|
assert subscription.credentials == {"token": "plain"}
|
|
assert subscription.properties == {"project": "plain"}
|
|
|
|
|
|
def test_delete_trigger_provider_should_raise_error_when_subscription_missing(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = None
|
|
|
|
# Act + Assert
|
|
with pytest.raises(ValueError, match="not found"):
|
|
TriggerProviderService.delete_trigger_provider(mock_session, "tenant-1", "sub-1")
|
|
|
|
|
|
def test_delete_trigger_provider_should_delete_and_clear_cache_even_if_unsubscribe_fails(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
subscription = SimpleNamespace(
|
|
id="sub-1",
|
|
user_id="user-1",
|
|
provider_id=str(provider_id),
|
|
credential_type=CredentialType.OAUTH2.value,
|
|
credentials={"token": "enc"},
|
|
to_entity=lambda: SimpleNamespace(id="sub-1"),
|
|
)
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = subscription
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
cred_enc = _encrypter_mock(decrypted={"token": "plain"})
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.create_trigger_provider_encrypter_for_subscription",
|
|
return_value=(cred_enc, MagicMock()),
|
|
)
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.TriggerManager.unsubscribe_trigger",
|
|
side_effect=RuntimeError("remote fail"),
|
|
)
|
|
mock_delete_cache = mocker.patch("services.trigger.trigger_provider_service.delete_cache_for_subscription")
|
|
|
|
# Act
|
|
TriggerProviderService.delete_trigger_provider(mock_session, "tenant-1", "sub-1")
|
|
|
|
# Assert
|
|
mock_session.delete.assert_called_once_with(subscription)
|
|
mock_delete_cache.assert_called_once()
|
|
|
|
|
|
def test_delete_trigger_provider_should_skip_unsubscribe_for_unauthorized(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
subscription = SimpleNamespace(
|
|
id="sub-2",
|
|
user_id="user-1",
|
|
provider_id=str(provider_id),
|
|
credential_type=CredentialType.UNAUTHORIZED.value,
|
|
credentials={},
|
|
to_entity=lambda: SimpleNamespace(id="sub-2"),
|
|
)
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = subscription
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
mock_unsubscribe = mocker.patch("services.trigger.trigger_provider_service.TriggerManager.unsubscribe_trigger")
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.create_trigger_provider_encrypter_for_subscription",
|
|
return_value=(_encrypter_mock(decrypted={}), MagicMock()),
|
|
)
|
|
|
|
# Act
|
|
TriggerProviderService.delete_trigger_provider(mock_session, "tenant-1", "sub-2")
|
|
|
|
# Assert
|
|
mock_unsubscribe.assert_not_called()
|
|
mock_session.delete.assert_called_once_with(subscription)
|
|
|
|
|
|
def test_refresh_oauth_token_should_raise_error_when_subscription_missing(
|
|
mocker: MockerFixture, mock_session: MagicMock
|
|
) -> None:
|
|
# Arrange
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = None
|
|
|
|
# Act + Assert
|
|
with pytest.raises(ValueError, match="not found"):
|
|
TriggerProviderService.refresh_oauth_token("tenant-1", "sub-1")
|
|
|
|
|
|
def test_refresh_oauth_token_should_raise_error_for_non_oauth_credentials(
|
|
mocker: MockerFixture, mock_session: MagicMock
|
|
) -> None:
|
|
# Arrange
|
|
subscription = SimpleNamespace(credential_type=CredentialType.API_KEY.value)
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = subscription
|
|
|
|
# Act + Assert
|
|
with pytest.raises(ValueError, match="Only OAuth credentials can be refreshed"):
|
|
TriggerProviderService.refresh_oauth_token("tenant-1", "sub-1")
|
|
|
|
|
|
def test_refresh_oauth_token_should_refresh_and_persist_new_credentials(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
subscription = SimpleNamespace(
|
|
provider_id=str(provider_id),
|
|
user_id="user-1",
|
|
credential_type=CredentialType.OAUTH2.value,
|
|
credentials={"access_token": "enc"},
|
|
credential_expires_at=0,
|
|
)
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = subscription
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
cache = MagicMock()
|
|
cred_enc = _encrypter_mock(decrypted={"access_token": "old"}, encrypted={"access_token": "new"})
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.create_provider_encrypter",
|
|
return_value=(cred_enc, cache),
|
|
)
|
|
mocker.patch.object(TriggerProviderService, "get_oauth_client", return_value={"client_id": "id"})
|
|
refreshed = SimpleNamespace(credentials={"access_token": "new"}, expires_at=12345)
|
|
oauth_handler = MagicMock()
|
|
oauth_handler.refresh_credentials.return_value = refreshed
|
|
mocker.patch("services.trigger.trigger_provider_service.OAuthHandler", return_value=oauth_handler)
|
|
|
|
# Act
|
|
result = TriggerProviderService.refresh_oauth_token("tenant-1", "sub-1")
|
|
|
|
# Assert
|
|
assert result == {"result": "success", "expires_at": 12345}
|
|
assert subscription.credentials == {"access_token": "new"}
|
|
assert subscription.credential_expires_at == 12345
|
|
mock_session.commit.assert_called_once()
|
|
cache.delete.assert_called_once()
|
|
|
|
|
|
def test_refresh_subscription_should_raise_error_when_subscription_missing(
|
|
mocker: MockerFixture, mock_session: MagicMock
|
|
) -> None:
|
|
# Arrange
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = None
|
|
|
|
# Act + Assert
|
|
with pytest.raises(ValueError, match="not found"):
|
|
TriggerProviderService.refresh_subscription("tenant-1", "sub-1", now=100)
|
|
|
|
|
|
def test_refresh_subscription_should_skip_when_not_due(mocker: MockerFixture, mock_session: MagicMock) -> None:
|
|
# Arrange
|
|
subscription = SimpleNamespace(expires_at=200)
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = subscription
|
|
|
|
# Act
|
|
result = TriggerProviderService.refresh_subscription("tenant-1", "sub-1", now=100)
|
|
|
|
# Assert
|
|
assert result == {"result": "skipped", "expires_at": 200}
|
|
|
|
|
|
def test_refresh_subscription_should_refresh_and_persist_properties(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
subscription = SimpleNamespace(
|
|
id="sub-1",
|
|
tenant_id="tenant-1",
|
|
endpoint_id="endpoint-1",
|
|
expires_at=50,
|
|
provider_id=str(provider_id),
|
|
parameters={"event": "push"},
|
|
properties={"p": "enc"},
|
|
credentials={"c": "enc"},
|
|
credential_type=CredentialType.API_KEY.value,
|
|
)
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = subscription
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
cred_enc = _encrypter_mock(decrypted={"c": "plain"})
|
|
prop_cache = MagicMock()
|
|
prop_enc = _encrypter_mock(decrypted={"p": "plain"}, encrypted={"p": "new-enc"})
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.create_trigger_provider_encrypter_for_subscription",
|
|
return_value=(cred_enc, MagicMock()),
|
|
)
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.create_trigger_provider_encrypter_for_properties",
|
|
return_value=(prop_enc, prop_cache),
|
|
)
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.generate_plugin_trigger_endpoint_url",
|
|
return_value="https://endpoint",
|
|
)
|
|
provider_controller.refresh_trigger.return_value = SimpleNamespace(properties={"p": "new"}, expires_at=999)
|
|
|
|
# Act
|
|
result = TriggerProviderService.refresh_subscription("tenant-1", "sub-1", now=100)
|
|
|
|
# Assert
|
|
assert result == {"result": "success", "expires_at": 999}
|
|
assert subscription.properties == {"p": "new-enc"}
|
|
assert subscription.expires_at == 999
|
|
mock_session.commit.assert_called_once()
|
|
prop_cache.delete.assert_called_once()
|
|
|
|
|
|
def test_get_oauth_client_should_return_tenant_client_when_available(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
tenant_client = SimpleNamespace(oauth_params={"client_id": "enc"})
|
|
system_client = None
|
|
query_tenant = MagicMock()
|
|
query_tenant.filter_by.return_value.first.return_value = tenant_client
|
|
mock_session.query.return_value = query_tenant
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
enc = _encrypter_mock(decrypted={"client_id": "plain"})
|
|
mocker.patch("services.trigger.trigger_provider_service.create_provider_encrypter", return_value=(enc, MagicMock()))
|
|
|
|
# Act
|
|
result = TriggerProviderService.get_oauth_client("tenant-1", provider_id)
|
|
|
|
# Assert
|
|
assert result == {"client_id": "plain"}
|
|
|
|
|
|
def test_get_oauth_client_should_return_none_when_plugin_not_verified(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
query_tenant = MagicMock()
|
|
query_tenant.filter_by.return_value.first.return_value = None
|
|
query_system = MagicMock()
|
|
query_system.filter_by.return_value.first.return_value = None
|
|
mock_session.query.side_effect = [query_tenant, query_system]
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
mocker.patch("services.trigger.trigger_provider_service.PluginService.is_plugin_verified", return_value=False)
|
|
|
|
# Act
|
|
result = TriggerProviderService.get_oauth_client("tenant-1", provider_id)
|
|
|
|
# Assert
|
|
assert result is None
|
|
|
|
|
|
def test_get_oauth_client_should_return_decrypted_system_client_when_verified(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
query_tenant = MagicMock()
|
|
query_tenant.filter_by.return_value.first.return_value = None
|
|
query_system = MagicMock()
|
|
query_system.filter_by.return_value.first.return_value = SimpleNamespace(encrypted_oauth_params="enc")
|
|
mock_session.query.side_effect = [query_tenant, query_system]
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
mocker.patch("services.trigger.trigger_provider_service.PluginService.is_plugin_verified", return_value=True)
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.decrypt_system_oauth_params",
|
|
return_value={"client_id": "system"},
|
|
)
|
|
|
|
# Act
|
|
result = TriggerProviderService.get_oauth_client("tenant-1", provider_id)
|
|
|
|
# Assert
|
|
assert result == {"client_id": "system"}
|
|
|
|
|
|
def test_get_oauth_client_should_raise_error_when_system_decryption_fails(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
query_tenant = MagicMock()
|
|
query_tenant.filter_by.return_value.first.return_value = None
|
|
query_system = MagicMock()
|
|
query_system.filter_by.return_value.first.return_value = SimpleNamespace(encrypted_oauth_params="enc")
|
|
mock_session.query.side_effect = [query_tenant, query_system]
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
mocker.patch("services.trigger.trigger_provider_service.PluginService.is_plugin_verified", return_value=True)
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.decrypt_system_oauth_params",
|
|
side_effect=RuntimeError("bad data"),
|
|
)
|
|
|
|
# Act + Assert
|
|
with pytest.raises(ValueError, match="Error decrypting system oauth params"):
|
|
TriggerProviderService.get_oauth_client("tenant-1", provider_id)
|
|
|
|
|
|
def test_is_oauth_system_client_exists_should_return_false_when_unverified(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
mocker.patch("services.trigger.trigger_provider_service.PluginService.is_plugin_verified", return_value=False)
|
|
|
|
# Act
|
|
result = TriggerProviderService.is_oauth_system_client_exists("tenant-1", provider_id)
|
|
|
|
# Assert
|
|
assert result is False
|
|
|
|
|
|
@pytest.mark.parametrize("has_client", [True, False])
|
|
def test_is_oauth_system_client_exists_should_reflect_database_record(
|
|
has_client: bool,
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = object() if has_client else None
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
mocker.patch("services.trigger.trigger_provider_service.PluginService.is_plugin_verified", return_value=True)
|
|
|
|
# Act
|
|
result = TriggerProviderService.is_oauth_system_client_exists("tenant-1", provider_id)
|
|
|
|
# Assert
|
|
assert result is has_client
|
|
|
|
|
|
def test_save_custom_oauth_client_params_should_return_success_when_nothing_to_update(
|
|
provider_id: TriggerProviderID,
|
|
) -> None:
|
|
# Arrange
|
|
# Act
|
|
result = TriggerProviderService.save_custom_oauth_client_params("tenant-1", provider_id, None, None)
|
|
|
|
# Assert
|
|
assert result == {"result": "success"}
|
|
|
|
|
|
def test_save_custom_oauth_client_params_should_create_record_and_clear_params_when_client_params_none(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
query = MagicMock()
|
|
query.filter_by.return_value.first.return_value = None
|
|
mock_session.query.return_value = query
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
fake_model = SimpleNamespace(encrypted_oauth_params="", enabled=False, oauth_params={})
|
|
mocker.patch("services.trigger.trigger_provider_service.TriggerOAuthTenantClient", return_value=fake_model)
|
|
|
|
# Act
|
|
result = TriggerProviderService.save_custom_oauth_client_params(
|
|
tenant_id="tenant-1",
|
|
provider_id=provider_id,
|
|
client_params=None,
|
|
enabled=True,
|
|
)
|
|
|
|
# Assert
|
|
assert result == {"result": "success"}
|
|
assert fake_model.encrypted_oauth_params == "{}"
|
|
assert fake_model.enabled is True
|
|
mock_session.add.assert_called_once_with(fake_model)
|
|
mock_session.commit.assert_called_once()
|
|
|
|
|
|
def test_save_custom_oauth_client_params_should_merge_hidden_values_and_delete_cache(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
custom_client = SimpleNamespace(oauth_params={"client_id": "enc-old"}, enabled=False)
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = custom_client
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
cache = MagicMock()
|
|
enc = _encrypter_mock(decrypted={"client_id": "old-id"}, encrypted={"client_id": "new-id"})
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.create_provider_encrypter",
|
|
return_value=(enc, cache),
|
|
)
|
|
|
|
# Act
|
|
result = TriggerProviderService.save_custom_oauth_client_params(
|
|
tenant_id="tenant-1",
|
|
provider_id=provider_id,
|
|
client_params={"client_id": HIDDEN_VALUE, "client_secret": "new"},
|
|
enabled=None,
|
|
)
|
|
|
|
# Assert
|
|
assert result == {"result": "success"}
|
|
assert json.loads(custom_client.encrypted_oauth_params) == {"client_id": "new-id"}
|
|
cache.delete.assert_called_once()
|
|
mock_session.commit.assert_called_once()
|
|
|
|
|
|
def test_get_custom_oauth_client_params_should_return_empty_when_record_missing(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
) -> None:
|
|
# Arrange
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = None
|
|
|
|
# Act
|
|
result = TriggerProviderService.get_custom_oauth_client_params("tenant-1", provider_id)
|
|
|
|
# Assert
|
|
assert result == {}
|
|
|
|
|
|
def test_get_custom_oauth_client_params_should_return_masked_decrypted_values(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
custom_client = SimpleNamespace(oauth_params={"client_id": "enc"})
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = custom_client
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
enc = _encrypter_mock(decrypted={"client_id": "plain"}, masked={"client_id": "pl***id"})
|
|
mocker.patch("services.trigger.trigger_provider_service.create_provider_encrypter", return_value=(enc, MagicMock()))
|
|
|
|
# Act
|
|
result = TriggerProviderService.get_custom_oauth_client_params("tenant-1", provider_id)
|
|
|
|
# Assert
|
|
assert result == {"client_id": "pl***id"}
|
|
|
|
|
|
def test_delete_custom_oauth_client_params_should_delete_record_and_commit(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
) -> None:
|
|
# Arrange
|
|
mock_session.query.return_value.filter_by.return_value.delete.return_value = 1
|
|
|
|
# Act
|
|
result = TriggerProviderService.delete_custom_oauth_client_params("tenant-1", provider_id)
|
|
|
|
# Assert
|
|
assert result == {"result": "success"}
|
|
mock_session.commit.assert_called_once()
|
|
|
|
|
|
@pytest.mark.parametrize("exists", [True, False])
|
|
def test_is_oauth_custom_client_enabled_should_return_expected_boolean(
|
|
exists: bool,
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
) -> None:
|
|
# Arrange
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = object() if exists else None
|
|
|
|
# Act
|
|
result = TriggerProviderService.is_oauth_custom_client_enabled("tenant-1", provider_id)
|
|
|
|
# Assert
|
|
assert result is exists
|
|
|
|
|
|
def test_get_subscription_by_endpoint_should_return_none_when_not_found(
|
|
mocker: MockerFixture, mock_session: MagicMock
|
|
) -> None:
|
|
# Arrange
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = None
|
|
|
|
# Act
|
|
result = TriggerProviderService.get_subscription_by_endpoint("endpoint-1")
|
|
|
|
# Assert
|
|
assert result is None
|
|
|
|
|
|
def test_get_subscription_by_endpoint_should_decrypt_credentials_and_properties(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
subscription = SimpleNamespace(
|
|
tenant_id="tenant-1",
|
|
provider_id="langgenius/github/github",
|
|
credentials={"token": "enc"},
|
|
properties={"hook": "enc"},
|
|
)
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = subscription
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.create_trigger_provider_encrypter_for_subscription",
|
|
return_value=(_encrypter_mock(decrypted={"token": "plain"}), MagicMock()),
|
|
)
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.create_trigger_provider_encrypter_for_properties",
|
|
return_value=(_encrypter_mock(decrypted={"hook": "plain"}), MagicMock()),
|
|
)
|
|
|
|
# Act
|
|
result = TriggerProviderService.get_subscription_by_endpoint("endpoint-1")
|
|
|
|
# Assert
|
|
assert result is subscription
|
|
assert subscription.credentials == {"token": "plain"}
|
|
assert subscription.properties == {"hook": "plain"}
|
|
|
|
|
|
def test_verify_subscription_credentials_should_raise_when_provider_not_found(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
) -> None:
|
|
# Arrange
|
|
_mock_get_trigger_provider(mocker, None)
|
|
|
|
# Act + Assert
|
|
with pytest.raises(ValueError, match="Provider .* not found"):
|
|
TriggerProviderService.verify_subscription_credentials(
|
|
tenant_id="tenant-1",
|
|
user_id="user-1",
|
|
provider_id=provider_id,
|
|
subscription_id="sub-1",
|
|
credentials={},
|
|
)
|
|
|
|
|
|
def test_verify_subscription_credentials_should_raise_when_subscription_not_found(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
mocker.patch.object(TriggerProviderService, "get_subscription_by_id", return_value=None)
|
|
|
|
# Act + Assert
|
|
with pytest.raises(ValueError, match="Subscription sub-1 not found"):
|
|
TriggerProviderService.verify_subscription_credentials(
|
|
tenant_id="tenant-1",
|
|
user_id="user-1",
|
|
provider_id=provider_id,
|
|
subscription_id="sub-1",
|
|
credentials={},
|
|
)
|
|
|
|
|
|
def test_verify_subscription_credentials_should_raise_when_api_key_validation_fails(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
subscription = SimpleNamespace(credential_type=CredentialType.API_KEY.value, credentials={"api_key": "old"})
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
mocker.patch.object(TriggerProviderService, "get_subscription_by_id", return_value=subscription)
|
|
provider_controller.validate_credentials.side_effect = RuntimeError("bad credentials")
|
|
|
|
# Act + Assert
|
|
with pytest.raises(ValueError, match="Invalid credentials: bad credentials"):
|
|
TriggerProviderService.verify_subscription_credentials(
|
|
tenant_id="tenant-1",
|
|
user_id="user-1",
|
|
provider_id=provider_id,
|
|
subscription_id="sub-1",
|
|
credentials={"api_key": HIDDEN_VALUE},
|
|
)
|
|
|
|
|
|
def test_verify_subscription_credentials_should_return_verified_when_api_key_validation_succeeds(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
subscription = SimpleNamespace(credential_type=CredentialType.API_KEY.value, credentials={"api_key": "old"})
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
mocker.patch.object(TriggerProviderService, "get_subscription_by_id", return_value=subscription)
|
|
|
|
# Act
|
|
result = TriggerProviderService.verify_subscription_credentials(
|
|
tenant_id="tenant-1",
|
|
user_id="user-1",
|
|
provider_id=provider_id,
|
|
subscription_id="sub-1",
|
|
credentials={"api_key": HIDDEN_VALUE},
|
|
)
|
|
|
|
# Assert
|
|
assert result == {"verified": True}
|
|
|
|
|
|
def test_verify_subscription_credentials_should_return_verified_for_non_api_key_credentials(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
subscription = SimpleNamespace(credential_type=CredentialType.OAUTH2.value, credentials={})
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
mocker.patch.object(TriggerProviderService, "get_subscription_by_id", return_value=subscription)
|
|
|
|
# Act
|
|
result = TriggerProviderService.verify_subscription_credentials(
|
|
tenant_id="tenant-1",
|
|
user_id="user-1",
|
|
provider_id=provider_id,
|
|
subscription_id="sub-1",
|
|
credentials={},
|
|
)
|
|
|
|
# Assert
|
|
assert result == {"verified": True}
|
|
|
|
|
|
def test_rebuild_trigger_subscription_should_raise_when_provider_not_found(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
) -> None:
|
|
# Arrange
|
|
_mock_get_trigger_provider(mocker, None)
|
|
|
|
# Act + Assert
|
|
with pytest.raises(ValueError, match="Provider .* not found"):
|
|
TriggerProviderService.rebuild_trigger_subscription(
|
|
tenant_id="tenant-1",
|
|
provider_id=provider_id,
|
|
subscription_id="sub-1",
|
|
credentials={},
|
|
parameters={},
|
|
)
|
|
|
|
|
|
def test_rebuild_trigger_subscription_should_raise_when_subscription_not_found(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
mocker.patch.object(TriggerProviderService, "get_subscription_by_id", return_value=None)
|
|
|
|
# Act + Assert
|
|
with pytest.raises(ValueError, match="Subscription sub-1 not found"):
|
|
TriggerProviderService.rebuild_trigger_subscription(
|
|
tenant_id="tenant-1",
|
|
provider_id=provider_id,
|
|
subscription_id="sub-1",
|
|
credentials={},
|
|
parameters={},
|
|
)
|
|
|
|
|
|
def test_rebuild_trigger_subscription_should_raise_for_unsupported_credential_type(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
subscription = SimpleNamespace(credential_type=CredentialType.UNAUTHORIZED.value)
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
mocker.patch.object(TriggerProviderService, "get_subscription_by_id", return_value=subscription)
|
|
|
|
# Act + Assert
|
|
with pytest.raises(ValueError, match="not supported for auto creation"):
|
|
TriggerProviderService.rebuild_trigger_subscription(
|
|
tenant_id="tenant-1",
|
|
provider_id=provider_id,
|
|
subscription_id="sub-1",
|
|
credentials={},
|
|
parameters={},
|
|
)
|
|
|
|
|
|
def test_rebuild_trigger_subscription_should_raise_when_unsubscribe_fails(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
subscription = SimpleNamespace(
|
|
id="sub-1",
|
|
user_id="user-1",
|
|
endpoint_id="endpoint-1",
|
|
credential_type=CredentialType.API_KEY.value,
|
|
credentials={"api_key": "old"},
|
|
to_entity=lambda: SimpleNamespace(id="sub-1"),
|
|
)
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
mocker.patch.object(TriggerProviderService, "get_subscription_by_id", return_value=subscription)
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.TriggerManager.unsubscribe_trigger",
|
|
return_value=SimpleNamespace(success=False, message="remote error"),
|
|
)
|
|
|
|
# Act + Assert
|
|
with pytest.raises(ValueError, match="Failed to delete previous subscription"):
|
|
TriggerProviderService.rebuild_trigger_subscription(
|
|
tenant_id="tenant-1",
|
|
provider_id=provider_id,
|
|
subscription_id="sub-1",
|
|
credentials={},
|
|
parameters={},
|
|
)
|
|
|
|
|
|
def test_rebuild_trigger_subscription_should_resubscribe_and_update_existing_subscription(
|
|
mocker: MockerFixture,
|
|
mock_session: MagicMock,
|
|
provider_id: TriggerProviderID,
|
|
provider_controller: MagicMock,
|
|
) -> None:
|
|
# Arrange
|
|
subscription = SimpleNamespace(
|
|
id="sub-1",
|
|
user_id="user-1",
|
|
endpoint_id="endpoint-1",
|
|
credential_type=CredentialType.API_KEY.value,
|
|
credentials={"api_key": "old-key"},
|
|
to_entity=lambda: SimpleNamespace(id="sub-1"),
|
|
)
|
|
new_subscription = SimpleNamespace(properties={"project": "new"}, expires_at=888)
|
|
_mock_get_trigger_provider(mocker, provider_controller)
|
|
mocker.patch.object(TriggerProviderService, "get_subscription_by_id", return_value=subscription)
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.TriggerManager.unsubscribe_trigger",
|
|
return_value=SimpleNamespace(success=True, message="ok"),
|
|
)
|
|
mock_subscribe = mocker.patch(
|
|
"services.trigger.trigger_provider_service.TriggerManager.subscribe_trigger",
|
|
return_value=new_subscription,
|
|
)
|
|
mocker.patch(
|
|
"services.trigger.trigger_provider_service.generate_plugin_trigger_endpoint_url",
|
|
return_value="https://endpoint",
|
|
)
|
|
mock_update = mocker.patch.object(TriggerProviderService, "update_trigger_subscription")
|
|
|
|
# Act
|
|
TriggerProviderService.rebuild_trigger_subscription(
|
|
tenant_id="tenant-1",
|
|
provider_id=provider_id,
|
|
subscription_id="sub-1",
|
|
credentials={"api_key": HIDDEN_VALUE, "region": "us"},
|
|
parameters={"event": "push"},
|
|
name="updated",
|
|
)
|
|
|
|
# Assert
|
|
call_kwargs = mock_subscribe.call_args.kwargs
|
|
assert call_kwargs["credentials"]["api_key"] == "old-key"
|
|
assert call_kwargs["credentials"]["region"] == "us"
|
|
mock_update.assert_called_once_with(
|
|
tenant_id="tenant-1",
|
|
subscription_id="sub-1",
|
|
name="updated",
|
|
parameters={"event": "push"},
|
|
credentials={"api_key": "old-key", "region": "us"},
|
|
properties={"project": "new"},
|
|
expires_at=888,
|
|
)
|