mirror of
https://github.com/langgenius/dify.git
synced 2026-10-04 13:47:55 +08:00
test: use typed config overrides in service coverage (#40848)
This commit is contained in:
1 parent
b064a13aa4
commit
da6ca23b50
4 files changed
+208
-344
No files matched your search
@@ -21,28 +21,23 @@ class TestQuotaType:
|
||||
|
||||
|
||||
class TestQuotaService:
|
||||
def test_reserve_outside_cloud_edition(self):
|
||||
with (
|
||||
patch("services.quota_service.dify_config") as mock_cfg,
|
||||
patch("services.billing_service.BillingService"),
|
||||
):
|
||||
mock_cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
@pytest.fixture(autouse=True)
|
||||
def _cloud_edition(self, config_overrides):
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
|
||||
def test_reserve_outside_cloud_edition(self, config_overrides):
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY)
|
||||
with patch("services.billing_service.BillingService"):
|
||||
charge = QuotaService.reserve(QuotaType.TRIGGER, "t1")
|
||||
assert charge.success is True
|
||||
assert charge.charge_id is None
|
||||
|
||||
def test_reserve_zero_amount_raises(self):
|
||||
with patch("services.quota_service.dify_config") as mock_cfg:
|
||||
mock_cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
with pytest.raises(ValueError, match="greater than 0"):
|
||||
QuotaService.reserve(QuotaType.TRIGGER, "t1", amount=0)
|
||||
with pytest.raises(ValueError, match="greater than 0"):
|
||||
QuotaService.reserve(QuotaType.TRIGGER, "t1", amount=0)
|
||||
|
||||
def test_reserve_success(self):
|
||||
with (
|
||||
patch("services.quota_service.dify_config") as mock_cfg,
|
||||
patch("services.billing_service.BillingService") as mock_bs,
|
||||
):
|
||||
mock_cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
with patch("services.billing_service.BillingService") as mock_bs:
|
||||
mock_bs.quota_reserve.return_value = {"reservation_id": "rid-1", "available": 99}
|
||||
|
||||
charge = QuotaService.reserve(QuotaType.TRIGGER, "t1", amount=1)
|
||||
@@ -57,11 +52,7 @@ class TestQuotaService:
|
||||
def test_reserve_no_reservation_id_raises(self):
|
||||
from services.errors.app import QuotaExceededError
|
||||
|
||||
with (
|
||||
patch("services.quota_service.dify_config") as mock_cfg,
|
||||
patch("services.billing_service.BillingService") as mock_bs,
|
||||
):
|
||||
mock_cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
with patch("services.billing_service.BillingService") as mock_bs:
|
||||
mock_bs.quota_reserve.return_value = {}
|
||||
|
||||
with pytest.raises(QuotaExceededError):
|
||||
@@ -70,22 +61,14 @@ class TestQuotaService:
|
||||
def test_reserve_quota_exceeded_propagates(self):
|
||||
from services.errors.app import QuotaExceededError
|
||||
|
||||
with (
|
||||
patch("services.quota_service.dify_config") as mock_cfg,
|
||||
patch("services.billing_service.BillingService") as mock_bs,
|
||||
):
|
||||
mock_cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
with patch("services.billing_service.BillingService") as mock_bs:
|
||||
mock_bs.quota_reserve.side_effect = QuotaExceededError(feature="trigger", tenant_id="t1", required=1)
|
||||
|
||||
with pytest.raises(QuotaExceededError):
|
||||
QuotaService.reserve(QuotaType.TRIGGER, "t1")
|
||||
|
||||
def test_reserve_api_exception_returns_unlimited(self):
|
||||
with (
|
||||
patch("services.quota_service.dify_config") as mock_cfg,
|
||||
patch("services.billing_service.BillingService") as mock_bs,
|
||||
):
|
||||
mock_cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
with patch("services.billing_service.BillingService") as mock_bs:
|
||||
mock_bs.quota_reserve.side_effect = RuntimeError("network")
|
||||
|
||||
charge = QuotaService.reserve(QuotaType.TRIGGER, "t1")
|
||||
@@ -93,11 +76,7 @@ class TestQuotaService:
|
||||
assert charge.charge_id is None
|
||||
|
||||
def test_consume_calls_reserve_and_commit(self):
|
||||
with (
|
||||
patch("services.quota_service.dify_config") as mock_cfg,
|
||||
patch("services.billing_service.BillingService") as mock_bs,
|
||||
):
|
||||
mock_cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
with patch("services.billing_service.BillingService") as mock_bs:
|
||||
mock_bs.quota_reserve.return_value = {"reservation_id": "rid-c"}
|
||||
mock_bs.quota_commit.return_value = {}
|
||||
|
||||
@@ -105,73 +84,43 @@ class TestQuotaService:
|
||||
assert charge.success is True
|
||||
mock_bs.quota_commit.assert_called_once()
|
||||
|
||||
def test_check_outside_cloud_edition(self):
|
||||
with patch("services.quota_service.dify_config") as mock_cfg:
|
||||
mock_cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
assert QuotaService.check(QuotaType.TRIGGER, "t1") is True
|
||||
def test_check_outside_cloud_edition(self, config_overrides):
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY)
|
||||
assert QuotaService.check(QuotaType.TRIGGER, "t1") is True
|
||||
|
||||
def test_check_zero_amount_raises(self):
|
||||
with patch("services.quota_service.dify_config") as mock_cfg:
|
||||
mock_cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
with pytest.raises(ValueError, match="greater than 0"):
|
||||
QuotaService.check(QuotaType.TRIGGER, "t1", amount=0)
|
||||
with pytest.raises(ValueError, match="greater than 0"):
|
||||
QuotaService.check(QuotaType.TRIGGER, "t1", amount=0)
|
||||
|
||||
def test_check_sufficient_quota(self):
|
||||
with (
|
||||
patch("services.quota_service.dify_config") as mock_cfg,
|
||||
patch.object(QuotaService, "get_remaining", return_value=100),
|
||||
):
|
||||
mock_cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
with patch.object(QuotaService, "get_remaining", return_value=100):
|
||||
assert QuotaService.check(QuotaType.TRIGGER, "t1", amount=50) is True
|
||||
|
||||
def test_check_insufficient_quota(self):
|
||||
with (
|
||||
patch("services.quota_service.dify_config") as mock_cfg,
|
||||
patch.object(QuotaService, "get_remaining", return_value=5),
|
||||
):
|
||||
mock_cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
with patch.object(QuotaService, "get_remaining", return_value=5):
|
||||
assert QuotaService.check(QuotaType.TRIGGER, "t1", amount=10) is False
|
||||
|
||||
def test_check_unlimited_quota(self):
|
||||
with (
|
||||
patch("services.quota_service.dify_config") as mock_cfg,
|
||||
patch.object(QuotaService, "get_remaining", return_value=-1),
|
||||
):
|
||||
mock_cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
with patch.object(QuotaService, "get_remaining", return_value=-1):
|
||||
assert QuotaService.check(QuotaType.TRIGGER, "t1", amount=999) is True
|
||||
|
||||
def test_check_exception_returns_true(self):
|
||||
with (
|
||||
patch("services.quota_service.dify_config") as mock_cfg,
|
||||
patch.object(QuotaService, "get_remaining", side_effect=RuntimeError),
|
||||
):
|
||||
mock_cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
with patch.object(QuotaService, "get_remaining", side_effect=RuntimeError):
|
||||
assert QuotaService.check(QuotaType.TRIGGER, "t1") is True
|
||||
|
||||
def test_release_outside_cloud_edition(self):
|
||||
with (
|
||||
patch("services.quota_service.dify_config") as mock_cfg,
|
||||
patch("services.billing_service.BillingService") as mock_bs,
|
||||
):
|
||||
mock_cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
def test_release_outside_cloud_edition(self, config_overrides):
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY)
|
||||
with patch("services.billing_service.BillingService") as mock_bs:
|
||||
QuotaService.release(QuotaType.TRIGGER, "rid-1", "t1", "trigger_event")
|
||||
mock_bs.quota_release.assert_not_called()
|
||||
|
||||
def test_release_empty_reservation(self):
|
||||
with (
|
||||
patch("services.quota_service.dify_config") as mock_cfg,
|
||||
patch("services.billing_service.BillingService") as mock_bs,
|
||||
):
|
||||
mock_cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
with patch("services.billing_service.BillingService") as mock_bs:
|
||||
QuotaService.release(QuotaType.TRIGGER, "", "t1", "trigger_event")
|
||||
mock_bs.quota_release.assert_not_called()
|
||||
|
||||
def test_release_success(self):
|
||||
with (
|
||||
patch("services.quota_service.dify_config") as mock_cfg,
|
||||
patch("services.billing_service.BillingService") as mock_bs,
|
||||
):
|
||||
mock_cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
with patch("services.billing_service.BillingService") as mock_bs:
|
||||
mock_bs.quota_release.return_value = {}
|
||||
QuotaService.release(QuotaType.TRIGGER, "rid-1", "t1", "trigger_event")
|
||||
mock_bs.quota_release.assert_called_once_with(
|
||||
@@ -179,11 +128,7 @@ class TestQuotaService:
|
||||
)
|
||||
|
||||
def test_release_exception_swallowed(self):
|
||||
with (
|
||||
patch("services.quota_service.dify_config") as mock_cfg,
|
||||
patch("services.billing_service.BillingService") as mock_bs,
|
||||
):
|
||||
mock_cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
with patch("services.billing_service.BillingService") as mock_bs:
|
||||
mock_bs.quota_release.side_effect = RuntimeError("fail")
|
||||
QuotaService.release(QuotaType.TRIGGER, "rid-1", "t1", "trigger_event")
|
||||
|
||||
|
||||
@@ -12,18 +12,16 @@ class TestPassportService:
|
||||
"""Test PassportService JWT operations"""
|
||||
|
||||
@pytest.fixture
|
||||
def passport_service(self):
|
||||
def passport_service(self, config_overrides):
|
||||
"""Create PassportService instance with test secret key"""
|
||||
with patch("libs.passport.dify_config") as mock_config:
|
||||
mock_config.SECRET_KEY = "test-secret-key-for-testing"
|
||||
return PassportService()
|
||||
config_overrides(SECRET_KEY="test-secret-key-for-testing")
|
||||
return PassportService()
|
||||
|
||||
@pytest.fixture
|
||||
def another_passport_service(self):
|
||||
def another_passport_service(self, config_overrides):
|
||||
"""Create another PassportService instance with different secret key"""
|
||||
with patch("libs.passport.dify_config") as mock_config:
|
||||
mock_config.SECRET_KEY = "another-secret-key-for-testing"
|
||||
return PassportService()
|
||||
config_overrides(SECRET_KEY="another-secret-key-for-testing")
|
||||
return PassportService()
|
||||
|
||||
# Core functionality tests
|
||||
def test_should_issue_and_verify_token(self, passport_service):
|
||||
@@ -98,10 +96,8 @@ class TestPassportService:
|
||||
payload = {"user_id": "123"}
|
||||
|
||||
# Create token with different algorithm
|
||||
with patch("libs.passport.dify_config") as mock_config:
|
||||
mock_config.SECRET_KEY = "test-secret-key-for-testing"
|
||||
# Create token with HS512 instead of HS256
|
||||
wrong_alg_token = jwt.encode(payload, mock_config.SECRET_KEY, algorithm="HS512")
|
||||
# Create token with HS512 instead of HS256
|
||||
wrong_alg_token = jwt.encode(payload, "test-secret-key-for-testing", algorithm="HS512")
|
||||
|
||||
# Should fail because service expects HS256
|
||||
# InvalidAlgorithmError is now caught by PyJWTError handler
|
||||
@@ -134,20 +130,17 @@ class TestPassportService:
|
||||
past_time = datetime.now(UTC) - timedelta(hours=1)
|
||||
payload = {"user_id": "123", "exp": past_time.timestamp()}
|
||||
|
||||
with patch("libs.passport.dify_config") as mock_config:
|
||||
mock_config.SECRET_KEY = "test-secret-key-for-testing"
|
||||
token = jwt.encode(payload, mock_config.SECRET_KEY, algorithm="HS256")
|
||||
token = jwt.encode(payload, "test-secret-key-for-testing", algorithm="HS256")
|
||||
|
||||
with pytest.raises(Unauthorized) as exc_info:
|
||||
passport_service.verify(token)
|
||||
assert str(exc_info.value) == "401 Unauthorized: Token has expired."
|
||||
|
||||
# Configuration tests
|
||||
def test_should_use_configured_secret_key_without_policy_validation(self):
|
||||
def test_should_use_configured_secret_key_without_policy_validation(self, config_overrides):
|
||||
"""Test that policy decisions are owned by config, not PassportService."""
|
||||
with patch("libs.passport.dify_config") as mock_config:
|
||||
mock_config.SECRET_KEY = "configured"
|
||||
service = PassportService()
|
||||
config_overrides(SECRET_KEY="configured")
|
||||
service = PassportService()
|
||||
|
||||
assert service.sk == "configured"
|
||||
|
||||
|
||||
+140
-209
@@ -26,12 +26,17 @@ def mock_repo():
|
||||
return MagicMock()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _cleanup_config(config_overrides):
|
||||
config_overrides(
|
||||
SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD=0,
|
||||
DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cleanup(mock_repo):
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
yield WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
return WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -41,91 +46,68 @@ def cleanup(mock_repo):
|
||||
|
||||
class TestWorkflowRunCleanupInit:
|
||||
def test_only_start_from_raises(self, mock_repo):
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
with pytest.raises(ValueError, match="both set or both omitted"):
|
||||
WorkflowRunCleanup(
|
||||
days=30,
|
||||
batch_size=10,
|
||||
start_from=datetime.datetime(2024, 1, 1),
|
||||
workflow_run_repo=mock_repo,
|
||||
)
|
||||
with pytest.raises(ValueError, match="both set or both omitted"):
|
||||
WorkflowRunCleanup(
|
||||
days=30,
|
||||
batch_size=10,
|
||||
start_from=datetime.datetime(2024, 1, 1),
|
||||
workflow_run_repo=mock_repo,
|
||||
)
|
||||
|
||||
def test_only_end_before_raises(self, mock_repo):
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
with pytest.raises(ValueError, match="both set or both omitted"):
|
||||
WorkflowRunCleanup(
|
||||
days=30,
|
||||
batch_size=10,
|
||||
end_before=datetime.datetime(2024, 1, 1),
|
||||
workflow_run_repo=mock_repo,
|
||||
)
|
||||
with pytest.raises(ValueError, match="both set or both omitted"):
|
||||
WorkflowRunCleanup(
|
||||
days=30,
|
||||
batch_size=10,
|
||||
end_before=datetime.datetime(2024, 1, 1),
|
||||
workflow_run_repo=mock_repo,
|
||||
)
|
||||
|
||||
def test_end_before_not_greater_than_start_raises(self, mock_repo):
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
with pytest.raises(ValueError, match="end_before must be greater than start_from"):
|
||||
WorkflowRunCleanup(
|
||||
days=30,
|
||||
batch_size=10,
|
||||
start_from=datetime.datetime(2024, 6, 1),
|
||||
end_before=datetime.datetime(2024, 1, 1),
|
||||
workflow_run_repo=mock_repo,
|
||||
)
|
||||
with pytest.raises(ValueError, match="end_before must be greater than start_from"):
|
||||
WorkflowRunCleanup(
|
||||
days=30,
|
||||
batch_size=10,
|
||||
start_from=datetime.datetime(2024, 6, 1),
|
||||
end_before=datetime.datetime(2024, 1, 1),
|
||||
workflow_run_repo=mock_repo,
|
||||
)
|
||||
|
||||
def test_equal_start_end_raises(self, mock_repo):
|
||||
dt = datetime.datetime(2024, 1, 1)
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
with pytest.raises(ValueError):
|
||||
WorkflowRunCleanup(
|
||||
days=30,
|
||||
batch_size=10,
|
||||
start_from=dt,
|
||||
end_before=dt,
|
||||
workflow_run_repo=mock_repo,
|
||||
)
|
||||
|
||||
def test_zero_batch_size_raises(self, mock_repo):
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
with pytest.raises(ValueError, match="batch_size must be greater than 0"):
|
||||
WorkflowRunCleanup(days=30, batch_size=0, workflow_run_repo=mock_repo)
|
||||
|
||||
def test_negative_batch_size_raises(self, mock_repo):
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
with pytest.raises(ValueError):
|
||||
WorkflowRunCleanup(days=30, batch_size=-1, workflow_run_repo=mock_repo)
|
||||
|
||||
def test_valid_window_init(self, mock_repo):
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 7
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
start = datetime.datetime(2024, 1, 1)
|
||||
end = datetime.datetime(2024, 6, 1)
|
||||
c = WorkflowRunCleanup(
|
||||
with pytest.raises(ValueError):
|
||||
WorkflowRunCleanup(
|
||||
days=30,
|
||||
batch_size=5,
|
||||
start_from=start,
|
||||
end_before=end,
|
||||
batch_size=10,
|
||||
start_from=dt,
|
||||
end_before=dt,
|
||||
workflow_run_repo=mock_repo,
|
||||
)
|
||||
assert c.window_start == start
|
||||
assert c.window_end == end
|
||||
|
||||
def test_zero_batch_size_raises(self, mock_repo):
|
||||
with pytest.raises(ValueError, match="batch_size must be greater than 0"):
|
||||
WorkflowRunCleanup(days=30, batch_size=0, workflow_run_repo=mock_repo)
|
||||
|
||||
def test_negative_batch_size_raises(self, mock_repo):
|
||||
with pytest.raises(ValueError):
|
||||
WorkflowRunCleanup(days=30, batch_size=-1, workflow_run_repo=mock_repo)
|
||||
|
||||
def test_valid_window_init(self, mock_repo, config_overrides):
|
||||
config_overrides(SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD=7)
|
||||
start = datetime.datetime(2024, 1, 1)
|
||||
end = datetime.datetime(2024, 6, 1)
|
||||
c = WorkflowRunCleanup(
|
||||
days=30,
|
||||
batch_size=5,
|
||||
start_from=start,
|
||||
end_before=end,
|
||||
workflow_run_repo=mock_repo,
|
||||
)
|
||||
assert c.window_start == start
|
||||
assert c.window_end == end
|
||||
|
||||
def test_default_task_label_is_custom(self, mock_repo):
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
c = WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
c = WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
|
||||
assert c._metrics._base_attributes["task_label"] == "custom"
|
||||
|
||||
@@ -219,21 +201,15 @@ class TestIsWithinGracePeriod:
|
||||
class TestGetCleanupWhitelist:
|
||||
def test_non_cloud_edition_returns_empty(self, cleanup):
|
||||
cleanup._cleanup_whitelist = None
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
result = cleanup._get_cleanup_whitelist()
|
||||
result = cleanup._get_cleanup_whitelist()
|
||||
assert result == set()
|
||||
|
||||
def test_cloud_edition_fetches_whitelist(self, mock_repo):
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
c = WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
with patch(
|
||||
"services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.BillingService"
|
||||
) as bs:
|
||||
bs.get_expired_subscription_cleanup_whitelist.return_value = ["t1", "t2"]
|
||||
result = c._get_cleanup_whitelist()
|
||||
def test_cloud_edition_fetches_whitelist(self, mock_repo, config_overrides):
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
c = WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.BillingService") as bs:
|
||||
bs.get_expired_subscription_cleanup_whitelist.return_value = ["t1", "t2"]
|
||||
result = c._get_cleanup_whitelist()
|
||||
assert result == {"t1", "t2"}
|
||||
|
||||
def test_cached_whitelist_returned(self, cleanup):
|
||||
@@ -241,16 +217,12 @@ class TestGetCleanupWhitelist:
|
||||
result = cleanup._get_cleanup_whitelist()
|
||||
assert result == {"cached"}
|
||||
|
||||
def test_billing_service_error_returns_empty(self, mock_repo):
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
c = WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
with patch(
|
||||
"services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.BillingService"
|
||||
) as bs:
|
||||
bs.get_expired_subscription_cleanup_whitelist.side_effect = Exception("error")
|
||||
result = c._get_cleanup_whitelist()
|
||||
def test_billing_service_error_returns_empty(self, mock_repo, config_overrides):
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
c = WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.BillingService") as bs:
|
||||
bs.get_expired_subscription_cleanup_whitelist.side_effect = Exception("error")
|
||||
result = c._get_cleanup_whitelist()
|
||||
assert result == set()
|
||||
|
||||
|
||||
@@ -264,68 +236,51 @@ class TestFilterFreeTenants:
|
||||
result = cleanup._filter_free_tenants(["t1", "t2"])
|
||||
assert result == {"t1", "t2"}
|
||||
|
||||
def test_empty_tenants_returns_empty(self, cleanup):
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
result = cleanup._filter_free_tenants([])
|
||||
def test_empty_tenants_returns_empty(self, cleanup, config_overrides):
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
result = cleanup._filter_free_tenants([])
|
||||
assert result == set()
|
||||
|
||||
def test_whitelisted_tenant_excluded(self, mock_repo):
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
c = WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
c._cleanup_whitelist = {"t1"}
|
||||
with patch(
|
||||
"services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.BillingService"
|
||||
) as bs:
|
||||
bs.get_plan_bulk_with_cache.return_value = {
|
||||
"t1": {"plan": CloudPlan.SANDBOX, "expiration_date": -1},
|
||||
"t2": {"plan": CloudPlan.SANDBOX, "expiration_date": -1},
|
||||
}
|
||||
result = c._filter_free_tenants(["t1", "t2"])
|
||||
def test_whitelisted_tenant_excluded(self, mock_repo, config_overrides):
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
c = WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
c._cleanup_whitelist = {"t1"}
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.BillingService") as bs:
|
||||
bs.get_plan_bulk_with_cache.return_value = {
|
||||
"t1": {"plan": CloudPlan.SANDBOX, "expiration_date": -1},
|
||||
"t2": {"plan": CloudPlan.SANDBOX, "expiration_date": -1},
|
||||
}
|
||||
result = c._filter_free_tenants(["t1", "t2"])
|
||||
assert "t1" not in result
|
||||
assert "t2" in result
|
||||
|
||||
def test_paid_tenant_excluded(self, mock_repo):
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
c = WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
c._cleanup_whitelist = set()
|
||||
with patch(
|
||||
"services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.BillingService"
|
||||
) as bs:
|
||||
bs.get_plan_bulk_with_cache.return_value = {
|
||||
"t1": {"plan": "professional", "expiration_date": -1},
|
||||
}
|
||||
result = c._filter_free_tenants(["t1"])
|
||||
def test_paid_tenant_excluded(self, mock_repo, config_overrides):
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
c = WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
c._cleanup_whitelist = set()
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.BillingService") as bs:
|
||||
bs.get_plan_bulk_with_cache.return_value = {
|
||||
"t1": {"plan": "professional", "expiration_date": -1},
|
||||
}
|
||||
result = c._filter_free_tenants(["t1"])
|
||||
assert result == set()
|
||||
|
||||
def test_missing_billing_info_treats_as_non_free(self, mock_repo):
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
c = WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
c._cleanup_whitelist = set()
|
||||
with patch(
|
||||
"services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.BillingService"
|
||||
) as bs:
|
||||
bs.get_plan_bulk_with_cache.return_value = {}
|
||||
result = c._filter_free_tenants(["t1"])
|
||||
def test_missing_billing_info_treats_as_non_free(self, mock_repo, config_overrides):
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
c = WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
c._cleanup_whitelist = set()
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.BillingService") as bs:
|
||||
bs.get_plan_bulk_with_cache.return_value = {}
|
||||
result = c._filter_free_tenants(["t1"])
|
||||
assert result == set()
|
||||
|
||||
def test_billing_bulk_error_treats_as_non_free(self, mock_repo):
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD
|
||||
c = WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
c._cleanup_whitelist = set()
|
||||
with patch(
|
||||
"services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.BillingService"
|
||||
) as bs:
|
||||
bs.get_plan_bulk_with_cache.side_effect = Exception("fail")
|
||||
result = c._filter_free_tenants(["t1"])
|
||||
def test_billing_bulk_error_treats_as_non_free(self, mock_repo, config_overrides):
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
c = WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
c._cleanup_whitelist = set()
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.BillingService") as bs:
|
||||
bs.get_plan_bulk_with_cache.side_effect = Exception("fail")
|
||||
result = c._filter_free_tenants(["t1"])
|
||||
assert result == set()
|
||||
|
||||
|
||||
@@ -336,17 +291,12 @@ class TestFilterFreeTenants:
|
||||
|
||||
class TestRunDeleteMode:
|
||||
def _make_cleanup(self, mock_repo):
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
return WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
return WorkflowRunCleanup(days=30, batch_size=10, workflow_run_repo=mock_repo)
|
||||
|
||||
def test_no_rows_stops_immediately(self, mock_repo):
|
||||
mock_repo.get_cleanup_refs_batch_by_time_range.return_value = []
|
||||
c = self._make_cleanup(mock_repo)
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
c.run()
|
||||
c.run()
|
||||
mock_repo.delete_runs_with_related_by_ids.assert_not_called()
|
||||
|
||||
def test_all_paid_skips_delete(self, mock_repo):
|
||||
@@ -355,9 +305,7 @@ class TestRunDeleteMode:
|
||||
c = self._make_cleanup(mock_repo)
|
||||
# Override the non-Cloud default to exercise the no-deletion path.
|
||||
c._filter_free_tenants = MagicMock(return_value=set())
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
c.run()
|
||||
c.run()
|
||||
mock_repo.delete_runs_with_related_by_ids.assert_not_called()
|
||||
|
||||
def test_runs_deleted_successfully(self, mock_repo):
|
||||
@@ -373,10 +321,8 @@ class TestRunDeleteMode:
|
||||
"pause_reasons": 0,
|
||||
}
|
||||
c = self._make_cleanup(mock_repo)
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.time.sleep"):
|
||||
c.run()
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.time.sleep"):
|
||||
c.run()
|
||||
mock_repo.delete_runs_with_related_by_ids.assert_called_once()
|
||||
|
||||
def test_delete_exception_reraises(self, mock_repo):
|
||||
@@ -384,24 +330,19 @@ class TestRunDeleteMode:
|
||||
mock_repo.get_cleanup_refs_batch_by_time_range.side_effect = [[ref], [ref]]
|
||||
mock_repo.delete_runs_with_related_by_ids.side_effect = RuntimeError("db error")
|
||||
c = self._make_cleanup(mock_repo)
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
with pytest.raises(RuntimeError):
|
||||
c.run()
|
||||
with pytest.raises(RuntimeError):
|
||||
c.run()
|
||||
|
||||
def test_summary_with_window_start(self, mock_repo):
|
||||
mock_repo.get_cleanup_refs_batch_by_time_range.return_value = []
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
c = WorkflowRunCleanup(
|
||||
days=30,
|
||||
batch_size=10,
|
||||
start_from=datetime.datetime(2024, 1, 1),
|
||||
end_before=datetime.datetime(2024, 6, 1),
|
||||
workflow_run_repo=mock_repo,
|
||||
)
|
||||
c.run()
|
||||
c = WorkflowRunCleanup(
|
||||
days=30,
|
||||
batch_size=10,
|
||||
start_from=datetime.datetime(2024, 1, 1),
|
||||
end_before=datetime.datetime(2024, 6, 1),
|
||||
workflow_run_repo=mock_repo,
|
||||
)
|
||||
c.run()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -411,15 +352,12 @@ class TestRunDeleteMode:
|
||||
|
||||
class TestRunDryRunMode:
|
||||
def _make_dry_cleanup(self, mock_repo):
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
return WorkflowRunCleanup(
|
||||
days=30,
|
||||
batch_size=10,
|
||||
workflow_run_repo=mock_repo,
|
||||
dry_run=True,
|
||||
)
|
||||
return WorkflowRunCleanup(
|
||||
days=30,
|
||||
batch_size=10,
|
||||
workflow_run_repo=mock_repo,
|
||||
dry_run=True,
|
||||
)
|
||||
|
||||
def test_dry_run_no_delete_called(self, mock_repo):
|
||||
ref = make_ref("t1")
|
||||
@@ -434,35 +372,28 @@ class TestRunDryRunMode:
|
||||
"pause_reasons": 0,
|
||||
}
|
||||
c = self._make_dry_cleanup(mock_repo)
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
c.run()
|
||||
c.run()
|
||||
mock_repo.delete_runs_with_related_by_ids.assert_not_called()
|
||||
mock_repo.count_runs_with_related_by_ids.assert_called_once()
|
||||
|
||||
def test_dry_run_summary_with_window_start(self, mock_repo):
|
||||
mock_repo.get_cleanup_refs_batch_by_time_range.return_value = []
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD = 0
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
c = WorkflowRunCleanup(
|
||||
days=30,
|
||||
batch_size=10,
|
||||
start_from=datetime.datetime(2024, 1, 1),
|
||||
end_before=datetime.datetime(2024, 6, 1),
|
||||
workflow_run_repo=mock_repo,
|
||||
dry_run=True,
|
||||
)
|
||||
c.run()
|
||||
c = WorkflowRunCleanup(
|
||||
days=30,
|
||||
batch_size=10,
|
||||
start_from=datetime.datetime(2024, 1, 1),
|
||||
end_before=datetime.datetime(2024, 6, 1),
|
||||
workflow_run_repo=mock_repo,
|
||||
dry_run=True,
|
||||
)
|
||||
c.run()
|
||||
|
||||
def test_dry_run_all_paid_skips_count(self, mock_repo):
|
||||
ref = make_ref("t1")
|
||||
mock_repo.get_cleanup_refs_batch_by_time_range.side_effect = [[ref], []]
|
||||
c = self._make_dry_cleanup(mock_repo)
|
||||
c._filter_free_tenants = MagicMock(return_value=set())
|
||||
with patch("services.retention.workflow_run.clear_free_plan_expired_workflow_run_logs.dify_config") as cfg:
|
||||
cfg.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY
|
||||
c.run()
|
||||
c.run()
|
||||
mock_repo.count_runs_with_related_by_ids.assert_not_called()
|
||||
|
||||
|
||||
|
||||
@@ -27,13 +27,12 @@ class TestSystemEncrypter:
|
||||
expected_key = hashlib.sha256(secret_key.encode()).digest()
|
||||
assert encrypter.key == expected_key
|
||||
|
||||
def test_init_with_none_secret_key(self):
|
||||
def test_init_with_none_secret_key(self, config_overrides):
|
||||
"""Test initialization with None secret key falls back to config"""
|
||||
with patch("core.tools.utils.system_encryption.dify_config") as mock_config:
|
||||
mock_config.SECRET_KEY = "config_secret"
|
||||
encrypter = SystemEncrypter(secret_key=None)
|
||||
expected_key = hashlib.sha256(b"config_secret").digest()
|
||||
assert encrypter.key == expected_key
|
||||
config_overrides(SECRET_KEY="config_secret")
|
||||
encrypter = SystemEncrypter(secret_key=None)
|
||||
expected_key = hashlib.sha256(b"config_secret").digest()
|
||||
assert encrypter.key == expected_key
|
||||
|
||||
def test_init_with_empty_secret_key(self):
|
||||
"""Test initialization with empty secret key"""
|
||||
@@ -41,13 +40,12 @@ class TestSystemEncrypter:
|
||||
expected_key = hashlib.sha256(b"").digest()
|
||||
assert encrypter.key == expected_key
|
||||
|
||||
def test_init_without_secret_key_uses_config(self):
|
||||
def test_init_without_secret_key_uses_config(self, config_overrides):
|
||||
"""Test initialization without secret key uses config"""
|
||||
with patch("core.tools.utils.system_encryption.dify_config") as mock_config:
|
||||
mock_config.SECRET_KEY = "default_secret"
|
||||
encrypter = SystemEncrypter()
|
||||
expected_key = hashlib.sha256(b"default_secret").digest()
|
||||
assert encrypter.key == expected_key
|
||||
config_overrides(SECRET_KEY="default_secret")
|
||||
encrypter = SystemEncrypter()
|
||||
expected_key = hashlib.sha256(b"default_secret").digest()
|
||||
assert encrypter.key == expected_key
|
||||
|
||||
def test_encrypt_params_basic(self):
|
||||
"""Test basic parameters encryption"""
|
||||
@@ -368,25 +366,23 @@ class TestFactoryFunctions:
|
||||
expected_key = hashlib.sha256(secret_key.encode()).digest()
|
||||
assert encrypter.key == expected_key
|
||||
|
||||
def test_create_system_encrypter_without_secret(self):
|
||||
def test_create_system_encrypter_without_secret(self, config_overrides):
|
||||
"""Test factory function without secret key"""
|
||||
with patch("core.tools.utils.system_encryption.dify_config") as mock_config:
|
||||
mock_config.SECRET_KEY = "config_secret"
|
||||
encrypter = create_system_encrypter()
|
||||
config_overrides(SECRET_KEY="config_secret")
|
||||
encrypter = create_system_encrypter()
|
||||
|
||||
assert isinstance(encrypter, SystemEncrypter)
|
||||
expected_key = hashlib.sha256(b"config_secret").digest()
|
||||
assert encrypter.key == expected_key
|
||||
assert isinstance(encrypter, SystemEncrypter)
|
||||
expected_key = hashlib.sha256(b"config_secret").digest()
|
||||
assert encrypter.key == expected_key
|
||||
|
||||
def test_create_system_encrypter_with_none_secret(self):
|
||||
def test_create_system_encrypter_with_none_secret(self, config_overrides):
|
||||
"""Test factory function with None secret key"""
|
||||
with patch("core.tools.utils.system_encryption.dify_config") as mock_config:
|
||||
mock_config.SECRET_KEY = "config_secret"
|
||||
encrypter = create_system_encrypter(None)
|
||||
config_overrides(SECRET_KEY="config_secret")
|
||||
encrypter = create_system_encrypter(None)
|
||||
|
||||
assert isinstance(encrypter, SystemEncrypter)
|
||||
expected_key = hashlib.sha256(b"config_secret").digest()
|
||||
assert encrypter.key == expected_key
|
||||
assert isinstance(encrypter, SystemEncrypter)
|
||||
expected_key = hashlib.sha256(b"config_secret").digest()
|
||||
assert encrypter.key == expected_key
|
||||
|
||||
|
||||
class TestGlobalEncrypterInstance:
|
||||
@@ -405,19 +401,18 @@ class TestGlobalEncrypterInstance:
|
||||
assert encrypter1 is encrypter2
|
||||
assert isinstance(encrypter1, SystemEncrypter)
|
||||
|
||||
def test_get_system_encrypter_uses_config(self):
|
||||
def test_get_system_encrypter_uses_config(self, config_overrides):
|
||||
"""Test that global encrypter uses config"""
|
||||
# Clear the global instance first
|
||||
import core.tools.utils.system_encryption
|
||||
|
||||
core.tools.utils.system_encryption._encrypter = None
|
||||
|
||||
with patch("core.tools.utils.system_encryption.dify_config") as mock_config:
|
||||
mock_config.SECRET_KEY = "global_secret"
|
||||
encrypter = get_system_encrypter()
|
||||
config_overrides(SECRET_KEY="global_secret")
|
||||
encrypter = get_system_encrypter()
|
||||
|
||||
expected_key = hashlib.sha256(b"global_secret").digest()
|
||||
assert encrypter.key == expected_key
|
||||
expected_key = hashlib.sha256(b"global_secret").digest()
|
||||
assert encrypter.key == expected_key
|
||||
|
||||
|
||||
class TestConvenienceFunctions:
|
||||
|
||||
Reference in new issue
Block a user