"""Tests for WooCommerce Plugin (plugins/woocommerce/).
Integration tests covering plugin initialization, configuration validation,
tool specifications, handler delegation, and health checks.
"""
from unittest.mock import AsyncMock
import pytest
from plugins.base import BasePlugin
from plugins.woocommerce.plugin import WooCommercePlugin
# --- WooCommercePlugin Initialization ---
class TestWooCommercePluginInit:
"""Test WooCommerce plugin initialization."""
VALID_CONFIG = {
"url": "https://shop.example.com",
"username": "admin",
"app_password": "xxxx xxxx xxxx xxxx",
}
def test_create_with_valid_config(self):
"""Should initialize with valid credentials."""
plugin = WooCommercePlugin(self.VALID_CONFIG)
assert plugin.project_id is not None
assert plugin.client is not None
assert isinstance(plugin, BasePlugin)
def test_handlers_initialized(self):
"""Should initialize all WooCommerce handlers."""
plugin = WooCommercePlugin(self.VALID_CONFIG)
assert plugin.products is not None
assert plugin.orders is not None
assert plugin.customers is not None
assert plugin.coupons is not None
assert plugin.reports is not None
def test_plugin_name(self):
"""Should return 'woocommerce' as plugin name."""
assert WooCommercePlugin.get_plugin_name() == "woocommerce"
def test_required_config_keys(self):
"""Should require url only (credentials checked at init)."""
keys = WooCommercePlugin.get_required_config_keys()
assert "url" in keys
assert len(keys) == 1
def test_missing_url_raises(self):
"""Should raise ValueError for missing URL."""
config = {"username": "admin", "app_password": "xxxx"}
with pytest.raises(ValueError, match="Missing required configuration"):
WooCommercePlugin(config)
def test_missing_credentials_raises(self):
"""Should raise ConfigurationError for missing credentials."""
from plugins.wordpress.client import ConfigurationError
config = {"url": "https://shop.example.com"}
with pytest.raises(ConfigurationError, match="credentials not configured"):
WooCommercePlugin(config)
def test_custom_project_id(self):
"""Should accept custom project_id."""
plugin = WooCommercePlugin(self.VALID_CONFIG, project_id="wc_myshop")
assert plugin.project_id == "wc_myshop"
def test_auto_generated_project_id(self):
"""Should auto-generate project_id from config."""
plugin = WooCommercePlugin(self.VALID_CONFIG)
assert plugin.project_id.startswith("woocommerce")
def test_uses_wordpress_client(self):
"""Should create a WordPressClient (shared API client)."""
from plugins.wordpress.client import WordPressClient
plugin = WooCommercePlugin(self.VALID_CONFIG)
assert isinstance(plugin.client, WordPressClient)
assert plugin.client.site_url == "https://shop.example.com"
# --- Tool Specifications ---
class TestWooCommerceToolSpecs:
"""Test WooCommerce tool specification generation."""
def test_specs_not_empty(self):
"""Should return non-empty tool specifications."""
specs = WooCommercePlugin.get_tool_specifications()
assert len(specs) > 0
def test_specs_count(self):
"""Should return exactly 28 tool specs."""
specs = WooCommercePlugin.get_tool_specifications()
assert len(specs) == 28
def test_specs_have_required_fields(self):
"""Each spec should have name, method_name, description, schema, scope."""
specs = WooCommercePlugin.get_tool_specifications()
for spec in specs:
assert "name" in spec, "Missing 'name' in spec"
assert "method_name" in spec, f"Missing 'method_name' in {spec.get('name')}"
assert "description" in spec, f"Missing 'description' in {spec.get('name')}"
assert "schema" in spec, f"Missing 'schema' in {spec.get('name')}"
assert "scope" in spec, f"Missing 'scope' in {spec.get('name')}"
def test_specs_scope_values(self):
"""All scopes should be valid (read, write, admin)."""
specs = WooCommercePlugin.get_tool_specifications()
valid_scopes = {"read", "write", "admin"}
for spec in specs:
assert (
spec["scope"] in valid_scopes
), f"Invalid scope '{spec['scope']}' in {spec['name']}"
def test_specs_unique_names(self):
"""All tool names should be unique."""
specs = WooCommercePlugin.get_tool_specifications()
names = [s["name"] for s in specs]
assert len(names) == len(
set(names)
), f"Duplicate names: {[n for n in names if names.count(n) > 1]}"
def test_product_tools_present(self):
"""Should include product management tools."""
specs = WooCommercePlugin.get_tool_specifications()
names = {s["name"] for s in specs}
expected = {
"list_products",
"get_product",
"create_product",
"update_product",
"delete_product",
}
assert expected.issubset(names), f"Missing product tools: {expected - names}"
def test_order_tools_present(self):
"""Should include order management tools."""
specs = WooCommercePlugin.get_tool_specifications()
names = {s["name"] for s in specs}
expected = {
"list_orders",
"get_order",
"create_order",
"update_order_status",
"delete_order",
}
assert expected.issubset(names), f"Missing order tools: {expected - names}"
def test_customer_tools_present(self):
"""Should include customer tools."""
specs = WooCommercePlugin.get_tool_specifications()
names = {s["name"] for s in specs}
assert "list_customers" in names
assert "create_customer" in names
def test_coupon_tools_present(self):
"""Should include coupon tools."""
specs = WooCommercePlugin.get_tool_specifications()
names = {s["name"] for s in specs}
assert "list_coupons" in names
assert "create_coupon" in names
def test_report_tools_present(self):
"""Should include report tools."""
specs = WooCommercePlugin.get_tool_specifications()
names = {s["name"] for s in specs}
assert "get_sales_report" in names
assert "get_top_sellers" in names
def test_no_wordpress_tools_leaked(self):
"""Should NOT include WordPress-core tools (posts, pages, etc)."""
specs = WooCommercePlugin.get_tool_specifications()
names = {s["name"] for s in specs}
wp_tools = {"list_posts", "create_post", "list_categories", "list_media"}
leaked = names & wp_tools
assert len(leaked) == 0, f"WordPress tools leaked into WooCommerce: {leaked}"
# --- Handler Delegation ---
class TestWooCommerceHandlerDelegation:
"""Test that plugin methods delegate to handlers correctly."""
VALID_CONFIG = {
"url": "https://shop.example.com",
"username": "admin",
"app_password": "xxxx xxxx xxxx xxxx",
}
@pytest.fixture
def plugin(self):
return WooCommercePlugin(self.VALID_CONFIG)
@pytest.mark.asyncio
async def test_list_products_delegates(self, plugin):
"""list_products should delegate to products handler."""
plugin.products.list_products = AsyncMock(return_value={"products": []})
result = await plugin.list_products(per_page=10)
plugin.products.list_products.assert_called_once_with(per_page=10)
assert result == {"products": []}
@pytest.mark.asyncio
async def test_create_product_delegates(self, plugin):
"""create_product should delegate to products handler."""
plugin.products.create_product = AsyncMock(return_value={"id": 99})
await plugin.create_product(name="Widget", regular_price="19.99")
plugin.products.create_product.assert_called_once_with(name="Widget", regular_price="19.99")
@pytest.mark.asyncio
async def test_list_orders_delegates(self, plugin):
"""list_orders should delegate to orders handler."""
plugin.orders.list_orders = AsyncMock(return_value=[])
await plugin.list_orders(status="processing")
plugin.orders.list_orders.assert_called_once_with(status="processing")
@pytest.mark.asyncio
async def test_get_order_delegates(self, plugin):
"""get_order should delegate to orders handler."""
plugin.orders.get_order = AsyncMock(return_value={"id": 1, "status": "completed"})
await plugin.get_order(order_id=1)
plugin.orders.get_order.assert_called_once_with(order_id=1)
@pytest.mark.asyncio
async def test_list_customers_delegates(self, plugin):
"""list_customers should delegate to customers handler."""
plugin.customers.list_customers = AsyncMock(return_value=[])
await plugin.list_customers()
plugin.customers.list_customers.assert_called_once()
@pytest.mark.asyncio
async def test_create_coupon_delegates(self, plugin):
"""create_coupon should delegate to coupons handler."""
plugin.coupons.create_coupon = AsyncMock(return_value={"id": 5})
await plugin.create_coupon(code="SAVE10", discount_type="percent")
plugin.coupons.create_coupon.assert_called_once_with(code="SAVE10", discount_type="percent")
@pytest.mark.asyncio
async def test_get_sales_report_delegates(self, plugin):
"""get_sales_report should delegate to reports handler."""
plugin.reports.get_sales_report = AsyncMock(return_value={"total": 1000})
await plugin.get_sales_report(period="month")
plugin.reports.get_sales_report.assert_called_once_with(period="month")
# --- Health Check ---
class TestWooCommerceHealthCheck:
"""Test WooCommerce health check."""
VALID_CONFIG = {
"url": "https://shop.example.com",
"username": "admin",
"app_password": "xxxx xxxx xxxx xxxx",
}
@pytest.fixture
def plugin(self):
return WooCommercePlugin(self.VALID_CONFIG)
@pytest.mark.asyncio
async def test_healthy_when_wc_available(self, plugin):
"""Should report healthy when WooCommerce is available."""
plugin.client.check_woocommerce = AsyncMock(
return_value={"available": True, "version": "8.5.0"}
)
result = await plugin.health_check()
assert result["healthy"] is True
assert result["version"] == "8.5.0"
assert result["plugin_type"] == "woocommerce"
@pytest.mark.asyncio
async def test_unhealthy_when_wc_unavailable(self, plugin):
"""Should report unhealthy when WooCommerce is not available."""
plugin.client.check_woocommerce = AsyncMock(
return_value={"available": False, "version": None}
)
result = await plugin.health_check()
assert result["healthy"] is False
assert "not available" in result["message"]
@pytest.mark.asyncio
async def test_unhealthy_on_exception(self, plugin):
"""Should report unhealthy on network errors."""
plugin.client.check_woocommerce = AsyncMock(side_effect=Exception("Connection refused"))
result = await plugin.health_check()
assert result["healthy"] is False
assert "Connection refused" in result["message"]
# --- Plugin Info ---
class TestWooCommercePluginInfo:
"""Test plugin info methods."""
VALID_CONFIG = {
"url": "https://shop.example.com",
"username": "admin",
"app_password": "xxxx xxxx xxxx xxxx",
}
def test_get_project_info(self):
"""Should return structured project info."""
plugin = WooCommercePlugin(self.VALID_CONFIG, project_id="wc_shop1")
info = plugin.get_project_info()
assert info["project_id"] == "wc_shop1"
assert info["plugin_type"] == "woocommerce"
def test_legacy_get_tools_empty(self):
"""Legacy get_tools should return empty list."""
plugin = WooCommercePlugin(self.VALID_CONFIG)
assert plugin.get_tools() == []