Skip to content

Commit 5a1df75

Browse files
authored
feat: Add tox configuration for OpenStack standard testing (#100)
1 parent 405336f commit 5a1df75

File tree

6 files changed

+577
-2
lines changed

6 files changed

+577
-2
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ __pycache__/
77
*.so
88
.idea
99

10+
# macOS
11+
.DS_Store
12+
1013
# Distribution / packaging
1114
.Python
1215
build/

README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,76 @@ Get started quickly with the OpenStack MCP server using Claude Desktop
9595
}
9696
```
9797

98+
# Development
99+
100+
## Setup
101+
102+
This project supports both `uv` and `tox` for development and testing.
103+
104+
### Using uv (Fast Local Development)
105+
106+
```bash
107+
# Install dependencies (including dev and test groups)
108+
uv sync
109+
110+
# Run tests
111+
uv run --group test pytest
112+
113+
# Run linting
114+
uv run ruff check src tests
115+
116+
# Format code
117+
uv run ruff format src tests
118+
```
119+
120+
### Using tox (OpenStack Standard)
121+
122+
```bash
123+
# Install tox
124+
pip install tox
125+
# or
126+
uv tool install tox
127+
128+
# Run tests
129+
tox -e py3
130+
131+
# Run linting
132+
tox -e pep8
133+
134+
# Auto-format code
135+
tox -e format
136+
137+
# Generate coverage report
138+
tox -e cover
139+
140+
# Run arbitrary commands in virtualenv
141+
tox -e venv -- <command>
142+
143+
# Test on specific Python version
144+
tox -e py310 # or py311, py312, py313
145+
146+
# List all available environments
147+
tox list
148+
```
149+
150+
## Testing
151+
152+
The project includes comprehensive test coverage (85%+). Tests are located in the `tests/` directory.
153+
154+
```bash
155+
# Run all tests
156+
tox -e py3
157+
158+
# Run with coverage
159+
tox -e cover
160+
161+
# Run with debugger
162+
tox -e debug
163+
164+
# Run specific test file
165+
tox -e py3 -- tests/tools/test_compute_tools.py
166+
```
167+
98168
# Contributing
99169
Contributions are welcome! Please see the [CONTRIBUTING](CONTRIBUTING.rst) file for details on how to contribute to this project.
100170

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ dev = [
1818
"ruff>=0.12.5",
1919
"pre-commit>=4.2.0",
2020
"setuptools-scm>=9.2.0",
21-
]
21+
]
2222
test = [
2323
"pytest>=8.4.1",
24+
"coverage>=7.0",
25+
"pytest-cov>=6.0.0",
2426
]
2527

2628

tests/README.md

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
# Test Suite Documentation
2+
3+
This directory contains comprehensive unit tests for the OpenStack MCP Server project.
4+
5+
## Overview
6+
7+
- **Total Tests**: 156
8+
- **Test Classes**: 5
9+
- **Test Coverage**: 85%+
10+
- **Test Framework**: pytest
11+
12+
## Directory Structure
13+
14+
```
15+
tests/
16+
├── conftest.py # Shared fixtures and configuration
17+
├── tools/
18+
│ ├── test_block_storage_tools.py # Cinder (Block Storage) tests
19+
│ ├── test_compute_tools.py # Nova (Compute) tests
20+
│ ├── test_identity_tools.py # Keystone (Identity) tests
21+
│ ├── test_image_tools.py # Glance (Image) tests
22+
│ └── test_network_tools.py # Neutron (Network) tests
23+
└── README.md # This file
24+
```
25+
26+
## Running Tests
27+
28+
### Run All Tests
29+
30+
```bash
31+
# Using uv
32+
uv run --group test pytest
33+
34+
# Using tox
35+
tox -e py3
36+
```
37+
38+
### Run Specific Test File
39+
40+
```bash
41+
uv run --group test pytest tests/tools/test_compute_tools.py
42+
```
43+
44+
### Run Tests by Marker
45+
46+
```bash
47+
# Run only unit tests
48+
uv run --group test pytest -m unit
49+
50+
# Run only integration tests
51+
uv run --group test pytest -m integration
52+
53+
# Run only slow tests
54+
uv run --group test pytest -m slow
55+
```
56+
57+
### Run with Verbose Output
58+
59+
```bash
60+
uv run --group test pytest -v
61+
```
62+
63+
### Run with Coverage
64+
65+
```bash
66+
# Using tox
67+
tox -e cover
68+
69+
# Generate HTML coverage report
70+
uv run --group test pytest --cov=openstack_mcp_server --cov-report=html
71+
```
72+
73+
## Test Markers
74+
75+
Tests are categorized using pytest markers for better organization:
76+
77+
- `@pytest.mark.unit` - Unit tests that don't require external dependencies
78+
- `@pytest.mark.integration` - Integration tests that may require OpenStack services
79+
- `@pytest.mark.slow` - Tests that take longer than 1 second to run
80+
81+
## Fixtures
82+
83+
### Shared Fixtures (conftest.py)
84+
85+
#### `mock_openstack_conn_factory`
86+
Factory fixture to create mock OpenStack connections for any module.
87+
88+
**Usage:**
89+
```python
90+
def test_something(mock_openstack_conn_factory):
91+
mock_conn = mock_openstack_conn_factory('compute')
92+
# Use mock_conn in your test
93+
```
94+
95+
**Available modules:**
96+
- `compute` - Nova compute service
97+
- `image` - Glance image service
98+
- `identity` - Keystone identity service
99+
- `network` - Neutron network service
100+
- `block_storage` - Cinder block storage service
101+
- `base` - Base module
102+
103+
#### Legacy Fixtures (Deprecated)
104+
The following fixtures are maintained for backward compatibility but will be removed in future versions:
105+
- `mock_get_openstack_conn` (use `mock_openstack_conn_factory('compute')` instead)
106+
- `mock_get_openstack_conn_image` (use `mock_openstack_conn_factory('image')` instead)
107+
- `mock_get_openstack_conn_identity` (use `mock_openstack_conn_factory('identity')` instead)
108+
- `mock_openstack_connect_network` (use `mock_openstack_conn_factory('network')` instead)
109+
- `mock_get_openstack_conn_block_storage` (use `mock_openstack_conn_factory('block_storage')` instead)
110+
- `mock_openstack_base` (use `mock_openstack_conn_factory('base')` instead)
111+
112+
## Test Coverage by Module
113+
114+
| Module | Tests | Coverage | Notes |
115+
|--------|-------|----------|-------|
116+
| **Compute Tools** | 40 | 100% | Nova server and flavor management |
117+
| **Image Tools** | 10 | 89% | Glance image operations |
118+
| **Identity Tools** | 34 | 86% | Keystone users, projects, roles |
119+
| **Network Tools** | 48 | 85% | Neutron networks, ports, routers, security groups |
120+
| **Block Storage Tools** | 24 | 98% | Cinder volume management |
121+
122+
## Writing New Tests
123+
124+
### Test Structure Standard
125+
126+
All test files should follow this structure:
127+
128+
```python
129+
"""Unit tests for [ModuleName] class.
130+
131+
This module contains comprehensive tests for the [ModuleName] class,
132+
which provides functionality for [brief description].
133+
134+
Test Coverage:
135+
- [Feature 1]
136+
- [Feature 2]
137+
- [Feature 3]
138+
"""
139+
140+
import pytest
141+
from openstack_mcp_server.tools.[module] import [Class]
142+
143+
144+
@pytest.mark.unit
145+
class Test[ClassName]:
146+
"""Test cases for [ClassName] class."""
147+
148+
def test_[feature]_success(self, mock_get_openstack_conn_[module]):
149+
"""Test [feature description].
150+
151+
Verifies that [method_name]() correctly [what it does].
152+
"""
153+
# Arrange
154+
mock_conn = mock_get_openstack_conn_[module]
155+
# ... setup mocks
156+
157+
# Act
158+
result = [Class]().[method]()
159+
160+
# Assert
161+
assert result == expected_output
162+
mock_conn.[service].[method].assert_called_once()
163+
```
164+
165+
### Docstring Guidelines
166+
167+
1. **Module-level docstring**: Describe what the test file covers
168+
2. **Class-level docstring**: Brief description of the class being tested
169+
3. **Test method docstring**:
170+
- First line: What the test does
171+
- Second paragraph: What it verifies
172+
173+
### Example Test
174+
175+
```python
176+
def test_get_server_success(self, mock_get_openstack_conn):
177+
"""Test retrieving a specific server by ID.
178+
179+
Verifies that get_server() correctly retrieves and returns
180+
a Server object for a given server ID.
181+
"""
182+
mock_conn = mock_get_openstack_conn
183+
mock_server = {"id": "server-123", "name": "web-01"}
184+
mock_conn.compute.get_server.return_value = mock_server
185+
186+
result = ComputeTools().get_server("server-123")
187+
188+
assert result.id == "server-123"
189+
assert result.name == "web-01"
190+
```
191+
192+
## Mock Data Guidelines
193+
194+
### Factory Methods
195+
196+
Use factory methods to create consistent mock data:
197+
198+
```python
199+
@staticmethod
200+
def server_factory(**overrides):
201+
"""Factory method to create mock server data.
202+
203+
Args:
204+
**overrides: Key-value pairs to override default values
205+
206+
Returns:
207+
Dictionary with mock server data
208+
"""
209+
defaults = {
210+
"id": str(uuid.uuid4()),
211+
"name": "test-server",
212+
"status": "ACTIVE",
213+
# ... other fields
214+
}
215+
defaults.update(overrides)
216+
return defaults
217+
```
218+
219+
## Common Test Patterns
220+
221+
### Testing Success Cases
222+
223+
```python
224+
def test_operation_success(self, fixture):
225+
"""Test successful operation."""
226+
# Setup mock
227+
mock_conn = fixture
228+
mock_conn.service.method.return_value = expected_data
229+
230+
# Execute
231+
result = ToolClass().method()
232+
233+
# Verify
234+
assert result == expected_output
235+
mock_conn.service.method.assert_called_once()
236+
```
237+
238+
### Testing with Filters
239+
240+
```python
241+
def test_get_with_filter(self, fixture):
242+
"""Test filtering by specific criteria."""
243+
mock_conn = fixture
244+
mock_conn.service.list.return_value = [filtered_item]
245+
246+
result = ToolClass().get_items(status="active")
247+
248+
mock_conn.service.list.assert_called_once_with(status="active")
249+
```
250+
251+
### Testing Empty Results
252+
253+
```python
254+
def test_get_empty_list(self, fixture):
255+
"""Test when no items exist."""
256+
mock_conn = fixture
257+
mock_conn.service.list.return_value = []
258+
259+
result = ToolClass().get_items()
260+
261+
assert result == []
262+
```
263+
264+
## Troubleshooting
265+
266+
### Tests Fail After Changes
267+
268+
1. Run tests with verbose output: `pytest -v`
269+
2. Check if fixtures need updating
270+
3. Verify mock data structure matches response models
271+
272+
### Import Errors
273+
274+
Make sure you're in the project root and run:
275+
```bash
276+
uv sync --group test
277+
```
278+
279+
### Marker Not Found
280+
281+
If you get "unknown marker" warnings, ensure `pyproject.toml` has the markers configured in `[tool.pytest.ini_options]`.
282+
283+
## Contributing
284+
285+
When adding new tests:
286+
287+
1. Follow the test structure standard
288+
2. Add appropriate markers (`@pytest.mark.unit`, etc.)
289+
3. Write comprehensive docstrings
290+
4. Use factory methods for mock data
291+
5. Ensure tests are independent and can run in any order
292+
6. Maintain or improve coverage (currently 85%+)
293+
294+
## Resources
295+
296+
- [pytest documentation](https://docs.pytest.org/)
297+
- [unittest.mock documentation](https://docs.python.org/3/library/unittest.mock.html)
298+
- [OpenStack SDK documentation](https://docs.openstack.org/openstacksdk/)

0 commit comments

Comments
 (0)