Skip to content

Commit b96b208

Browse files
Merge feature/improve-test-coverage: Test coverage and documentation improvements
✨ Major additions: - 142 comprehensive unit and integration tests - 75.37% test coverage (exceeds 75% target) - MIT LICENSE file added - README restructured to be feature-first (not test-heavy) - CONTRIBUTING.md created with development guide - Clarified REST API vs official MCP protocol distinction 📊 Test Results: - 142 tests passing - 75.37% coverage - All critical modules well-tested: - Connection pool: 100% - Header parser: 100% - Scope validator: 89% - OdooClient: 88% - Tool implementations: 82% 🎯 What's included: - tests/unit/ (8 test files, 130+ tests) - tests/integration/ (3 test files, 10+ tests) - CONTRIBUTING.md: Complete development guide - README.md: Feature-focused documentation - LICENSE: MIT license terms - pytest.ini: 75% coverage threshold 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
2 parents 53142e3 + 6cfbcec commit b96b208

File tree

12 files changed

+2252
-389
lines changed

12 files changed

+2252
-389
lines changed

CONTRIBUTING.md

Lines changed: 283 additions & 219 deletions
Large diffs are not rendered by default.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Christopher Igweze
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 43 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,21 @@
33
[![codecov](https://codecov.io/gh/christopher-igweze/python-odoo-mcp/branch/main/graph/badge.svg)](https://codecov.io/gh/christopher-igweze/python-odoo-mcp)
44
[![Tests](https://github.com/christopher-igweze/python-odoo-mcp/workflows/Tests/badge.svg)](https://github.com/christopher-igweze/python-odoo-mcp/actions)
55

6-
Multi-tenant Odoo XML-RPC MCP server with scope-based access control, connection pooling, and HTTP Stream transport.
6+
HTTP REST API for Odoo automation with encrypted API keys, multi-tenant scope isolation, and connection pooling. Built for n8n, webhooks, and custom integrations.
77

88
**Repository:** https://github.com/christopher-igweze/python-odoo-mcp
99

10+
## Features
11+
12+
**Multi-tenant** - Different users, different Odoo instances, same server
13+
**Scope-based access control** - Fine-grained R/W/D permissions per model
14+
**Encrypted API keys** - Fernet encryption for secure key storage
15+
**Connection pooling** - Caches authenticated sessions with TTL
16+
**n8n ready** - Drop-in HTTP integration for automation workflows
17+
**Complete CRUD** - Search, read, create, write, delete any Odoo model
18+
**Error handling** - Clear permission, auth, and connection errors
19+
**Async support** - Full async/await for high concurrency
20+
1021
## Quick Start
1122

1223
### Local Development
@@ -129,38 +140,26 @@ Permissions:
129140

130141
## Architecture
131142

143+
**Request Flow:**
144+
132145
```
133-
┌─ n8n ─────────────────────────────────┐
134-
│ HTTP Request with X-Auth-Credentials │
135-
└────────────┬────────────────────────────┘
136-
│ (base64 JSON with credentials + scope)
137-
138-
┌─ Python Odoo MCP ─────────────────────┐
139-
│ ┌──────────────────────────────────┐ │
140-
│ │ Authentication & Scope Validator │ │
141-
│ └──────────┬───────────────────────┘ │
142-
│ │ │
143-
│ ┌──────────▼───────────────────────┐ │
144-
│ │ Connection Pool (Scope-Aware) │ │
145-
│ │ Caches connections per user+scope│ │
146-
│ └──────────┬───────────────────────┘ │
147-
│ │ │
148-
│ ┌──────────▼───────────────────────┐ │
149-
│ │ Odoo Client │ │
150-
│ │ - Validates scope before every op│ │
151-
│ │ - Uses pooled connections │ │
152-
│ └──────────┬───────────────────────┘ │
153-
│ │ │
154-
│ ┌──────────▼───────────────────────┐ │
155-
│ │ Tools (search, read, write, etc) │ │
156-
│ │ Execute on Odoo via XML-RPC │ │
157-
│ └──────────────────────────────────┘ │
158-
└─────────────────────────────────────────┘
159-
160-
▼ (XML-RPC)
161-
Odoo Instance
146+
HTTP Request (X-API-Key header)
147+
148+
Authentication & Scope Validation
149+
150+
Connection Pool (scope-aware caching)
151+
152+
Odoo Client (validates permissions, executes operations)
153+
154+
Odoo Instance (XML-RPC)
162155
```
163156

157+
**Key Components:**
158+
- **HTTP Server** - FastAPI REST API on port 3000
159+
- **Connection Pool** - Caches authenticated Odoo sessions with TTL and scope isolation
160+
- **Scope Validator** - Enforces R/W/D permissions per model
161+
- **Odoo Client** - XML-RPC wrapper with automatic connection pooling
162+
164163
## Environment Variables
165164

166165
```bash
@@ -191,134 +190,12 @@ PORT=3000
191190
6. Get public URL from Coolify
192191
7. Use in n8n: `https://your-mcp-xxx.coolify.io/tools/call`
193192

194-
## Testing
195-
196-
### Unit and Integration Tests
193+
## Testing & Development
197194

198-
The project includes comprehensive unit and integration tests using pytest:
199-
200-
```bash
201-
# Run all tests
202-
pytest tests/
203-
204-
# Run with coverage report
205-
pytest tests/ --cov=src --cov-report=html --cov-report=term-missing
206-
207-
# Run specific test file
208-
pytest tests/unit/test_encryption.py -v
209-
210-
# Run tests matching pattern
211-
pytest -k "scope_validator" -v
212-
213-
# Run with markers
214-
pytest -m unit # Unit tests only
215-
pytest -m integration # Integration tests only
216-
pytest -m slow # Slow/e2e tests
217-
```
218-
219-
### Test Structure
220-
221-
```
222-
tests/
223-
├── unit/ # Unit tests (no external dependencies)
224-
│ ├── test_config.py # Config management & encryption key validation
225-
│ ├── test_encryption.py # Credential encryption/decryption
226-
│ └── test_scope_validator.py # Scope parsing and permission checking
227-
├── integration/ # Integration tests (with app instance)
228-
│ ├── test_auth_endpoints.py # /auth/generate and /auth/validate
229-
│ ├── test_health.py # Health check endpoints
230-
│ └── test_tools_endpoints.py # /tools/list and /tools/call
231-
└── conftest.py # Shared pytest fixtures
232-
```
233-
234-
### Health Check
235-
236-
```bash
237-
curl http://localhost:3000/health
238-
```
239-
240-
### List Tools
241-
242-
```bash
243-
curl -X POST http://localhost:3000/tools/list
244-
```
245-
246-
### Test with Script
247-
248-
```bash
249-
./test_server.sh
250-
```
251-
252-
### Test in n8n
253-
254-
Import `test_n8n.json` workflow for examples.
255-
256-
### Coverage Goals
257-
258-
Current coverage: 46% (61 tests passing)
259-
- **High coverage:** Authentication, encryption, scope validation (89%), connection pool management
260-
- **Low coverage:** Tool implementations, Odoo client methods (require live Odoo instance)
261-
262-
To improve coverage:
263-
1. Run tests against real Odoo instance (see Phase 8 in development history)
264-
2. Mock Odoo responses in unit tests
265-
3. Use testcontainers for Odoo in CI/CD
266-
267-
### Coverage Monitoring with Codecov
268-
269-
This project uses [Codecov](https://codecov.io/) for continuous coverage tracking and reporting.
270-
271-
**Coverage Tracking:**
272-
- View detailed coverage dashboard: https://codecov.io/gh/christopher-igweze/python-odoo-mcp
273-
- Coverage reports generated automatically on every push via GitHub Actions
274-
- Pull requests show coverage impact (lines added/removed/changed)
275-
- Coverage history graphs track improvements over time
276-
277-
**Current Status:**
278-
- Overall coverage: 46% (target: 75%+)
279-
- Authentication & encryption: 89% ✅
280-
- Scope validation: 89% ✅
281-
- Tools & Odoo client: 15-29% (improving with mocked tests)
282-
283-
**Understanding Coverage Reports:**
284-
1. **Line Coverage** - Percentage of source code lines executed during tests
285-
2. **Branch Coverage** - All code paths (if/else) tested
286-
3. **Uncovered Lines** - Listed with file paths for targeting improvement
287-
4. **Trending** - See if coverage increases or decreases with each commit
288-
289-
**Local Coverage Reports:**
290-
291-
Generate and view HTML coverage reports locally:
292-
293-
```bash
294-
# Run tests with coverage
295-
pytest tests/ --cov=src --cov-report=html --cov-report=term-missing
296-
297-
# View in browser
298-
open htmlcov/index.html # macOS
299-
# or
300-
xdg-open htmlcov/index.html # Linux
301-
```
302-
303-
**For Contributors:**
304-
305-
When implementing new features:
306-
1. Write tests alongside implementation
307-
2. Aim for 80%+ coverage on new code
308-
3. Run `pytest` to see coverage before pushing
309-
4. Codecov will comment on PRs showing impact
310-
5. Target overall coverage of 75%+
311-
312-
## Features
313-
314-
**Multi-tenant** - Different users, different Odoo instances, same server
315-
**Scope-based access control** - Fine-grained R/W/D permissions per model
316-
**Connection pooling** - Caches authenticated sessions, scope-aware
317-
**HTTP Stream MCP** - Works with n8n's official MCP node
318-
**Stateless** - Each request is self-contained
319-
**Error handling** - Clear permission errors, auth errors, connection errors
320-
**Thread-safe** - RLock for pool access
321-
**Async** - Full async/await support
195+
See [CONTRIBUTING.md](CONTRIBUTING.md) for:
196+
- Running tests with pytest
197+
- Test coverage information
198+
- Development workflow and micro-commits
322199

323200
## Error Handling
324201

@@ -331,25 +208,22 @@ All errors return `{"error": "...", "status": "..."}`:
331208
- `tool_not_found` - Unknown tool name
332209
- `odoo_error` - Odoo RPC error
333210

334-
## Development
211+
## Note: REST API vs MCP Protocol
335212

336-
Micro-commit workflow with glassbear format:
213+
This project exposes **MCP-style tools via HTTP REST API**, which is different from the official MCP protocol:
337214

338-
```bash
339-
# Read the requirements
340-
cat requirements.txt
215+
- **This server:** REST API with HTTP endpoints (`/tools/list`, `/tools/call`)
216+
- **Official MCP protocol:** Used by Claude Desktop, Cursor, and other AI assistants via stdio or websockets
341217

342-
# Make a change, test it
343-
# Then commit
344-
git commit -m "🔧 fix(scope): handle wildcard override"
345-
git push origin main
346-
```
218+
For AI assistant integration, see the excellent [ivnvxd/mcp-server-odoo](https://github.com/ivnvxd/mcp-server-odoo) which uses true MCP protocol.
347219

348-
See `.git/logs` for commit history.
220+
This REST API approach is ideal for **n8n, webhooks, and automation platforms** where HTTP is more practical.
349221

350222
## License
351223

352-
This project is open source.
224+
MIT License - see [LICENSE](LICENSE) for details.
225+
226+
This means you can freely use, modify, and distribute this software as long as you include the license notice.
353227

354228
## Support
355229

pytest.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ addopts =
1111
--cov=src
1212
--cov-report=html
1313
--cov-report=term-missing
14-
--cov-fail-under=40
14+
--cov-fail-under=75
1515

1616
markers =
1717
unit: Unit tests

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ cryptography==41.0.7
66
pytest==7.4.3
77
pytest-asyncio==0.21.1
88
pytest-cov==4.1.0
9+
pytest-mock==3.12.0
910
httpx==0.25.2

tests/unit/test_config.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Unit tests for configuration module"""
22

3+
import os
34
import pytest
45
from cryptography.fernet import Fernet
56

@@ -86,3 +87,55 @@ def test_default_connection_pool_ttl(self):
8687

8788
# Default should be 60 minutes
8889
assert config.CONNECTION_POOL_TTL_MINUTES >= 1
90+
91+
92+
@pytest.mark.unit
93+
class TestConfigValidationErrors:
94+
"""Test configuration validation errors"""
95+
96+
def test_config_validate_with_invalid_log_level(self, monkeypatch):
97+
"""Test config validation fails with invalid LOG_LEVEL"""
98+
# Create a Config instance and manually set invalid log level
99+
monkeypatch.setenv("LOG_LEVEL", "INVALID")
100+
101+
# Re-import to get fresh config
102+
import importlib
103+
import src.config
104+
importlib.reload(src.config)
105+
106+
from src.config import config
107+
108+
# Validation should fail
109+
with pytest.raises(ValueError):
110+
config.validate()
111+
112+
def test_config_validate_with_negative_ttl(self, monkeypatch):
113+
"""Test config validation fails with negative TTL"""
114+
# Set negative TTL
115+
monkeypatch.setenv("CONNECTION_POOL_TTL_MINUTES", "-1")
116+
117+
# Re-import to get fresh config
118+
import importlib
119+
import src.config
120+
importlib.reload(src.config)
121+
122+
from src.config import config
123+
124+
# Validation should fail
125+
with pytest.raises(ValueError):
126+
config.validate()
127+
128+
def test_config_validate_with_zero_ttl(self, monkeypatch):
129+
"""Test config validation fails with zero TTL"""
130+
monkeypatch.setenv("CONNECTION_POOL_TTL_MINUTES", "0")
131+
132+
# Re-import to get fresh config
133+
import importlib
134+
import src.config
135+
importlib.reload(src.config)
136+
137+
from src.config import config
138+
139+
# Validation should fail
140+
with pytest.raises(ValueError):
141+
config.validate()

0 commit comments

Comments
 (0)