Files
claudetools/test_phase5_api_endpoints.py
Mike Swanson 390b10b32c Complete Phase 6: MSP Work Tracking with Context Recall System
Implements production-ready MSP platform with cross-machine persistent memory for Claude.

API Implementation:
- 130 REST API endpoints across 21 entities
- JWT authentication on all endpoints
- AES-256-GCM encryption for credentials
- Automatic audit logging
- Complete OpenAPI documentation

Database:
- 43 tables in MariaDB (172.16.3.20:3306)
- 42 SQLAlchemy models with modern 2.0 syntax
- Full Alembic migration system
- 99.1% CRUD test pass rate

Context Recall System (Phase 6):
- Cross-machine persistent memory via database
- Automatic context injection via Claude Code hooks
- Automatic context saving after task completion
- 90-95% token reduction with compression utilities
- Relevance scoring with time decay
- Tag-based semantic search
- One-command setup script

Security Features:
- JWT tokens with Argon2 password hashing
- AES-256-GCM encryption for all sensitive data
- Comprehensive audit trail for credentials
- HMAC tamper detection
- Secure configuration management

Test Results:
- Phase 3: 38/38 CRUD tests passing (100%)
- Phase 4: 34/35 core API tests passing (97.1%)
- Phase 5: 62/62 extended API tests passing (100%)
- Phase 6: 10/10 compression tests passing (100%)
- Overall: 144/145 tests passing (99.3%)

Documentation:
- Comprehensive architecture guides
- Setup automation scripts
- API documentation at /api/docs
- Complete test reports
- Troubleshooting guides

Project Status: 95% Complete (Production-Ready)
Phase 7 (optional work context APIs) remains for future enhancement.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-17 06:00:26 -07:00

1598 lines
60 KiB
Python

"""
Comprehensive API Endpoint Tests for ClaudeTools Phase 5 Endpoints
This test suite validates all 12 Phase 5 API endpoints across 3 categories:
MSP Work Tracking (3 entities):
- Work Items API - /api/work-items
- Tasks API - /api/tasks
- Billable Time API - /api/billable-time
Infrastructure Management (6 entities):
- Sites API - /api/sites
- Infrastructure API - /api/infrastructure
- Services API - /api/services
- Networks API - /api/networks
- Firewall Rules API - /api/firewall-rules
- M365 Tenants API - /api/m365-tenants
Credentials Management (3 entities):
- Credentials API - /api/credentials (with encryption!)
- Credential Audit Logs API - /api/credential-audit-logs (read-only)
- Security Incidents API - /api/security-incidents
Tests include:
- CRUD operations for all entities
- Authentication (with JWT tokens)
- Pagination parameters
- Relationship queries (by-client, by-site, etc.)
- Special features (encryption for credentials, audit log creation)
- Error handling (404, 409, 422 responses)
"""
import sys
from datetime import date, timedelta
from uuid import uuid4
from fastapi.testclient import TestClient
# Import the FastAPI app and auth utilities
from api.main import app
from api.middleware.auth import create_access_token
# Create test client
client = TestClient(app)
# Test counters
tests_passed = 0
tests_failed = 0
test_results = []
# Track created entities for cleanup
created_entities = {
"clients": [],
"sites": [],
"projects": [],
"sessions": [],
"work_items": [],
"tasks": [],
"billable_time": [],
"infrastructure": [],
"services": [],
"networks": [],
"firewall_rules": [],
"m365_tenants": [],
"credentials": [],
"security_incidents": [],
}
def log_test(test_name: str, passed: bool, error_msg: str = ""):
"""Log test result and update counters."""
global tests_passed, tests_failed
if passed:
tests_passed += 1
status = "PASS"
symbol = "[+]"
else:
tests_failed += 1
status = "FAIL"
symbol = "[-]"
result = f"{symbol} {status}: {test_name}"
if error_msg:
result += f"\n Error: {error_msg}"
test_results.append((test_name, passed, error_msg))
print(result)
def create_test_token():
"""Create a test JWT token for authentication."""
token_data = {
"sub": "test_user@claudetools.com",
"scopes": ["msp:read", "msp:write", "msp:admin"]
}
return create_access_token(token_data, expires_delta=timedelta(hours=1))
def get_auth_headers():
"""Get authorization headers with test token."""
token = create_test_token()
return {"Authorization": f"Bearer {token}"}
# ============================================================================
# SECTION 1: MSP Work Tracking - Work Items API
# ============================================================================
print("\n" + "="*70)
print("SECTION 1: Work Items API Tests")
print("="*70 + "\n")
# First, create dependencies (client, project, session)
work_items_client_id = None
work_items_project_id = None
work_items_session_id = None
work_item_id = None
def test_create_work_items_dependencies():
"""Create client, project, and session for work items tests."""
global work_items_client_id, work_items_project_id, work_items_session_id
try:
headers = get_auth_headers()
# Create client
client_data = {
"name": f"WorkItems Test Client {uuid4().hex[:8]}",
"type": "msp_client",
"is_active": True
}
response = client.post("/api/clients", json=client_data, headers=headers)
assert response.status_code == 201, f"Failed to create client: {response.text}"
work_items_client_id = response.json()["id"]
created_entities["clients"].append(work_items_client_id)
# Create project
project_data = {
"name": f"WorkItems Test Project {uuid4().hex[:8]}",
"client_id": work_items_client_id,
"status": "active"
}
response = client.post("/api/projects", json=project_data, headers=headers)
assert response.status_code == 201, f"Failed to create project: {response.text}"
work_items_project_id = response.json()["id"]
created_entities["projects"].append(work_items_project_id)
# Create session
session_data = {
"session_title": f"WorkItems Test Session {uuid4().hex[:8]}",
"session_date": str(date.today()),
"client_id": work_items_client_id,
"project_id": work_items_project_id,
"status": "completed"
}
response = client.post("/api/sessions", json=session_data, headers=headers)
assert response.status_code == 201, f"Failed to create session: {response.text}"
work_items_session_id = response.json()["id"]
created_entities["sessions"].append(work_items_session_id)
log_test("Create work items dependencies (client, project, session)", True)
except Exception as e:
log_test("Create work items dependencies (client, project, session)", False, str(e))
def test_create_work_item():
"""Test creating a work item."""
global work_item_id
try:
headers = get_auth_headers()
work_item_data = {
"session_id": work_items_session_id,
"category": "infrastructure",
"title": f"Test Work Item {uuid4().hex[:8]}",
"description": "Testing work item creation",
"status": "completed",
"priority": "high",
"is_billable": True,
"estimated_minutes": 30,
"actual_minutes": 25
}
response = client.post("/api/work-items", json=work_item_data, headers=headers)
assert response.status_code == 201, f"Expected 201, got {response.status_code}. Response: {response.text}"
data = response.json()
assert "id" in data, "Response missing 'id' field"
work_item_id = data["id"]
created_entities["work_items"].append(work_item_id)
print(f" Created work item with ID: {work_item_id}")
log_test("Create work item", True)
except Exception as e:
log_test("Create work item", False, str(e))
def test_list_work_items():
"""Test listing work items."""
try:
headers = get_auth_headers()
response = client.get("/api/work-items?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "total" in data, "Response missing 'total' field"
assert "work_items" in data, "Response missing 'work_items' field"
log_test("List work items", True)
except Exception as e:
log_test("List work items", False, str(e))
def test_get_work_item():
"""Test getting a specific work item."""
try:
if work_item_id is None:
raise Exception("No work_item_id available")
headers = get_auth_headers()
response = client.get(f"/api/work-items/{work_item_id}", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["id"] == work_item_id, f"Expected ID {work_item_id}, got {data.get('id')}"
log_test("Get work item by ID", True)
except Exception as e:
log_test("Get work item by ID", False, str(e))
def test_update_work_item():
"""Test updating a work item."""
try:
if work_item_id is None:
raise Exception("No work_item_id available")
headers = get_auth_headers()
update_data = {
"status": "completed",
"actual_minutes": 30
}
response = client.put(f"/api/work-items/{work_item_id}", json=update_data, headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["actual_minutes"] == 30, "Update not reflected"
log_test("Update work item", True)
except Exception as e:
log_test("Update work item", False, str(e))
def test_get_work_items_by_client():
"""Test getting work items by client."""
try:
headers = get_auth_headers()
response = client.get(f"/api/work-items/by-client/{work_items_client_id}?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "work_items" in data, "Response missing 'work_items' field"
log_test("Get work items by client", True)
except Exception as e:
log_test("Get work items by client", False, str(e))
# ============================================================================
# SECTION 2: MSP Work Tracking - Tasks API
# ============================================================================
print("\n" + "="*70)
print("SECTION 2: Tasks API Tests")
print("="*70 + "\n")
task_id = None
def test_create_task():
"""Test creating a task."""
global task_id
try:
headers = get_auth_headers()
task_data = {
"client_id": work_items_client_id,
"title": f"Test Task {uuid4().hex[:8]}",
"description": "Testing task creation",
"status": "pending",
"task_order": 1
}
response = client.post("/api/tasks", json=task_data, headers=headers)
assert response.status_code == 201, f"Expected 201, got {response.status_code}. Response: {response.text}"
data = response.json()
assert "id" in data, "Response missing 'id' field"
task_id = data["id"]
created_entities["tasks"].append(task_id)
print(f" Created task with ID: {task_id}")
log_test("Create task", True)
except Exception as e:
log_test("Create task", False, str(e))
def test_list_tasks():
"""Test listing tasks."""
try:
headers = get_auth_headers()
response = client.get("/api/tasks?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "total" in data, "Response missing 'total' field"
assert "tasks" in data, "Response missing 'tasks' field"
log_test("List tasks", True)
except Exception as e:
log_test("List tasks", False, str(e))
def test_get_task():
"""Test getting a specific task."""
try:
if task_id is None:
raise Exception("No task_id available")
headers = get_auth_headers()
response = client.get(f"/api/tasks/{task_id}", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["id"] == task_id, f"Expected ID {task_id}, got {data.get('id')}"
log_test("Get task by ID", True)
except Exception as e:
log_test("Get task by ID", False, str(e))
def test_update_task():
"""Test updating a task."""
try:
if task_id is None:
raise Exception("No task_id available")
headers = get_auth_headers()
update_data = {
"status": "in_progress",
"priority": "high"
}
response = client.put(f"/api/tasks/{task_id}", json=update_data, headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["status"] == "in_progress", "Update not reflected"
log_test("Update task", True)
except Exception as e:
log_test("Update task", False, str(e))
def test_get_tasks_filtering():
"""Test getting tasks with status filtering."""
try:
headers = get_auth_headers()
response = client.get(f"/api/tasks?status_filter=pending&skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "tasks" in data, "Response missing 'tasks' field"
log_test("Get tasks with status filtering", True)
except Exception as e:
log_test("Get tasks with status filtering", False, str(e))
# ============================================================================
# SECTION 3: MSP Work Tracking - Billable Time API
# ============================================================================
print("\n" + "="*70)
print("SECTION 3: Billable Time API Tests")
print("="*70 + "\n")
billable_time_id = None
def test_create_billable_time():
"""Test creating a billable time entry."""
global billable_time_id
try:
headers = get_auth_headers()
from datetime import datetime
billable_time_data = {
"client_id": work_items_client_id,
"session_id": work_items_session_id,
"work_item_id": work_item_id,
"description": "Testing billable time entry",
"start_time": datetime.now().isoformat(),
"duration_minutes": 60,
"hourly_rate": 150.00,
"total_amount": 150.00,
"category": "consulting",
"is_billable": True
}
response = client.post("/api/billable-time", json=billable_time_data, headers=headers)
assert response.status_code == 201, f"Expected 201, got {response.status_code}. Response: {response.text}"
data = response.json()
assert "id" in data, "Response missing 'id' field"
billable_time_id = data["id"]
created_entities["billable_time"].append(billable_time_id)
print(f" Created billable time with ID: {billable_time_id}")
log_test("Create billable time", True)
except Exception as e:
log_test("Create billable time", False, str(e))
def test_list_billable_time():
"""Test listing billable time entries."""
try:
headers = get_auth_headers()
response = client.get("/api/billable-time?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "total" in data, "Response missing 'total' field"
assert "billable_time" in data, "Response missing 'billable_time' field"
log_test("List billable time", True)
except Exception as e:
log_test("List billable time", False, str(e))
def test_get_billable_time():
"""Test getting a specific billable time entry."""
try:
if billable_time_id is None:
raise Exception("No billable_time_id available")
headers = get_auth_headers()
response = client.get(f"/api/billable-time/{billable_time_id}", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["id"] == billable_time_id, f"Expected ID {billable_time_id}, got {data.get('id')}"
log_test("Get billable time by ID", True)
except Exception as e:
log_test("Get billable time by ID", False, str(e))
def test_update_billable_time():
"""Test updating a billable time entry."""
try:
if billable_time_id is None:
raise Exception("No billable_time_id available")
headers = get_auth_headers()
update_data = {
"duration_minutes": 90,
"hourly_rate": 175.00,
"total_amount": 262.50
}
response = client.put(f"/api/billable-time/{billable_time_id}", json=update_data, headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["duration_minutes"] == 90, "Update not reflected"
log_test("Update billable time", True)
except Exception as e:
log_test("Update billable time", False, str(e))
def test_get_billable_time_by_session():
"""Test getting billable time by session."""
try:
headers = get_auth_headers()
response = client.get(f"/api/billable-time/by-session/{work_items_session_id}?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "billable_time" in data, "Response missing 'billable_time' field"
log_test("Get billable time by session", True)
except Exception as e:
log_test("Get billable time by session", False, str(e))
# ============================================================================
# SECTION 4: Infrastructure Management - Sites API
# ============================================================================
print("\n" + "="*70)
print("SECTION 4: Sites API Tests")
print("="*70 + "\n")
site_id = None
def test_create_site():
"""Test creating a site."""
global site_id
try:
headers = get_auth_headers()
site_data = {
"client_id": work_items_client_id,
"name": f"Test Site {uuid4().hex[:8]}",
"network_subnet": "172.16.1.0/24",
"vpn_required": True,
"gateway_ip": "172.16.1.1"
}
response = client.post("/api/sites", json=site_data, headers=headers)
assert response.status_code == 201, f"Expected 201, got {response.status_code}. Response: {response.text}"
data = response.json()
assert "id" in data, "Response missing 'id' field"
site_id = data["id"]
created_entities["sites"].append(site_id)
print(f" Created site with ID: {site_id}")
log_test("Create site", True)
except Exception as e:
log_test("Create site", False, str(e))
def test_list_sites():
"""Test listing sites."""
try:
headers = get_auth_headers()
response = client.get("/api/sites?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "total" in data, "Response missing 'total' field"
assert "sites" in data, "Response missing 'sites' field"
log_test("List sites", True)
except Exception as e:
log_test("List sites", False, str(e))
def test_get_site():
"""Test getting a specific site."""
try:
if site_id is None:
raise Exception("No site_id available")
headers = get_auth_headers()
response = client.get(f"/api/sites/{site_id}", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["id"] == site_id, f"Expected ID {site_id}, got {data.get('id')}"
log_test("Get site by ID", True)
except Exception as e:
log_test("Get site by ID", False, str(e))
def test_update_site():
"""Test updating a site."""
try:
if site_id is None:
raise Exception("No site_id available")
headers = get_auth_headers()
update_data = {
"vpn_required": False,
"notes": "VPN disabled"
}
response = client.put(f"/api/sites/{site_id}", json=update_data, headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["vpn_required"] == False, "Update not reflected"
log_test("Update site", True)
except Exception as e:
log_test("Update site", False, str(e))
def test_get_sites_by_client():
"""Test getting sites by client."""
try:
headers = get_auth_headers()
response = client.get(f"/api/sites/by-client/{work_items_client_id}?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "sites" in data, "Response missing 'sites' field"
log_test("Get sites by client", True)
except Exception as e:
log_test("Get sites by client", False, str(e))
# ============================================================================
# SECTION 5: Infrastructure Management - Infrastructure API
# ============================================================================
print("\n" + "="*70)
print("SECTION 5: Infrastructure API Tests")
print("="*70 + "\n")
infrastructure_id = None
def test_create_infrastructure():
"""Test creating an infrastructure component."""
global infrastructure_id
try:
headers = get_auth_headers()
infra_data = {
"site_id": site_id,
"asset_type": "physical_server",
"hostname": f"test-server-{uuid4().hex[:8]}",
"ip_address": "172.16.1.100",
"status": "active"
}
response = client.post("/api/infrastructure", json=infra_data, headers=headers)
assert response.status_code == 201, f"Expected 201, got {response.status_code}. Response: {response.text}"
data = response.json()
assert "id" in data, "Response missing 'id' field"
infrastructure_id = data["id"]
created_entities["infrastructure"].append(infrastructure_id)
print(f" Created infrastructure with ID: {infrastructure_id}")
log_test("Create infrastructure", True)
except Exception as e:
log_test("Create infrastructure", False, str(e))
def test_list_infrastructure():
"""Test listing infrastructure components."""
try:
headers = get_auth_headers()
response = client.get("/api/infrastructure?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "total" in data, "Response missing 'total' field"
assert "infrastructure" in data, "Response missing 'infrastructure' field"
log_test("List infrastructure", True)
except Exception as e:
log_test("List infrastructure", False, str(e))
def test_get_infrastructure():
"""Test getting a specific infrastructure component."""
try:
if infrastructure_id is None:
raise Exception("No infrastructure_id available")
headers = get_auth_headers()
response = client.get(f"/api/infrastructure/{infrastructure_id}", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["id"] == infrastructure_id, f"Expected ID {infrastructure_id}, got {data.get('id')}"
log_test("Get infrastructure by ID", True)
except Exception as e:
log_test("Get infrastructure by ID", False, str(e))
def test_update_infrastructure():
"""Test updating an infrastructure component."""
try:
if infrastructure_id is None:
raise Exception("No infrastructure_id available")
headers = get_auth_headers()
update_data = {
"os": "Ubuntu 24.04",
"notes": "OS updated"
}
response = client.put(f"/api/infrastructure/{infrastructure_id}", json=update_data, headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["os"] == "Ubuntu 24.04", "Update not reflected"
log_test("Update infrastructure", True)
except Exception as e:
log_test("Update infrastructure", False, str(e))
def test_get_infrastructure_by_site():
"""Test getting infrastructure by site."""
try:
headers = get_auth_headers()
response = client.get(f"/api/infrastructure/by-site/{site_id}?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "infrastructure" in data, "Response missing 'infrastructure' field"
log_test("Get infrastructure by site", True)
except Exception as e:
log_test("Get infrastructure by site", False, str(e))
# ============================================================================
# SECTION 6: Infrastructure Management - Services API
# ============================================================================
print("\n" + "="*70)
print("SECTION 6: Services API Tests")
print("="*70 + "\n")
service_id = None
def test_create_service():
"""Test creating a service."""
global service_id
try:
headers = get_auth_headers()
service_data = {
"infrastructure_id": infrastructure_id,
"service_name": f"Test Service {uuid4().hex[:8]}",
"service_type": "web_server",
"port": 443,
"protocol": "https",
"status": "running"
}
response = client.post("/api/services", json=service_data, headers=headers)
assert response.status_code == 201, f"Expected 201, got {response.status_code}. Response: {response.text}"
data = response.json()
assert "id" in data, "Response missing 'id' field"
service_id = data["id"]
created_entities["services"].append(service_id)
print(f" Created service with ID: {service_id}")
log_test("Create service", True)
except Exception as e:
log_test("Create service", False, str(e))
def test_list_services():
"""Test listing services."""
try:
headers = get_auth_headers()
response = client.get("/api/services?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "total" in data, "Response missing 'total' field"
assert "services" in data, "Response missing 'services' field"
log_test("List services", True)
except Exception as e:
log_test("List services", False, str(e))
def test_get_service():
"""Test getting a specific service."""
try:
if service_id is None:
raise Exception("No service_id available")
headers = get_auth_headers()
response = client.get(f"/api/services/{service_id}", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["id"] == service_id, f"Expected ID {service_id}, got {data.get('id')}"
log_test("Get service by ID", True)
except Exception as e:
log_test("Get service by ID", False, str(e))
def test_update_service():
"""Test updating a service."""
try:
if service_id is None:
raise Exception("No service_id available")
headers = get_auth_headers()
update_data = {
"status": "stopped",
"notes": "Service stopped for maintenance"
}
response = client.put(f"/api/services/{service_id}", json=update_data, headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["status"] == "stopped", "Update not reflected"
log_test("Update service", True)
except Exception as e:
log_test("Update service", False, str(e))
def test_get_services_by_client():
"""Test getting services by client."""
try:
headers = get_auth_headers()
response = client.get(f"/api/services/by-client/{work_items_client_id}?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "services" in data, "Response missing 'services' field"
log_test("Get services by client", True)
except Exception as e:
log_test("Get services by client", False, str(e))
# ============================================================================
# SECTION 7: Infrastructure Management - Networks API
# ============================================================================
print("\n" + "="*70)
print("SECTION 7: Networks API Tests")
print("="*70 + "\n")
network_id = None
def test_create_network():
"""Test creating a network."""
global network_id
try:
headers = get_auth_headers()
network_data = {
"site_id": site_id,
"network_name": f"Test Network {uuid4().hex[:8]}",
"cidr": "10.0.1.0/24",
"vlan_id": 100,
"network_type": "lan"
}
response = client.post("/api/networks", json=network_data, headers=headers)
assert response.status_code == 201, f"Expected 201, got {response.status_code}. Response: {response.text}"
data = response.json()
assert "id" in data, "Response missing 'id' field"
network_id = data["id"]
created_entities["networks"].append(network_id)
print(f" Created network with ID: {network_id}")
log_test("Create network", True)
except Exception as e:
log_test("Create network", False, str(e))
def test_list_networks():
"""Test listing networks."""
try:
headers = get_auth_headers()
response = client.get("/api/networks?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "total" in data, "Response missing 'total' field"
assert "networks" in data, "Response missing 'networks' field"
log_test("List networks", True)
except Exception as e:
log_test("List networks", False, str(e))
def test_get_network():
"""Test getting a specific network."""
try:
if network_id is None:
raise Exception("No network_id available")
headers = get_auth_headers()
response = client.get(f"/api/networks/{network_id}", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["id"] == network_id, f"Expected ID {network_id}, got {data.get('id')}"
log_test("Get network by ID", True)
except Exception as e:
log_test("Get network by ID", False, str(e))
def test_update_network():
"""Test updating a network."""
try:
if network_id is None:
raise Exception("No network_id available")
headers = get_auth_headers()
update_data = {
"vlan_id": 200,
"notes": "VLAN updated"
}
response = client.put(f"/api/networks/{network_id}", json=update_data, headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["vlan_id"] == 200, "Update not reflected"
log_test("Update network", True)
except Exception as e:
log_test("Update network", False, str(e))
def test_get_networks_by_site():
"""Test getting networks by site."""
try:
headers = get_auth_headers()
response = client.get(f"/api/networks/by-site/{site_id}?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "networks" in data, "Response missing 'networks' field"
log_test("Get networks by site", True)
except Exception as e:
log_test("Get networks by site", False, str(e))
# ============================================================================
# SECTION 8: Infrastructure Management - Firewall Rules API
# ============================================================================
print("\n" + "="*70)
print("SECTION 8: Firewall Rules API Tests")
print("="*70 + "\n")
firewall_rule_id = None
def test_create_firewall_rule():
"""Test creating a firewall rule."""
global firewall_rule_id
try:
headers = get_auth_headers()
firewall_data = {
"infrastructure_id": infrastructure_id,
"rule_name": f"Test Rule {uuid4().hex[:8]}",
"source": "0.0.0.0/0",
"destination": "172.16.1.100",
"port": 443,
"protocol": "tcp",
"action": "allow",
"priority": 100
}
response = client.post("/api/firewall-rules", json=firewall_data, headers=headers)
assert response.status_code == 201, f"Expected 201, got {response.status_code}. Response: {response.text}"
data = response.json()
assert "id" in data, "Response missing 'id' field"
firewall_rule_id = data["id"]
created_entities["firewall_rules"].append(firewall_rule_id)
print(f" Created firewall rule with ID: {firewall_rule_id}")
log_test("Create firewall rule", True)
except Exception as e:
log_test("Create firewall rule", False, str(e))
def test_list_firewall_rules():
"""Test listing firewall rules."""
try:
headers = get_auth_headers()
response = client.get("/api/firewall-rules?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "total" in data, "Response missing 'total' field"
assert "firewall_rules" in data, "Response missing 'firewall_rules' field"
log_test("List firewall rules", True)
except Exception as e:
log_test("List firewall rules", False, str(e))
def test_get_firewall_rule():
"""Test getting a specific firewall rule."""
try:
if firewall_rule_id is None:
raise Exception("No firewall_rule_id available")
headers = get_auth_headers()
response = client.get(f"/api/firewall-rules/{firewall_rule_id}", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["id"] == firewall_rule_id, f"Expected ID {firewall_rule_id}, got {data.get('id')}"
log_test("Get firewall rule by ID", True)
except Exception as e:
log_test("Get firewall rule by ID", False, str(e))
def test_update_firewall_rule():
"""Test updating a firewall rule."""
try:
if firewall_rule_id is None:
raise Exception("No firewall_rule_id available")
headers = get_auth_headers()
update_data = {
"action": "deny",
"notes": "Rule changed to deny"
}
response = client.put(f"/api/firewall-rules/{firewall_rule_id}", json=update_data, headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["action"] == "deny", "Update not reflected"
log_test("Update firewall rule", True)
except Exception as e:
log_test("Update firewall rule", False, str(e))
def test_get_firewall_rules_by_infrastructure():
"""Test getting firewall rules by infrastructure."""
try:
headers = get_auth_headers()
response = client.get(f"/api/firewall-rules/by-infrastructure/{infrastructure_id}?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "firewall_rules" in data, "Response missing 'firewall_rules' field"
log_test("Get firewall rules by infrastructure", True)
except Exception as e:
log_test("Get firewall rules by infrastructure", False, str(e))
# ============================================================================
# SECTION 9: Infrastructure Management - M365 Tenants API
# ============================================================================
print("\n" + "="*70)
print("SECTION 9: M365 Tenants API Tests")
print("="*70 + "\n")
m365_tenant_id = None
def test_create_m365_tenant():
"""Test creating an M365 tenant."""
global m365_tenant_id
try:
headers = get_auth_headers()
m365_data = {
"client_id": work_items_client_id,
"tenant_name": f"Test Tenant {uuid4().hex[:8]}",
"tenant_id": str(uuid4()),
"primary_domain": f"test{uuid4().hex[:8]}.onmicrosoft.com",
"admin_email": "admin@test.com"
}
response = client.post("/api/m365-tenants", json=m365_data, headers=headers)
assert response.status_code == 201, f"Expected 201, got {response.status_code}. Response: {response.text}"
data = response.json()
assert "id" in data, "Response missing 'id' field"
m365_tenant_id = data["id"]
created_entities["m365_tenants"].append(m365_tenant_id)
print(f" Created M365 tenant with ID: {m365_tenant_id}")
log_test("Create M365 tenant", True)
except Exception as e:
log_test("Create M365 tenant", False, str(e))
def test_list_m365_tenants():
"""Test listing M365 tenants."""
try:
headers = get_auth_headers()
response = client.get("/api/m365-tenants?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "total" in data, "Response missing 'total' field"
assert "m365_tenants" in data, "Response missing 'm365_tenants' field"
log_test("List M365 tenants", True)
except Exception as e:
log_test("List M365 tenants", False, str(e))
def test_get_m365_tenant():
"""Test getting a specific M365 tenant."""
try:
if m365_tenant_id is None:
raise Exception("No m365_tenant_id available")
headers = get_auth_headers()
response = client.get(f"/api/m365-tenants/{m365_tenant_id}", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["id"] == m365_tenant_id, f"Expected ID {m365_tenant_id}, got {data.get('id')}"
log_test("Get M365 tenant by ID", True)
except Exception as e:
log_test("Get M365 tenant by ID", False, str(e))
def test_update_m365_tenant():
"""Test updating an M365 tenant."""
try:
if m365_tenant_id is None:
raise Exception("No m365_tenant_id available")
headers = get_auth_headers()
update_data = {
"admin_email": "newadmin@test.com",
"notes": "Admin email updated"
}
response = client.put(f"/api/m365-tenants/{m365_tenant_id}", json=update_data, headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["admin_email"] == "newadmin@test.com", "Update not reflected"
log_test("Update M365 tenant", True)
except Exception as e:
log_test("Update M365 tenant", False, str(e))
def test_get_m365_tenants_by_client():
"""Test getting M365 tenants by client."""
try:
headers = get_auth_headers()
response = client.get(f"/api/m365-tenants/by-client/{work_items_client_id}?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "m365_tenants" in data, "Response missing 'm365_tenants' field"
log_test("Get M365 tenants by client", True)
except Exception as e:
log_test("Get M365 tenants by client", False, str(e))
# ============================================================================
# SECTION 10: Credentials Management - Credentials API (with Encryption!)
# ============================================================================
print("\n" + "="*70)
print("SECTION 10: Credentials API Tests (with Encryption)")
print("="*70 + "\n")
credential_id = None
def test_create_credential_password():
"""Test creating a password credential with encryption."""
global credential_id
try:
headers = get_auth_headers()
credential_data = {
"client_id": work_items_client_id,
"service_id": service_id,
"credential_type": "password",
"service_name": f"Test Service Cred {uuid4().hex[:8]}",
"username": "testuser",
"password": "SuperSecretPassword123!",
"requires_vpn": False,
"requires_2fa": True,
"is_active": True
}
response = client.post("/api/credentials", json=credential_data, headers=headers)
assert response.status_code == 201, f"Expected 201, got {response.status_code}. Response: {response.text}"
data = response.json()
assert "id" in data, "Response missing 'id' field"
credential_id = data["id"]
created_entities["credentials"].append(credential_id)
# Verify password is decrypted in response
assert data["password"] == "SuperSecretPassword123!", "Password not decrypted in response"
print(f" Created credential with ID: {credential_id}")
print(f" Password encrypted and decrypted successfully: {data['password']}")
log_test("Create credential with password encryption", True)
except Exception as e:
log_test("Create credential with password encryption", False, str(e))
def test_create_credential_api_key():
"""Test creating an API key credential with encryption."""
try:
headers = get_auth_headers()
credential_data = {
"client_id": work_items_client_id,
"credential_type": "api_key",
"service_name": f"Test API Key {uuid4().hex[:8]}",
"api_key": "sk-test-1234567890abcdef",
"is_active": True
}
response = client.post("/api/credentials", json=credential_data, headers=headers)
assert response.status_code == 201, f"Expected 201, got {response.status_code}. Response: {response.text}"
data = response.json()
assert "id" in data, "Response missing 'id' field"
created_entities["credentials"].append(data["id"])
# Verify API key is decrypted in response
assert data["api_key"] == "sk-test-1234567890abcdef", "API key not decrypted in response"
print(f" API key encrypted and decrypted successfully")
log_test("Create credential with API key encryption", True)
except Exception as e:
log_test("Create credential with API key encryption", False, str(e))
def test_create_credential_oauth():
"""Test creating an OAuth credential with encryption."""
try:
headers = get_auth_headers()
credential_data = {
"client_id": work_items_client_id,
"credential_type": "oauth",
"service_name": f"Test OAuth {uuid4().hex[:8]}",
"client_id_oauth": "oauth-client-id-123",
"client_secret": "oauth-secret-xyz789",
"tenant_id_oauth": "tenant-abc-456",
"is_active": True
}
response = client.post("/api/credentials", json=credential_data, headers=headers)
assert response.status_code == 201, f"Expected 201, got {response.status_code}. Response: {response.text}"
data = response.json()
assert "id" in data, "Response missing 'id' field"
created_entities["credentials"].append(data["id"])
# Verify client secret is decrypted in response
assert data["client_secret"] == "oauth-secret-xyz789", "Client secret not decrypted in response"
print(f" OAuth client secret encrypted and decrypted successfully")
log_test("Create credential with OAuth encryption", True)
except Exception as e:
log_test("Create credential with OAuth encryption", False, str(e))
def test_list_credentials():
"""Test listing credentials."""
try:
headers = get_auth_headers()
response = client.get("/api/credentials?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "total" in data, "Response missing 'total' field"
assert "credentials" in data, "Response missing 'credentials' field"
log_test("List credentials", True)
except Exception as e:
log_test("List credentials", False, str(e))
def test_get_credential():
"""Test getting a specific credential (should create audit log)."""
try:
if credential_id is None:
raise Exception("No credential_id available")
headers = get_auth_headers()
response = client.get(f"/api/credentials/{credential_id}", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["id"] == credential_id, f"Expected ID {credential_id}, got {data.get('id')}"
# Verify password is still decrypted
assert data["password"] is not None, "Password not present in response"
print(f" Credential retrieved and audit log created")
log_test("Get credential by ID (creates audit log)", True)
except Exception as e:
log_test("Get credential by ID (creates audit log)", False, str(e))
def test_update_credential():
"""Test updating a credential."""
try:
if credential_id is None:
raise Exception("No credential_id available")
headers = get_auth_headers()
update_data = {
"password": "NewSuperSecretPassword456!",
"requires_2fa": False
}
response = client.put(f"/api/credentials/{credential_id}", json=update_data, headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
# Verify new password is decrypted in response
assert data["password"] == "NewSuperSecretPassword456!", "Password update not reflected"
assert data["requires_2fa"] == False, "Update not reflected"
print(f" Password re-encrypted successfully")
log_test("Update credential (re-encrypts password)", True)
except Exception as e:
log_test("Update credential (re-encrypts password)", False, str(e))
def test_get_credentials_by_client():
"""Test getting credentials by client."""
try:
headers = get_auth_headers()
response = client.get(f"/api/credentials/by-client/{work_items_client_id}?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "credentials" in data, "Response missing 'credentials' field"
log_test("Get credentials by client", True)
except Exception as e:
log_test("Get credentials by client", False, str(e))
# ============================================================================
# SECTION 11: Credentials Management - Credential Audit Logs API (Read-Only)
# ============================================================================
print("\n" + "="*70)
print("SECTION 11: Credential Audit Logs API Tests (Read-Only)")
print("="*70 + "\n")
def test_list_credential_audit_logs():
"""Test listing credential audit logs."""
try:
headers = get_auth_headers()
response = client.get("/api/credential-audit-logs?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "total" in data, "Response missing 'total' field"
assert "logs" in data, "Response missing 'logs' field"
# Should have at least some audit logs from credential operations
assert data["total"] > 0, "No audit logs found (should have been created by credential operations)"
print(f" Found {data['total']} audit log entries")
log_test("List credential audit logs", True)
except Exception as e:
log_test("List credential audit logs", False, str(e))
def test_get_credential_audit_logs_by_credential():
"""Test getting audit logs for a specific credential."""
try:
if credential_id is None:
raise Exception("No credential_id available")
headers = get_auth_headers()
response = client.get(f"/api/credential-audit-logs/by-credential/{credential_id}?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "logs" in data, "Response missing 'logs' field"
# Should have audit logs for CREATE, VIEW, and UPDATE actions
assert data["total"] >= 3, f"Expected at least 3 audit logs (create, view, update), got {data['total']}"
print(f" Found {data['total']} audit logs for credential")
log_test("Get audit logs by credential", True)
except Exception as e:
log_test("Get audit logs by credential", False, str(e))
def test_get_credential_audit_logs_by_user():
"""Test getting audit logs by user."""
try:
headers = get_auth_headers()
user_id = "test_user@claudetools.com"
response = client.get(f"/api/credential-audit-logs/by-user/{user_id}?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "logs" in data, "Response missing 'logs' field"
print(f" Found {data['total']} audit logs for user")
log_test("Get audit logs by user", True)
except Exception as e:
log_test("Get audit logs by user", False, str(e))
# ============================================================================
# SECTION 12: Credentials Management - Security Incidents API
# ============================================================================
print("\n" + "="*70)
print("SECTION 12: Security Incidents API Tests")
print("="*70 + "\n")
security_incident_id = None
def test_create_security_incident():
"""Test creating a security incident."""
global security_incident_id
try:
headers = get_auth_headers()
from datetime import datetime
incident_data = {
"client_id": work_items_client_id,
"incident_type": "unauthorized_access",
"severity": "high",
"description": "Testing security incident creation",
"incident_date": datetime.now().isoformat(),
"status": "investigating"
}
response = client.post("/api/security-incidents", json=incident_data, headers=headers)
assert response.status_code == 201, f"Expected 201, got {response.status_code}. Response: {response.text}"
data = response.json()
assert "id" in data, "Response missing 'id' field"
security_incident_id = data["id"]
created_entities["security_incidents"].append(security_incident_id)
print(f" Created security incident with ID: {security_incident_id}")
log_test("Create security incident", True)
except Exception as e:
log_test("Create security incident", False, str(e))
def test_list_security_incidents():
"""Test listing security incidents."""
try:
headers = get_auth_headers()
response = client.get("/api/security-incidents?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "total" in data, "Response missing 'total' field"
assert "incidents" in data, "Response missing 'incidents' field"
log_test("List security incidents", True)
except Exception as e:
log_test("List security incidents", False, str(e))
def test_get_security_incident():
"""Test getting a specific security incident."""
try:
if security_incident_id is None:
raise Exception("No security_incident_id available")
headers = get_auth_headers()
response = client.get(f"/api/security-incidents/{security_incident_id}", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["id"] == security_incident_id, f"Expected ID {security_incident_id}, got {data.get('id')}"
log_test("Get security incident by ID", True)
except Exception as e:
log_test("Get security incident by ID", False, str(e))
def test_update_security_incident():
"""Test updating a security incident."""
try:
if security_incident_id is None:
raise Exception("No security_incident_id available")
headers = get_auth_headers()
update_data = {
"status": "resolved",
"severity": "medium",
"remediation_steps": "Issue resolved"
}
response = client.put(f"/api/security-incidents/{security_incident_id}", json=update_data, headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert data["status"] == "resolved", "Update not reflected"
log_test("Update security incident", True)
except Exception as e:
log_test("Update security incident", False, str(e))
def test_get_security_incidents_by_client():
"""Test getting security incidents by client."""
try:
headers = get_auth_headers()
response = client.get(f"/api/security-incidents/by-client/{work_items_client_id}?skip=0&limit=10", headers=headers)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "incidents" in data, "Response missing 'incidents' field"
log_test("Get security incidents by client", True)
except Exception as e:
log_test("Get security incidents by client", False, str(e))
# ============================================================================
# SECTION 13: Cleanup - Delete Test Data
# ============================================================================
print("\n" + "="*70)
print("SECTION 13: Cleanup - Delete Test Data")
print("="*70 + "\n")
def test_cleanup_all_entities():
"""Clean up all created test entities in the correct order."""
try:
headers = get_auth_headers()
cleanup_count = 0
# Delete in reverse order of dependencies
# Delete security incidents
for entity_id in created_entities["security_incidents"]:
response = client.delete(f"/api/security-incidents/{entity_id}", headers=headers)
if response.status_code == 200:
cleanup_count += 1
# Delete credentials (audit logs remain)
for entity_id in created_entities["credentials"]:
response = client.delete(f"/api/credentials/{entity_id}", headers=headers)
if response.status_code == 200:
cleanup_count += 1
# Delete M365 tenants
for entity_id in created_entities["m365_tenants"]:
response = client.delete(f"/api/m365-tenants/{entity_id}", headers=headers)
if response.status_code == 200:
cleanup_count += 1
# Delete firewall rules
for entity_id in created_entities["firewall_rules"]:
response = client.delete(f"/api/firewall-rules/{entity_id}", headers=headers)
if response.status_code == 200:
cleanup_count += 1
# Delete networks
for entity_id in created_entities["networks"]:
response = client.delete(f"/api/networks/{entity_id}", headers=headers)
if response.status_code == 200:
cleanup_count += 1
# Delete services
for entity_id in created_entities["services"]:
response = client.delete(f"/api/services/{entity_id}", headers=headers)
if response.status_code == 200:
cleanup_count += 1
# Delete infrastructure
for entity_id in created_entities["infrastructure"]:
response = client.delete(f"/api/infrastructure/{entity_id}", headers=headers)
if response.status_code == 200:
cleanup_count += 1
# Delete billable time
for entity_id in created_entities["billable_time"]:
response = client.delete(f"/api/billable-time/{entity_id}", headers=headers)
if response.status_code == 200:
cleanup_count += 1
# Delete tasks
for entity_id in created_entities["tasks"]:
response = client.delete(f"/api/tasks/{entity_id}", headers=headers)
if response.status_code == 200:
cleanup_count += 1
# Delete work items
for entity_id in created_entities["work_items"]:
response = client.delete(f"/api/work-items/{entity_id}", headers=headers)
if response.status_code == 200:
cleanup_count += 1
# Delete sessions
for entity_id in created_entities["sessions"]:
response = client.delete(f"/api/sessions/{entity_id}", headers=headers)
if response.status_code == 200:
cleanup_count += 1
# Delete projects
for entity_id in created_entities["projects"]:
response = client.delete(f"/api/projects/{entity_id}", headers=headers)
if response.status_code == 200:
cleanup_count += 1
# Delete sites
for entity_id in created_entities["sites"]:
response = client.delete(f"/api/sites/{entity_id}", headers=headers)
if response.status_code == 200:
cleanup_count += 1
# Delete clients
for entity_id in created_entities["clients"]:
response = client.delete(f"/api/clients/{entity_id}", headers=headers)
if response.status_code == 200:
cleanup_count += 1
print(f" Cleaned up {cleanup_count} test entities")
log_test(f"Cleanup all test entities ({cleanup_count} deleted)", True)
except Exception as e:
log_test("Cleanup all test entities", False, str(e))
# ============================================================================
# Run All Tests
# ============================================================================
def run_all_tests():
"""Run all test functions."""
print("\n" + "="*70)
print("CLAUDETOOLS PHASE 5 API ENDPOINT TESTS")
print("="*70)
# Section 1: Work Items
test_create_work_items_dependencies()
test_create_work_item()
test_list_work_items()
test_get_work_item()
test_update_work_item()
test_get_work_items_by_client()
# Section 2: Tasks
test_create_task()
test_list_tasks()
test_get_task()
test_update_task()
test_get_tasks_filtering()
# Section 3: Billable Time
test_create_billable_time()
test_list_billable_time()
test_get_billable_time()
test_update_billable_time()
test_get_billable_time_by_session()
# Section 4: Sites
test_create_site()
test_list_sites()
test_get_site()
test_update_site()
test_get_sites_by_client()
# Section 5: Infrastructure
test_create_infrastructure()
test_list_infrastructure()
test_get_infrastructure()
test_update_infrastructure()
test_get_infrastructure_by_site()
# Section 6: Services
test_create_service()
test_list_services()
test_get_service()
test_update_service()
test_get_services_by_client()
# Section 7: Networks
test_create_network()
test_list_networks()
test_get_network()
test_update_network()
test_get_networks_by_site()
# Section 8: Firewall Rules
test_create_firewall_rule()
test_list_firewall_rules()
test_get_firewall_rule()
test_update_firewall_rule()
test_get_firewall_rules_by_infrastructure()
# Section 9: M365 Tenants
test_create_m365_tenant()
test_list_m365_tenants()
test_get_m365_tenant()
test_update_m365_tenant()
test_get_m365_tenants_by_client()
# Section 10: Credentials (with encryption)
test_create_credential_password()
test_create_credential_api_key()
test_create_credential_oauth()
test_list_credentials()
test_get_credential()
test_update_credential()
test_get_credentials_by_client()
# Section 11: Credential Audit Logs (read-only)
test_list_credential_audit_logs()
test_get_credential_audit_logs_by_credential()
test_get_credential_audit_logs_by_user()
# Section 12: Security Incidents
test_create_security_incident()
test_list_security_incidents()
test_get_security_incident()
test_update_security_incident()
test_get_security_incidents_by_client()
# Section 13: Cleanup
test_cleanup_all_entities()
if __name__ == "__main__":
print("\n>> Starting ClaudeTools Phase 5 API Test Suite...")
try:
run_all_tests()
# Print summary
print("\n" + "="*70)
print("TEST SUMMARY")
print("="*70)
print(f"\nTotal Tests: {tests_passed + tests_failed}")
print(f"Passed: {tests_passed}")
print(f"Failed: {tests_failed}")
if tests_failed > 0:
print("\nFAILED TESTS:")
for name, passed, error in test_results:
if not passed:
print(f" - {name}")
if error:
print(f" Error: {error}")
if tests_failed == 0:
print("\n>> All tests passed!")
sys.exit(0)
else:
print(f"\n>> {tests_failed} test(s) failed")
sys.exit(1)
except Exception as e:
print(f"\n>> Fatal error running tests: {e}")
import traceback
traceback.print_exc()
sys.exit(1)