We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/marioser/dolibarr-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
"""Tests for API Key authentication module."""
import pytest
import time
from unittest.mock import MagicMock
from src.dolibarr_mcp.auth.api_key import (
APIKeyAuth,
generate_api_key,
verify_api_key,
extract_bearer_token,
)
class TestAPIKeyGeneration:
"""Tests for API key generation."""
def test_generate_key_default_length(self):
"""Generate key with default length (32 bytes = 64 hex chars)."""
key = generate_api_key()
assert len(key) == 64
assert all(c in "0123456789abcdef" for c in key)
def test_generate_key_custom_length(self):
"""Generate key with custom length."""
key = generate_api_key(length=16)
assert len(key) == 32
def test_generate_unique_keys(self):
"""Each generated key should be unique."""
keys = [generate_api_key() for _ in range(100)]
assert len(set(keys)) == 100
class TestBearerTokenExtraction:
"""Tests for bearer token extraction from Authorization header."""
def test_extract_valid_bearer(self):
"""Extract token from valid Bearer header."""
token = extract_bearer_token("Bearer abc123")
assert token == "abc123"
def test_extract_bearer_case_insensitive(self):
"""Bearer keyword should be case-insensitive."""
token = extract_bearer_token("bearer abc123")
assert token == "abc123"
token = extract_bearer_token("BEARER abc123")
assert token == "abc123"
def test_extract_empty_header(self):
"""Return None for empty header."""
token = extract_bearer_token("")
assert token is None
def test_extract_none_header(self):
"""Return None for None header."""
token = extract_bearer_token(None)
assert token is None
def test_extract_invalid_format(self):
"""Return None for non-Bearer auth."""
token = extract_bearer_token("Basic abc123")
assert token is None
def test_extract_no_token(self):
"""Return None when token missing."""
token = extract_bearer_token("Bearer")
assert token is None
class TestAPIKeyAuth:
"""Tests for APIKeyAuth class."""
def test_init_with_keys(self):
"""Initialize with explicit keys."""
auth = APIKeyAuth(api_keys=["key1", "key2"])
assert len(auth._key_hashes) == 2
def test_verify_valid_key(self):
"""Verify a valid API key."""
key = "test_api_key_12345"
auth = APIKeyAuth(api_keys=[key])
assert auth.verify(key) is True
def test_verify_invalid_key(self):
"""Reject an invalid API key."""
auth = APIKeyAuth(api_keys=["valid_key"])
assert auth.verify("invalid_key") is False
def test_verify_empty_key(self):
"""Reject empty key."""
auth = APIKeyAuth(api_keys=["valid_key"])
assert auth.verify("") is False
def test_no_keys_allows_all(self):
"""When no keys configured, allow all requests."""
auth = APIKeyAuth(api_keys=[])
assert auth.verify("any_key") is True
def test_rate_limiting(self):
"""Rate limiting should block excess requests."""
key = "test_key"
auth = APIKeyAuth(api_keys=[key], rate_limit=5, rate_window=60)
# First 5 requests should succeed
for _ in range(5):
assert auth.verify(key) is True
# 6th request should be rate limited
assert auth.verify(key) is False
def test_failed_attempts_tracking(self):
"""Track failed authentication attempts."""
auth = APIKeyAuth(api_keys=["valid_key"])
client_ip = "192.168.1.100"
# Make some failed attempts
for _ in range(5):
auth.verify("invalid_key", client_ip)
# Check attempts are tracked
assert client_ip in auth._failed_attempts
assert len(auth._failed_attempts[client_ip]) == 5
def test_ip_blocking(self):
"""Block IP after too many failed attempts."""
auth = APIKeyAuth(api_keys=["valid_key"])
client_ip = "192.168.1.100"
# Make 20 failed attempts
for _ in range(20):
auth.verify("invalid_key", client_ip)
# IP should now be blocked
assert auth.is_blocked(client_ip) is True
def test_ip_not_blocked_under_threshold(self):
"""IP should not be blocked under threshold."""
auth = APIKeyAuth(api_keys=["valid_key"])
client_ip = "192.168.1.100"
# Make 10 failed attempts (under default 20)
for _ in range(10):
auth.verify("invalid_key", client_ip)
assert auth.is_blocked(client_ip) is False
def test_get_stats(self):
"""Get authentication statistics."""
auth = APIKeyAuth(api_keys=["key1", "key2"])
stats = auth.get_stats()
assert stats["keys_configured"] == 2
assert "total_requests_last_minute" in stats
assert "blocked_ips" in stats
assert "failed_attempts_last_hour" in stats
class TestGlobalAuthFunctions:
"""Tests for global authentication functions."""
def test_verify_api_key_function(self, monkeypatch):
"""Test the global verify_api_key function."""
# Set environment variable for test
monkeypatch.setenv("MCP_API_KEY", "global_test_key")
# Reset global instance
import src.dolibarr_mcp.auth.api_key as auth_module
auth_module._auth_instance = None
# Should verify the key
assert verify_api_key("global_test_key") is True
assert verify_api_key("wrong_key") is False