"""Lambda functional test module.""" from unittest.mock import MagicMock import pytest from src import app def test_get_public_repos_success(mock_dependencies, mock_repos): """Test fetching public repositories without pagination and no errors.""" mock_post = mock_dependencies['request_post'] mock_logger = mock_dependencies['logger'] mock_post.return_value.json.return_value = { 'data': { 'organization': { 'repositories': { 'nodes': mock_repos, 'pageInfo': { 'endCursor': None, 'hasNextPage': False } } } } } repos = app.get_public_repos() assert len(repos) == 2 assert repos[0]['name'] == 'repo1' assert mock_post.call_count == 1 mock_post.assert_called_with( app.GITHUB_GQL_ENDPOINT, json={ 'query': app.GITHUB_GRAPHQL_QUERY, 'variables': {'org': app.GITHUB_ORG, 'after': None} }, headers={ 'Authorization': f'Bearer {app.GITHUB_TOKEN}', 'Content-Type': 'application/json' } ) mock_logger.error.assert_not_called() def test_get_public_repos_error_response(mock_dependencies): """Test error handling when GitHub GraphQL API returns an error.""" mock_post = mock_dependencies['request_post'] mock_logger = mock_dependencies['logger'] mock_post.return_value.json.return_value = { 'errors': [{'message': 'Something went wrong'}] } with pytest.raises(Exception, match='GraphQL error'): app.get_public_repos() mock_logger.error.assert_called_once() assert 'Failed to fetch public repositories' in mock_logger.error.call_args[0][0] def test_get_public_repos_http_error_response(mock_dependencies): """Test error handling when GitHub API returns a non-2xx HTTP status (e.g. 401).""" mock_post = mock_dependencies['request_post'] mock_logger = mock_dependencies['logger'] mock_post.return_value = MagicMock( ok=False, status_code=401, reason='Unauthorized', **{'json.return_value': {'message': 'Bad credentials', 'status': '401'}} ) with pytest.raises(Exception, match='GitHub API returned 401 Unauthorized'): app.get_public_repos() mock_logger.error.assert_called_once() assert 'Failed to fetch public repositories' in mock_logger.error.call_args[0][0] def test_get_public_repos_missing_data_key(mock_dependencies): """Test error handling when a 2xx GitHub response unexpectedly omits 'data'.""" mock_post = mock_dependencies['request_post'] mock_logger = mock_dependencies['logger'] mock_post.return_value = MagicMock(ok=True, **{'json.return_value': {}}) with pytest.raises(Exception, match="missing 'data'"): app.get_public_repos() mock_logger.error.assert_called_once() assert 'Failed to fetch public repositories' in mock_logger.error.call_args[0][0] def test_get_public_repos_with_pagination(mock_dependencies, mock_repos): """Test fetching public repositories with paginated results.""" mock_post = mock_dependencies['request_post'] mock_logger = mock_dependencies['logger'] mock_post.side_effect = [ MagicMock(**{ 'json.return_value': { 'data': { 'organization': { 'repositories': { 'nodes': [mock_repos[0]], 'pageInfo': {'endCursor': 'cursor1', 'hasNextPage': True} } } } } }), MagicMock(**{ 'json.return_value': { 'data': { 'organization': { 'repositories': { 'nodes': [mock_repos[1]], 'pageInfo': {'endCursor': None, 'hasNextPage': False} } } } } }) ] repos = app.get_public_repos() assert len(repos) == 2 assert mock_post.call_count == 2 first_call = mock_post.call_args_list[0] second_call = mock_post.call_args_list[1] assert first_call.kwargs['json']['variables']['after'] is None assert second_call.kwargs['json']['variables']['after'] == 'cursor1' assert first_call.kwargs['headers']['Authorization'] == f'Bearer {app.GITHUB_TOKEN}' mock_logger.error.assert_not_called() def test_notify_success(mock_dependencies, mock_repos): """Test sending SES email notification for valid repositories.""" mock_ses = mock_dependencies['ses'] mock_logger = mock_dependencies['logger'] app.notify(mock_repos) mock_ses.send_email.assert_called_once() args, kwargs = mock_ses.send_email.call_args assert kwargs['sender'] == app.SES_EMAIL_SENDER assert 'repo1' in kwargs['message'] mock_logger.info.assert_any_call('SES email sent successfully.') mock_logger.error.assert_not_called() def test_notify_date_parse_failure(mock_dependencies, mock_bad_repo): """Test fallback to raw date string and warning log when date parsing fails.""" mock_ses = mock_dependencies['ses'] mock_logger = mock_dependencies['logger'] app.notify(mock_bad_repo) mock_logger.warning.assert_called_once() assert 'Could not parse date for repo bad-repo' in mock_logger.warning.call_args[0][0] mock_ses.send_email.assert_called_once() mock_logger.error.assert_not_called() def test_notify_ses_failure(mock_dependencies, mock_repos): """Test error handling and logging when SES email sending fails.""" mock_ses = mock_dependencies['ses'] mock_logger = mock_dependencies['logger'] mock_ses.send_email.side_effect = Exception('SES failed') with pytest.raises(Exception, match='SES failed'): app.notify(mock_repos) mock_logger.error.assert_called_once() assert 'SES email notification failed' in mock_logger.error.call_args[0][0]