"""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"]