mirror of
https://github.com/latinogino/dolibarr-mcp.git
synced 2026-04-12 05:45:35 +02:00
146 lines
5.3 KiB
Python
146 lines
5.3 KiB
Python
"""Integration-style tests for creating minimal Dolibarr entities."""
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Dict, Tuple
|
|
|
|
import pytest
|
|
import pytest_asyncio
|
|
from aiohttp import web
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "src"))
|
|
|
|
from dolibarr_mcp import Config, DolibarrClient
|
|
|
|
|
|
@pytest_asyncio.fixture
|
|
async def dolibarr_api_server(unused_tcp_port: int) -> Tuple[str, Dict, Dict]:
|
|
"""Spin up a lightweight fake Dolibarr API for integration-style tests."""
|
|
created: Dict[str, Dict[int, Dict]] = {"products": {}, "projects": {}}
|
|
statuses: Dict[str, list] = {"products": [], "projects": []}
|
|
|
|
async def create_product(request: web.Request) -> web.Response:
|
|
data = await request.json()
|
|
product_id = len(created["products"]) + 1
|
|
created["products"][product_id] = data
|
|
statuses["products"].append(201)
|
|
return web.json_response({"id": product_id, "ref": data.get("ref")}, status=201)
|
|
|
|
async def delete_product(request: web.Request) -> web.Response:
|
|
product_id = int(request.match_info["product_id"])
|
|
created["products"].pop(product_id, None)
|
|
return web.json_response({"success": True})
|
|
|
|
async def create_project(request: web.Request) -> web.Response:
|
|
data = await request.json()
|
|
project_id = len(created["projects"]) + 1
|
|
created["projects"][project_id] = data
|
|
statuses["projects"].append(201)
|
|
return web.json_response({"id": project_id, "ref": data.get("ref")}, status=201)
|
|
|
|
async def delete_project(request: web.Request) -> web.Response:
|
|
project_id = int(request.match_info["project_id"])
|
|
created["projects"].pop(project_id, None)
|
|
return web.json_response({"success": True})
|
|
|
|
app = web.Application()
|
|
app.router.add_post("/api/index.php/products", create_product)
|
|
app.router.add_delete("/api/index.php/products/{product_id}", delete_product)
|
|
app.router.add_post("/api/index.php/projects", create_project)
|
|
app.router.add_delete("/api/index.php/projects/{project_id}", delete_project)
|
|
|
|
runner = web.AppRunner(app)
|
|
await runner.setup()
|
|
port = unused_tcp_port
|
|
site = web.TCPSite(runner, "127.0.0.1", port)
|
|
await site.start()
|
|
|
|
base_url = f"http://127.0.0.1:{port}/api/index.php"
|
|
|
|
try:
|
|
yield base_url, created, statuses
|
|
finally:
|
|
await runner.cleanup()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_minimal_product_returns_created_id_and_ref(dolibarr_api_server):
|
|
"""POST minimal product payload and confirm 201 + echoed ref."""
|
|
base_url, created, statuses = dolibarr_api_server
|
|
config = Config(dolibarr_url=base_url, dolibarr_api_key="test-key")
|
|
|
|
async with DolibarrClient(config) as client:
|
|
payload = {
|
|
"ref": "FREELANCE_HOUR_TEST",
|
|
"label": "Freelance hourly rate test — Gino test",
|
|
"type": "service",
|
|
"price": 110.00,
|
|
"tva_tx": 19.0,
|
|
}
|
|
|
|
product_id = await client.create_product(payload)
|
|
|
|
assert statuses["products"][-1] == 201
|
|
assert product_id == 1
|
|
assert created["products"][product_id]["ref"] == payload["ref"]
|
|
|
|
cleanup_response = await client.delete_product(product_id)
|
|
assert cleanup_response.get("success") is True
|
|
assert product_id not in created["products"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_minimal_project_returns_created_id_and_ref(dolibarr_api_server):
|
|
"""POST minimal project payload and confirm 201 + echoed ref."""
|
|
base_url, created, statuses = dolibarr_api_server
|
|
config = Config(dolibarr_url=base_url, dolibarr_api_key="test-key")
|
|
|
|
async with DolibarrClient(config) as client:
|
|
payload = {
|
|
"ref": "PERCISION_TEST_PROJECT",
|
|
"name": "Percision test — Project test",
|
|
"socid": 8,
|
|
}
|
|
|
|
project_id = await client.create_project(payload)
|
|
|
|
assert statuses["projects"][-1] == 201
|
|
assert project_id == 1
|
|
assert created["projects"][project_id]["ref"] == payload["ref"]
|
|
|
|
cleanup_response = await client.delete_project(project_id)
|
|
assert cleanup_response.get("success") is True
|
|
assert project_id not in created["projects"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_autogenerated_reference_uses_prefix_when_enabled(dolibarr_api_server):
|
|
"""When auto-generation is enabled, ensure generated refs include the prefix."""
|
|
base_url, created, statuses = dolibarr_api_server
|
|
config = Config(
|
|
dolibarr_url=base_url,
|
|
dolibarr_api_key="test-key",
|
|
allow_ref_autogen=True,
|
|
ref_autogen_prefix="AUTO",
|
|
)
|
|
|
|
async with DolibarrClient(config) as client:
|
|
payload = {
|
|
"name": "Percision test — Project test",
|
|
"socid": 8,
|
|
}
|
|
|
|
project_id = await client.create_project(payload)
|
|
|
|
generated_ref = created["projects"][project_id]["ref"]
|
|
|
|
assert statuses["projects"][-1] == 201
|
|
assert project_id == 1
|
|
assert generated_ref.startswith(f"{config.ref_autogen_prefix}_")
|
|
assert re.match(rf"{config.ref_autogen_prefix}_[0-9]{{14}}_[0-9a-f]{{8}}", generated_ref)
|
|
|
|
cleanup_response = await client.delete_project(project_id)
|
|
assert cleanup_response.get("success") is True
|
|
assert project_id not in created["projects"]
|