"""Unit tests for the 'suspend-user-by-id' action on the Lambda handler.""" from typing import Any from unittest.mock import MagicMock, patch import pytest import requests from auth0_client import app def _event(**overrides: Any) -> dict[str, Any]: """Build a valid suspend-user-by-id event with optional overrides.""" event = { 'action': 'suspend-user-by-id', 'tenant': 'prod-orchard', 'user_id': 'auth0|abc', 'ticket_id': 'SYS-1', } event.update(overrides) return event def test_suspend_user_dry_run_skips_patch() -> None: """dry_run returns suspended=False without touching Auth0 or secrets.""" with patch.object(app, 'SecretsManagerClient') as secrets: result = app.handler(_event(dry_run=True), None) assert result == { 'tenant': 'prod-orchard', 'user_id': 'auth0|abc', 'ticket_id': 'SYS-1', 'suspended': False, 'dry_run': True, } secrets.assert_not_called() @patch.object(app, 'Auth0Client') @patch.object(app, 'SecretsManagerClient') def test_suspend_user_success(secrets_cls: MagicMock, client_cls: MagicMock) -> None: """A successful block returns suspended=True and calls block_user_by_id.""" secrets_cls.return_value.get_tenant_credentials.return_value = MagicMock( domain='d', client_id='c', client_secret='s' ) client = client_cls.return_value result = app.handler(_event(), None) client.block_user_by_id.assert_called_once_with('auth0|abc') assert result['suspended'] is True assert result['dry_run'] is False @patch.object(app, 'Auth0Client') @patch.object(app, 'SecretsManagerClient') def test_suspend_user_404_is_idempotent( secrets_cls: MagicMock, client_cls: MagicMock ) -> None: """A 404 from Auth0 is treated as idempotent success (suspended=False).""" secrets_cls.return_value.get_tenant_credentials.return_value = MagicMock( domain='d', client_id='c', client_secret='s' ) response = requests.Response() response.status_code = 404 err = requests.exceptions.HTTPError(response=response) client_cls.return_value.block_user_by_id.side_effect = err result = app.handler(_event(), None) assert result['suspended'] is False assert result['dry_run'] is False @patch.object(app, 'Auth0Client') @patch.object(app, 'SecretsManagerClient') def test_suspend_user_non_404_http_error_propagates( secrets_cls: MagicMock, client_cls: MagicMock ) -> None: """A non-404 HTTPError propagates (fails the Step Functions task).""" secrets_cls.return_value.get_tenant_credentials.return_value = MagicMock( domain='d', client_id='c', client_secret='s' ) response = requests.Response() response.status_code = 500 err = requests.exceptions.HTTPError(response=response) client_cls.return_value.block_user_by_id.side_effect = err with pytest.raises(requests.exceptions.HTTPError): app.handler(_event(), None) @patch.object(app, 'SecretsManagerClient') def test_suspend_user_credential_failure_raises_value_error( secrets_cls: MagicMock, ) -> None: """A credential-fetch failure raises a descriptive ValueError.""" secrets_cls.return_value.get_tenant_credentials.side_effect = Exception('boom') with pytest.raises(ValueError) as exc_info: app.handler(_event(), None) assert 'failed to fetch credentials' in str(exc_info.value)