Phase 1 - Vendor Router Integration: - Wire up vendor module routers in app/api/v1/vendor/__init__.py - Use lazy imports via __getattr__ to avoid circular dependencies Phase 2 - Extract Remaining Modules: - Create 6 new module directories: customers, cms, analytics, messaging, dev_tools, monitoring - Each module has definition.py and route wrappers - Update registry to import from extracted modules Phase 3 - Database Table Migration: - Add PlatformModule junction table for auditable module tracking - Add migration zc2m3n4o5p6q7_add_platform_modules_table.py - Add modules relationship to Platform model - Update ModuleService with JSON-to-junction-table migration Phase 4 - Module-Specific Configuration UI: - Add /api/v1/admin/module-config/* endpoints - Add module-config.html template and JS Phase 5 - Integration Tests: - Add tests/fixtures/module_fixtures.py - Add tests/integration/api/v1/admin/test_modules.py - Add tests/integration/api/v1/modules/test_module_access.py Architecture fixes: - Fix JS-003 errors: use ...data() directly in Alpine components - Fix JS-005 warnings: add init() guards to prevent duplicate init - Fix API-001 errors: add MenuActionResponse Pydantic model - Add FE-008 noqa for dynamic number input in template Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
329 lines
12 KiB
Python
329 lines
12 KiB
Python
# tests/integration/api/v1/admin/test_modules.py
|
|
"""
|
|
Integration tests for admin module management endpoints.
|
|
|
|
Tests the /api/v1/admin/modules/* and /api/v1/admin/module-config/* endpoints.
|
|
All endpoints require super admin access.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.api
|
|
@pytest.mark.admin
|
|
@pytest.mark.modules
|
|
class TestAdminModulesAPI:
|
|
"""Tests for admin module management endpoints."""
|
|
|
|
# ========================================================================
|
|
# List Modules Tests
|
|
# ========================================================================
|
|
|
|
def test_list_all_modules(self, client, super_admin_headers):
|
|
"""Test super admin listing all modules."""
|
|
response = client.get("/api/v1/admin/modules", headers=super_admin_headers)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "modules" in data
|
|
assert "total" in data
|
|
assert data["total"] >= 10 # At least 10 modules defined
|
|
|
|
# Check expected modules exist
|
|
module_codes = [m["code"] for m in data["modules"]]
|
|
assert "core" in module_codes
|
|
assert "billing" in module_codes
|
|
assert "inventory" in module_codes
|
|
|
|
def test_list_modules_requires_super_admin(self, client, admin_headers):
|
|
"""Test that listing modules requires super admin."""
|
|
response = client.get("/api/v1/admin/modules", headers=admin_headers)
|
|
|
|
# Should require super admin
|
|
assert response.status_code == 403
|
|
|
|
def test_list_modules_unauthenticated(self, client):
|
|
"""Test that listing modules requires authentication."""
|
|
response = client.get("/api/v1/admin/modules")
|
|
|
|
assert response.status_code == 401
|
|
|
|
# ========================================================================
|
|
# Get Platform Modules Tests
|
|
# ========================================================================
|
|
|
|
def test_get_platform_modules(self, client, super_admin_headers, test_platform):
|
|
"""Test getting modules for a specific platform."""
|
|
response = client.get(
|
|
f"/api/v1/admin/modules/platforms/{test_platform.id}",
|
|
headers=super_admin_headers,
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["platform_id"] == test_platform.id
|
|
assert data["platform_code"] == test_platform.code
|
|
assert "modules" in data
|
|
assert "enabled" in data
|
|
assert "disabled" in data
|
|
|
|
def test_get_platform_modules_not_found(self, client, super_admin_headers):
|
|
"""Test getting modules for non-existent platform."""
|
|
response = client.get(
|
|
"/api/v1/admin/modules/platforms/99999",
|
|
headers=super_admin_headers,
|
|
)
|
|
|
|
assert response.status_code == 404
|
|
|
|
# ========================================================================
|
|
# Enable/Disable Module Tests
|
|
# ========================================================================
|
|
|
|
def test_enable_module(self, client, super_admin_headers, test_platform, db):
|
|
"""Test enabling a module for a platform."""
|
|
# First disable the module via settings
|
|
test_platform.settings = {"enabled_modules": ["core", "platform-admin"]}
|
|
db.commit()
|
|
|
|
response = client.post(
|
|
f"/api/v1/admin/modules/platforms/{test_platform.id}/enable",
|
|
headers=super_admin_headers,
|
|
json={"module_code": "billing"},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert "billing" in data["message"].lower() or "enabled" in data["message"].lower()
|
|
|
|
def test_disable_module(self, client, super_admin_headers, test_platform, db):
|
|
"""Test disabling a module for a platform."""
|
|
# Ensure module is enabled
|
|
test_platform.settings = {"enabled_modules": ["billing", "inventory"]}
|
|
db.commit()
|
|
|
|
response = client.post(
|
|
f"/api/v1/admin/modules/platforms/{test_platform.id}/disable",
|
|
headers=super_admin_headers,
|
|
json={"module_code": "billing"},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
|
|
def test_cannot_disable_core_module(self, client, super_admin_headers, test_platform):
|
|
"""Test that core modules cannot be disabled."""
|
|
response = client.post(
|
|
f"/api/v1/admin/modules/platforms/{test_platform.id}/disable",
|
|
headers=super_admin_headers,
|
|
json={"module_code": "core"},
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
data = response.json()
|
|
assert "core" in data.get("message", "").lower() or "cannot" in data.get("message", "").lower()
|
|
|
|
def test_enable_invalid_module(self, client, super_admin_headers, test_platform):
|
|
"""Test enabling a non-existent module."""
|
|
response = client.post(
|
|
f"/api/v1/admin/modules/platforms/{test_platform.id}/enable",
|
|
headers=super_admin_headers,
|
|
json={"module_code": "invalid_module"},
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
|
|
# ========================================================================
|
|
# Bulk Operations Tests
|
|
# ========================================================================
|
|
|
|
def test_update_platform_modules(self, client, super_admin_headers, test_platform):
|
|
"""Test updating all enabled modules at once."""
|
|
response = client.put(
|
|
f"/api/v1/admin/modules/platforms/{test_platform.id}",
|
|
headers=super_admin_headers,
|
|
json={"module_codes": ["billing", "inventory", "orders"]},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["platform_id"] == test_platform.id
|
|
|
|
# Check that specified modules are enabled
|
|
enabled_codes = [m["code"] for m in data["modules"] if m["is_enabled"]]
|
|
assert "billing" in enabled_codes
|
|
assert "inventory" in enabled_codes
|
|
assert "orders" in enabled_codes
|
|
# Core modules should always be enabled
|
|
assert "core" in enabled_codes
|
|
|
|
def test_enable_all_modules(self, client, super_admin_headers, test_platform):
|
|
"""Test enabling all modules for a platform."""
|
|
response = client.post(
|
|
f"/api/v1/admin/modules/platforms/{test_platform.id}/enable-all",
|
|
headers=super_admin_headers,
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert data["enabled_count"] >= 10
|
|
|
|
def test_disable_optional_modules(self, client, super_admin_headers, test_platform):
|
|
"""Test disabling all optional modules."""
|
|
response = client.post(
|
|
f"/api/v1/admin/modules/platforms/{test_platform.id}/disable-optional",
|
|
headers=super_admin_headers,
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert "core" in data["core_modules"]
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.api
|
|
@pytest.mark.admin
|
|
@pytest.mark.modules
|
|
class TestAdminModuleConfigAPI:
|
|
"""Tests for admin module configuration endpoints."""
|
|
|
|
# ========================================================================
|
|
# Get Module Config Tests
|
|
# ========================================================================
|
|
|
|
def test_get_module_config(self, client, super_admin_headers, test_platform):
|
|
"""Test getting module configuration."""
|
|
response = client.get(
|
|
f"/api/v1/admin/module-config/platforms/{test_platform.id}/modules/billing/config",
|
|
headers=super_admin_headers,
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["module_code"] == "billing"
|
|
assert "config" in data
|
|
assert "schema_info" in data
|
|
assert "defaults" in data
|
|
|
|
def test_get_module_config_has_defaults(self, client, super_admin_headers, test_platform):
|
|
"""Test that module config includes default values."""
|
|
response = client.get(
|
|
f"/api/v1/admin/module-config/platforms/{test_platform.id}/modules/billing/config",
|
|
headers=super_admin_headers,
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# Should have default billing config
|
|
assert "stripe_mode" in data["config"]
|
|
assert "default_trial_days" in data["config"]
|
|
|
|
def test_get_module_config_invalid_module(self, client, super_admin_headers, test_platform):
|
|
"""Test getting config for invalid module."""
|
|
response = client.get(
|
|
f"/api/v1/admin/module-config/platforms/{test_platform.id}/modules/invalid_module/config",
|
|
headers=super_admin_headers,
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
|
|
# ========================================================================
|
|
# Update Module Config Tests
|
|
# ========================================================================
|
|
|
|
def test_update_module_config(self, client, super_admin_headers, test_platform):
|
|
"""Test updating module configuration."""
|
|
response = client.put(
|
|
f"/api/v1/admin/module-config/platforms/{test_platform.id}/modules/billing/config",
|
|
headers=super_admin_headers,
|
|
json={
|
|
"config": {
|
|
"stripe_mode": "live",
|
|
"default_trial_days": 7,
|
|
}
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["config"]["stripe_mode"] == "live"
|
|
assert data["config"]["default_trial_days"] == 7
|
|
|
|
def test_update_module_config_persists(self, client, super_admin_headers, test_platform):
|
|
"""Test that config updates persist across requests."""
|
|
# Update config
|
|
client.put(
|
|
f"/api/v1/admin/module-config/platforms/{test_platform.id}/modules/inventory/config",
|
|
headers=super_admin_headers,
|
|
json={
|
|
"config": {
|
|
"low_stock_threshold": 25,
|
|
}
|
|
},
|
|
)
|
|
|
|
# Fetch again
|
|
response = client.get(
|
|
f"/api/v1/admin/module-config/platforms/{test_platform.id}/modules/inventory/config",
|
|
headers=super_admin_headers,
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["config"]["low_stock_threshold"] == 25
|
|
|
|
# ========================================================================
|
|
# Reset Config Tests
|
|
# ========================================================================
|
|
|
|
def test_reset_module_config(self, client, super_admin_headers, test_platform):
|
|
"""Test resetting module config to defaults."""
|
|
# First set custom config
|
|
client.put(
|
|
f"/api/v1/admin/module-config/platforms/{test_platform.id}/modules/billing/config",
|
|
headers=super_admin_headers,
|
|
json={
|
|
"config": {
|
|
"stripe_mode": "live",
|
|
"default_trial_days": 1,
|
|
}
|
|
},
|
|
)
|
|
|
|
# Reset to defaults
|
|
response = client.post(
|
|
f"/api/v1/admin/module-config/platforms/{test_platform.id}/modules/billing/reset",
|
|
headers=super_admin_headers,
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
# Config should be reset to defaults
|
|
assert data["config"]["stripe_mode"] == "test"
|
|
assert data["config"]["default_trial_days"] == 14
|
|
|
|
# ========================================================================
|
|
# Get Defaults Tests
|
|
# ========================================================================
|
|
|
|
def test_get_config_defaults(self, client, super_admin_headers):
|
|
"""Test getting default config for a module."""
|
|
response = client.get(
|
|
"/api/v1/admin/module-config/defaults/billing",
|
|
headers=super_admin_headers,
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["module_code"] == "billing"
|
|
assert "defaults" in data
|
|
assert "schema_info" in data
|
|
assert data["defaults"]["stripe_mode"] == "test"
|