#!/usr/bin/env python3 """ Unit tests for email Flask endpoints in api/app.py. Covers: GET /api/email/users POST /api/email/users DELETE /api/email/users/ GET /api/email/status GET /api/email/connectivity """ import sys import json import unittest from pathlib import Path from unittest.mock import patch, MagicMock api_dir = Path(__file__).parent.parent / 'api' sys.path.insert(0, str(api_dir)) from app import app class TestGetEmailUsers(unittest.TestCase): def setUp(self): app.config['TESTING'] = True self.client = app.test_client() @patch('app.email_manager') def test_get_users_returns_200_with_list(self, mock_em): mock_em.get_users.return_value = [ {'username': 'alice@cell', 'domain': 'cell'}, {'username': 'bob@cell', 'domain': 'cell'}, ] r = self.client.get('/api/email/users') self.assertEqual(r.status_code, 200) data = json.loads(r.data) self.assertIsInstance(data, list) self.assertEqual(len(data), 2) @patch('app.email_manager') def test_get_users_returns_empty_list_when_no_users(self, mock_em): mock_em.get_users.return_value = [] r = self.client.get('/api/email/users') self.assertEqual(r.status_code, 200) self.assertEqual(json.loads(r.data), []) @patch('app.email_manager') def test_get_users_returns_500_on_exception(self, mock_em): mock_em.get_users.side_effect = Exception('mailbox unreachable') r = self.client.get('/api/email/users') self.assertEqual(r.status_code, 500) self.assertIn('error', json.loads(r.data)) class TestCreateEmailUser(unittest.TestCase): def setUp(self): app.config['TESTING'] = True self.client = app.test_client() @patch('app.email_manager') def test_create_user_returns_200_on_success(self, mock_em): mock_em.create_email_user.return_value = True r = self.client.post( '/api/email/users', data=json.dumps({'username': 'alice', 'password': 'secret123'}), content_type='application/json', ) self.assertEqual(r.status_code, 200) data = json.loads(r.data) self.assertIn('created', data) @patch('app.email_manager') def test_create_user_calls_manager_with_username_and_password(self, mock_em): mock_em.create_email_user.return_value = True self.client.post( '/api/email/users', data=json.dumps({'username': 'alice', 'password': 'secret123'}), content_type='application/json', ) mock_em.create_email_user.assert_called_once() args = mock_em.create_email_user.call_args[0] self.assertEqual(args[0], 'alice') self.assertEqual(args[2], 'secret123') @patch('app.email_manager') def test_create_user_returns_400_when_username_missing(self, mock_em): r = self.client.post( '/api/email/users', data=json.dumps({'password': 'secret123'}), content_type='application/json', ) self.assertEqual(r.status_code, 400) self.assertIn('error', json.loads(r.data)) mock_em.create_email_user.assert_not_called() @patch('app.email_manager') def test_create_user_returns_400_when_password_missing(self, mock_em): r = self.client.post( '/api/email/users', data=json.dumps({'username': 'alice'}), content_type='application/json', ) self.assertEqual(r.status_code, 400) self.assertIn('error', json.loads(r.data)) mock_em.create_email_user.assert_not_called() @patch('app.email_manager') def test_create_user_returns_400_when_no_body(self, mock_em): r = self.client.post('/api/email/users') self.assertEqual(r.status_code, 400) self.assertIn('error', json.loads(r.data)) @patch('app.email_manager') def test_create_user_returns_500_on_exception(self, mock_em): mock_em.create_email_user.side_effect = Exception('SMTP config error') r = self.client.post( '/api/email/users', data=json.dumps({'username': 'alice', 'password': 'secret123'}), content_type='application/json', ) self.assertEqual(r.status_code, 500) self.assertIn('error', json.loads(r.data)) class TestDeleteEmailUser(unittest.TestCase): def setUp(self): app.config['TESTING'] = True self.client = app.test_client() @patch('app.email_manager') def test_delete_user_returns_200_on_success(self, mock_em): mock_em.delete_email_user.return_value = True r = self.client.delete('/api/email/users/alice') self.assertEqual(r.status_code, 200) data = json.loads(r.data) self.assertIn('deleted', data) @patch('app.email_manager') def test_delete_user_calls_manager_with_username(self, mock_em): mock_em.delete_email_user.return_value = True self.client.delete('/api/email/users/bob') mock_em.delete_email_user.assert_called_once() args = mock_em.delete_email_user.call_args[0] self.assertEqual(args[0], 'bob') @patch('app.email_manager') def test_delete_user_returns_500_on_exception(self, mock_em): mock_em.delete_email_user.side_effect = Exception('LDAP error') r = self.client.delete('/api/email/users/alice') self.assertEqual(r.status_code, 500) self.assertIn('error', json.loads(r.data)) class TestGetEmailStatus(unittest.TestCase): def setUp(self): app.config['TESTING'] = True self.client = app.test_client() @patch('app.email_manager') def test_get_status_returns_200_with_status_dict(self, mock_em): mock_em.get_status.return_value = { 'running': True, 'smtp_port': 25, 'imap_port': 993, } r = self.client.get('/api/email/status') self.assertEqual(r.status_code, 200) data = json.loads(r.data) self.assertIn('running', data) @patch('app.email_manager') def test_get_status_returns_500_on_exception(self, mock_em): mock_em.get_status.side_effect = Exception('container not found') r = self.client.get('/api/email/status') self.assertEqual(r.status_code, 500) self.assertIn('error', json.loads(r.data)) class TestEmailConnectivity(unittest.TestCase): def setUp(self): app.config['TESTING'] = True self.client = app.test_client() @patch('app.email_manager') def test_connectivity_returns_200_with_result(self, mock_em): mock_em.test_connectivity.return_value = { 'smtp': True, 'imap': True, 'latency_ms': 12, } r = self.client.get('/api/email/connectivity') self.assertEqual(r.status_code, 200) data = json.loads(r.data) self.assertIn('smtp', data) @patch('app.email_manager') def test_connectivity_returns_500_on_exception(self, mock_em): mock_em.test_connectivity.side_effect = Exception('timeout') r = self.client.get('/api/email/connectivity') self.assertEqual(r.status_code, 500) self.assertIn('error', json.loads(r.data)) if __name__ == '__main__': unittest.main()