Skip to main content
Glama

Finizi B4B MCP Server

by hqtrung
test_invoices.py16.7 kB
"""Comprehensive tests for invoice management functionality.""" import json import pytest from unittest.mock import Mock, AsyncMock, patch import httpx from src.finizi_b4b_mcp.tools.invoices import ( list_invoices, get_invoice, import_invoice_xml, get_invoice_statistics ) from src.finizi_b4b_mcp.utils.errors import MCPValidationError, MCPAuthenticationError, MCPAuthorizationError # Load mock responses with open("tests/fixtures/mock_responses.json") as f: MOCK_RESPONSES = json.load(f) # Tests for list_invoices function @pytest.mark.asyncio async def test_list_invoices_success(): """Test listing invoices with pagination.""" ctx = Mock() ctx.session = Mock() ctx.session.metadata = {"user_token": "fake_token"} mock_response = MOCK_RESPONSES["invoices"]["list_success"] with patch('src.finizi_b4b_mcp.tools.invoices.get_api_client') as mock_client: mock_api = Mock() mock_api.get = AsyncMock(return_value=mock_response) mock_client.return_value = mock_api result = await list_invoices( entity_id="123e4567-e89b-12d3-a456-426614174000", page=1, per_page=20, ctx=ctx ) assert result["success"] is True assert "data" in result assert len(result["data"]["items"]) == 2 assert result["data"]["total"] == 2 assert result["data"]["items"][0]["invoice_number"] == "INV-2024-001" @pytest.mark.asyncio async def test_list_invoices_with_filters(): """Test listing invoices with date and status filters.""" ctx = Mock() ctx.session = Mock() ctx.session.metadata = {"user_token": "fake_token"} mock_response = { "items": [MOCK_RESPONSES["invoices"]["list_success"]["items"][0]], "total": 1, "page": 1, "per_page": 20, "pages": 1 } with patch('src.finizi_b4b_mcp.tools.invoices.get_api_client') as mock_client: mock_api = Mock() mock_api.get = AsyncMock(return_value=mock_response) mock_client.return_value = mock_api result = await list_invoices( entity_id="123e4567-e89b-12d3-a456-426614174000", page=1, per_page=20, date_from="2024-01-01", date_to="2024-01-31", status=1, ctx=ctx ) assert result["success"] is True assert len(result["data"]["items"]) == 1 @pytest.mark.asyncio async def test_list_invoices_forbidden(): """Test 403 error when access is denied to entity invoices.""" ctx = Mock() ctx.session = Mock() ctx.session.metadata = {"user_token": "fake_token"} mock_response = Mock() mock_response.status_code = 403 mock_response.json.return_value = MOCK_RESPONSES["errors"]["forbidden_403"] mock_response.text = "Forbidden" with patch('src.finizi_b4b_mcp.tools.invoices.get_api_client') as mock_client: mock_api = Mock() mock_api.get = AsyncMock(side_effect=httpx.HTTPStatusError( "Forbidden", request=Mock(), response=mock_response )) mock_client.return_value = mock_api result = await list_invoices( entity_id="123e4567-e89b-12d3-a456-426614174000", page=1, per_page=20, ctx=ctx ) assert result["success"] is False assert "Access denied" in result["error"] @pytest.mark.asyncio async def test_list_invoices_not_authenticated(): """Test listing invoices without authentication.""" ctx = Mock() ctx.session = Mock() ctx.session.metadata = {} result = await list_invoices( entity_id="123e4567-e89b-12d3-a456-426614174000", page=1, per_page=20, ctx=ctx ) # Updated to match actual error format from invoices.py assert result["success"] is False assert ("authentication" in result["error"].lower() or "no authentication session" in result["error"].lower()) # Tests for get_invoice function @pytest.mark.asyncio async def test_get_invoice_success(): """Test retrieving detailed invoice information.""" ctx = Mock() ctx.session = Mock() ctx.session.metadata = {"user_token": "fake_token"} mock_response = MOCK_RESPONSES["invoices"]["get_success"] with patch('src.finizi_b4b_mcp.tools.invoices.get_api_client') as mock_client: mock_api = Mock() mock_api.get = AsyncMock(return_value=mock_response) mock_client.return_value = mock_api result = await get_invoice( entity_id="123e4567-e89b-12d3-a456-426614174000", invoice_id="inv-123e4567-e89b-12d3-a456-426614174000", ctx=ctx ) # Invoice responses are wrapped in success/data structure assert result["success"] is True assert "data" in result assert result["data"]["invoice_number"] == "INV-2024-001" assert result["data"]["vendor_name"] == "Supplier Co." assert len(result["data"]["line_items"]) == 1 assert result["data"]["line_items"][0]["product_name"] == "Product A" @pytest.mark.asyncio async def test_get_invoice_not_found(): """Test 404 error when invoice doesn't exist.""" ctx = Mock() ctx.session = Mock() ctx.session.metadata = {"user_token": "fake_token"} mock_response = Mock() mock_response.status_code = 404 mock_response.json.return_value = MOCK_RESPONSES["errors"]["not_found_404"] mock_response.text = "Not Found" with patch('src.finizi_b4b_mcp.tools.invoices.get_api_client') as mock_client: mock_api = Mock() mock_api.get = AsyncMock(side_effect=httpx.HTTPStatusError( "Not Found", request=Mock(), response=mock_response )) mock_client.return_value = mock_api result = await get_invoice( entity_id="123e4567-e89b-12d3-a456-426614174000", invoice_id="inv-nonexistent", ctx=ctx ) # Invoice responses use success/error structure assert result["success"] is False assert "not found" in result["error"].lower() @pytest.mark.asyncio async def test_get_invoice_forbidden(): """Test 403 error when access is denied to invoice.""" ctx = Mock() ctx.session = Mock() ctx.session.metadata = {"user_token": "fake_token"} mock_response = Mock() mock_response.status_code = 403 mock_response.json.return_value = MOCK_RESPONSES["errors"]["forbidden_403"] mock_response.text = "Forbidden" with patch('src.finizi_b4b_mcp.tools.invoices.get_api_client') as mock_client: mock_api = Mock() mock_api.get = AsyncMock(side_effect=httpx.HTTPStatusError( "Forbidden", request=Mock(), response=mock_response )) mock_client.return_value = mock_api result = await get_invoice( entity_id="123e4567-e89b-12d3-a456-426614174000", invoice_id="inv-123e4567-e89b-12d3-a456-426614174000", ctx=ctx ) # Invoice responses use success/error structure assert result["success"] is False assert ("access" in result["error"].lower() or "forbidden" in result["error"].lower()) # Tests for import_invoice_xml function @pytest.mark.asyncio async def test_import_invoice_xml_success(): """Test successful XML invoice import.""" ctx = Mock() ctx.session = Mock() ctx.session.metadata = {"user_token": "fake_token"} mock_response = MOCK_RESPONSES["invoices"]["import_xml_success"] xml_content = """<?xml version="1.0" encoding="UTF-8"?> <Invoice> <InvoiceNumber>INV-XML-2024-001</InvoiceNumber> <TotalAmount>5000000</TotalAmount> </Invoice>""" with patch('src.finizi_b4b_mcp.tools.invoices.get_api_client') as mock_client: mock_api = Mock() mock_api.post = AsyncMock(return_value=mock_response) mock_client.return_value = mock_api result = await import_invoice_xml( entity_id="123e4567-e89b-12d3-a456-426614174000", xml_content=xml_content, ctx=ctx ) assert result["success"] is True assert result["data"]["invoice_number"] == "INV-XML-2024-001" assert result["data"]["parsing_status"] == "success" assert result["data"]["total_amount"] == 5000000 @pytest.mark.asyncio async def test_import_invoice_xml_parsing_error(): """Test 400 error when XML parsing fails.""" ctx = Mock() ctx.session = Mock() ctx.session.metadata = {"user_token": "fake_token"} mock_response = Mock() mock_response.status_code = 400 mock_response.json.return_value = {"detail": "Invalid XML format"} mock_response.text = "Bad Request" with patch('src.finizi_b4b_mcp.tools.invoices.get_api_client') as mock_client: mock_api = Mock() mock_api.post = AsyncMock(side_effect=httpx.HTTPStatusError( "Bad Request", request=Mock(), response=mock_response )) mock_client.return_value = mock_api result = await import_invoice_xml( entity_id="123e4567-e89b-12d3-a456-426614174000", xml_content="<Invalid>XML</Invalid", ctx=ctx ) assert result["success"] is False assert "Failed to parse XML" in result["error"] @pytest.mark.asyncio async def test_import_invoice_xml_empty_content(): """Test validation error when XML content is empty.""" ctx = Mock() ctx.session = Mock() ctx.session.metadata = {"user_token": "fake_token"} result = await import_invoice_xml( entity_id="123e4567-e89b-12d3-a456-426614174000", xml_content="", ctx=ctx ) assert result["success"] is False assert "XML content cannot be empty" in result["error"] @pytest.mark.asyncio async def test_import_invoice_xml_forbidden(): """Test 403 error when import permission is denied.""" ctx = Mock() ctx.session = Mock() ctx.session.metadata = {"user_token": "fake_token"} mock_response = Mock() mock_response.status_code = 403 mock_response.json.return_value = MOCK_RESPONSES["errors"]["forbidden_403"] mock_response.text = "Forbidden" with patch('src.finizi_b4b_mcp.tools.invoices.get_api_client') as mock_client: mock_api = Mock() mock_api.post = AsyncMock(side_effect=httpx.HTTPStatusError( "Forbidden", request=Mock(), response=mock_response )) mock_client.return_value = mock_api result = await import_invoice_xml( entity_id="123e4567-e89b-12d3-a456-426614174000", xml_content="<Invoice></Invoice>", ctx=ctx ) assert result["success"] is False assert "Access denied" in result["error"] # Tests for get_invoice_statistics function @pytest.mark.asyncio async def test_get_invoice_statistics_success(): """Test retrieving invoice statistics.""" ctx = Mock() ctx.session = Mock() ctx.session.metadata = {"user_token": "fake_token"} mock_response = MOCK_RESPONSES["invoices"]["statistics_success"] with patch('src.finizi_b4b_mcp.tools.invoices.get_api_client') as mock_client: mock_api = Mock() mock_api.get = AsyncMock(return_value=mock_response) mock_client.return_value = mock_api result = await get_invoice_statistics( entity_id="123e4567-e89b-12d3-a456-426614174000", ctx=ctx ) assert result["success"] is True assert result["data"]["total_invoices"] == 150 assert result["data"]["total_amount"] == 500000000 assert result["data"]["total_vat"] == 50000000 assert "by_status" in result["data"] assert result["data"]["by_status"]["active"] == 120 @pytest.mark.asyncio async def test_get_invoice_statistics_with_filters(): """Test statistics with year and month filters.""" ctx = Mock() ctx.session = Mock() ctx.session.metadata = {"user_token": "fake_token"} mock_response = { "total_invoices": 40, "total_amount": 150000000, "total_vat": 15000000, "by_status": { "draft": 2, "active": 35, "cancelled": 2, "archived": 1 } } with patch('src.finizi_b4b_mcp.tools.invoices.get_api_client') as mock_client: mock_api = Mock() mock_api.get = AsyncMock(return_value=mock_response) mock_client.return_value = mock_api result = await get_invoice_statistics( entity_id="123e4567-e89b-12d3-a456-426614174000", year=2024, month=2, ctx=ctx ) assert result["success"] is True assert result["data"]["total_invoices"] == 40 assert result["data"]["total_amount"] == 150000000 @pytest.mark.asyncio async def test_get_invoice_statistics_invalid_month(): """Test validation error with invalid month.""" ctx = Mock() ctx.session = Mock() ctx.session.metadata = {"user_token": "fake_token"} result = await get_invoice_statistics( entity_id="123e4567-e89b-12d3-a456-426614174000", year=2024, month=13, ctx=ctx ) assert result["success"] is False assert "Month must be between 1 and 12" in result["error"] @pytest.mark.asyncio async def test_get_invoice_statistics_forbidden(): """Test 403 error when statistics access is denied.""" ctx = Mock() ctx.session = Mock() ctx.session.metadata = {"user_token": "fake_token"} mock_response = Mock() mock_response.status_code = 403 mock_response.json.return_value = MOCK_RESPONSES["errors"]["forbidden_403"] mock_response.text = "Forbidden" with patch('src.finizi_b4b_mcp.tools.invoices.get_api_client') as mock_client: mock_api = Mock() mock_api.get = AsyncMock(side_effect=httpx.HTTPStatusError( "Forbidden", request=Mock(), response=mock_response )) mock_client.return_value = mock_api result = await get_invoice_statistics( entity_id="123e4567-e89b-12d3-a456-426614174000", ctx=ctx ) assert result["success"] is False assert "Access denied" in result["error"] @pytest.mark.asyncio async def test_get_invoice_statistics_entity_not_found(): """Test 404 error when entity doesn't exist.""" ctx = Mock() ctx.session = Mock() ctx.session.metadata = {"user_token": "fake_token"} mock_response = Mock() mock_response.status_code = 404 mock_response.json.return_value = MOCK_RESPONSES["errors"]["not_found_404"] mock_response.text = "Not Found" with patch('src.finizi_b4b_mcp.tools.invoices.get_api_client') as mock_client: mock_api = Mock() mock_api.get = AsyncMock(side_effect=httpx.HTTPStatusError( "Not Found", request=Mock(), response=mock_response )) mock_client.return_value = mock_api result = await get_invoice_statistics( entity_id="nonexistent-entity-id", ctx=ctx ) # Invoice responses use success/error structure assert result["success"] is False assert "not found" in result["error"].lower() # Tests for error classes def test_authentication_error_default_message(): """Test authentication error with default message.""" error = MCPAuthenticationError() assert "Authentication required" in str(error) def test_authentication_error_custom_message(): """Test authentication error with custom message.""" error = MCPAuthenticationError("Custom auth error") assert str(error) == "Custom auth error" def test_authorization_error_default_message(): """Test authorization error with default message.""" error = MCPAuthorizationError() assert "do not have permission" in str(error) def test_authorization_error_custom_message(): """Test authorization error with custom message.""" error = MCPAuthorizationError("Custom authz error") assert str(error) == "Custom authz error" def test_validation_error_without_field(): """Test validation error without field name.""" error = MCPValidationError("Invalid input") assert "Invalid input" in str(error) assert error.field is None def test_validation_error_with_field(): """Test validation error with field name.""" error = MCPValidationError("Invalid value", field="amount") assert "amount" in str(error) assert "Invalid value" in str(error) assert error.field == "amount"

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/hqtrung/finizi-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server