|
| 1 | +# Microsoft 365 RAG Agent Tests |
| 2 | + |
| 3 | +This directory contains comprehensive pytest-based tests for the Microsoft 365 RAG Agent. |
| 4 | + |
| 5 | +## Test Structure |
| 6 | + |
| 7 | +### Test Files |
| 8 | + |
| 9 | +- **`test_teams_components.py`** - Tests for Teams UI components and Adaptive Cards |
| 10 | +- **`test_teams_response_adapter.py`** - Tests for Teams response formatting and adaptation |
| 11 | +- **`test_teams_handler.py`** - Tests for Teams message handling and adaptive card actions |
| 12 | + |
| 13 | +### Test Categories |
| 14 | + |
| 15 | +- **Unit Tests** - Test individual components in isolation with mocks |
| 16 | +- **Integration Tests** - Test component interactions (marked with `@pytest.mark.integration`) |
| 17 | +- **Async Tests** - Test asynchronous functionality with `@pytest.mark.asyncio` |
| 18 | + |
| 19 | +## Running Tests |
| 20 | + |
| 21 | +### Prerequisites |
| 22 | + |
| 23 | +Install the required testing dependencies: |
| 24 | + |
| 25 | +```bash |
| 26 | +# Using system packages (Ubuntu/Debian) |
| 27 | +sudo apt install python3-pytest python3-pytest-asyncio python3-pytest-cov python3-pytest-mock |
| 28 | + |
| 29 | +# Or using pip (if virtual environment is available) |
| 30 | +pip install pytest pytest-asyncio pytest-cov pytest-mock |
| 31 | +``` |
| 32 | + |
| 33 | +### Running All Tests |
| 34 | + |
| 35 | +```bash |
| 36 | +# Run all tests |
| 37 | +python3 -m pytest tests/ -v |
| 38 | + |
| 39 | +# Run with coverage |
| 40 | +python3 -m pytest tests/ --cov=. --cov-report=html |
| 41 | + |
| 42 | +# Run specific test file |
| 43 | +python3 -m pytest tests/test_teams_components.py -v |
| 44 | + |
| 45 | +# Run specific test |
| 46 | +python3 -m pytest tests/test_teams_components.py::TestTeamsComponents::test_create_welcome_card -v |
| 47 | +``` |
| 48 | + |
| 49 | +### Test Configuration |
| 50 | + |
| 51 | +The tests use `pytest.ini` for configuration: |
| 52 | + |
| 53 | +```ini |
| 54 | +[tool:pytest] |
| 55 | +testpaths = tests |
| 56 | +python_files = test_*.py |
| 57 | +python_classes = Test* |
| 58 | +python_functions = test_* |
| 59 | +addopts = |
| 60 | + -v |
| 61 | + --tb=short |
| 62 | + --strict-markers |
| 63 | + --disable-warnings |
| 64 | + --asyncio-mode=auto |
| 65 | +markers = |
| 66 | + integration: marks tests as integration tests (deselect with '-m "not integration"') |
| 67 | + unit: marks tests as unit tests |
| 68 | + slow: marks tests as slow running |
| 69 | +``` |
| 70 | + |
| 71 | +## Test Design Principles |
| 72 | + |
| 73 | +### 1. **No External Dependencies** |
| 74 | +- All tests use mocks instead of hitting real Azure services |
| 75 | +- No network calls or external API dependencies |
| 76 | +- Tests run fast and reliably in any environment |
| 77 | + |
| 78 | +### 2. **Comprehensive Coverage** |
| 79 | +- Test all public methods and edge cases |
| 80 | +- Test error handling and exception scenarios |
| 81 | +- Test both success and failure paths |
| 82 | + |
| 83 | +### 3. **Proper Async Testing** |
| 84 | +- Use `@pytest.mark.asyncio` for async test methods |
| 85 | +- Properly await async methods in tests |
| 86 | +- Mock async dependencies correctly |
| 87 | + |
| 88 | +### 4. **Mock Strategy** |
| 89 | +- Mock external dependencies (RAG service, auth service, etc.) |
| 90 | +- Use `unittest.mock.Mock` and `unittest.mock.patch` |
| 91 | +- Create realistic mock data that matches expected interfaces |
| 92 | + |
| 93 | +## Example Test Structure |
| 94 | + |
| 95 | +```python |
| 96 | +import pytest |
| 97 | +from unittest.mock import Mock, patch |
| 98 | +from components.teams_components import TeamsComponents |
| 99 | + |
| 100 | +class TestTeamsComponents: |
| 101 | + """Test cases for TeamsComponents.""" |
| 102 | + |
| 103 | + def test_create_welcome_card(self): |
| 104 | + """Test welcome card creation.""" |
| 105 | + card = TeamsComponents.create_welcome_card() |
| 106 | + |
| 107 | + assert card["type"] == "AdaptiveCard" |
| 108 | + assert card["version"] == "1.4" |
| 109 | + assert len(card["body"]) > 0 |
| 110 | + assert len(card["actions"]) > 0 |
| 111 | + |
| 112 | + @pytest.mark.asyncio |
| 113 | + async def test_async_method(self, mock_dependency): |
| 114 | + """Test async method with mocked dependency.""" |
| 115 | + with patch.object(mock_dependency, 'method', return_value="test"): |
| 116 | + result = await some_async_method() |
| 117 | + assert result == "expected" |
| 118 | +``` |
| 119 | + |
| 120 | +## Mock Data Patterns |
| 121 | + |
| 122 | +### RAG Response Mock |
| 123 | +```python |
| 124 | +mock_rag_response = RAGResponse( |
| 125 | + answer="Test response", |
| 126 | + sources=[{"title": "Source 1", "url": "https://example.com"}], |
| 127 | + citations=["Citation 1"], |
| 128 | + thoughts=[{"title": "Thought 1", "description": "Description 1"}], |
| 129 | + token_usage={"total_tokens": 100}, |
| 130 | + model_info={"model": "gpt-4"} |
| 131 | +) |
| 132 | +``` |
| 133 | + |
| 134 | +### Turn Context Mock |
| 135 | +```python |
| 136 | +class MockTurnContext: |
| 137 | + def __init__(self, activity: Activity): |
| 138 | + self.activity = activity |
| 139 | + self.channel_id = "msteams" |
| 140 | + self.conversation = activity.conversation |
| 141 | + self.from_property = activity.from_property |
| 142 | + self.recipient = Mock() |
| 143 | + self.recipient.id = "bot1" |
| 144 | +``` |
| 145 | + |
| 146 | +## Continuous Integration |
| 147 | + |
| 148 | +These tests are designed to run in CI/CD pipelines: |
| 149 | + |
| 150 | +- No external dependencies or network calls |
| 151 | +- Fast execution (all tests complete in ~1-2 seconds) |
| 152 | +- Reliable and deterministic results |
| 153 | +- Proper error reporting and logging |
| 154 | + |
| 155 | +## Coverage Goals |
| 156 | + |
| 157 | +- **Unit Tests**: 100% coverage of core business logic |
| 158 | +- **Integration Tests**: Cover all major component interactions |
| 159 | +- **Error Handling**: Test all exception scenarios |
| 160 | +- **Edge Cases**: Test boundary conditions and unusual inputs |
| 161 | + |
| 162 | +## Debugging Tests |
| 163 | + |
| 164 | +### Verbose Output |
| 165 | +```bash |
| 166 | +python3 -m pytest tests/ -v -s |
| 167 | +``` |
| 168 | + |
| 169 | +### Stop on First Failure |
| 170 | +```bash |
| 171 | +python3 -m pytest tests/ -x |
| 172 | +``` |
| 173 | + |
| 174 | +### Run Specific Test with Debug |
| 175 | +```bash |
| 176 | +python3 -m pytest tests/test_teams_components.py::TestTeamsComponents::test_create_welcome_card -v -s --tb=long |
| 177 | +``` |
| 178 | + |
| 179 | +### Coverage Report |
| 180 | +```bash |
| 181 | +python3 -m pytest tests/ --cov=. --cov-report=html |
| 182 | +# Open htmlcov/index.html in browser |
| 183 | +``` |
0 commit comments