fix: correct tojson|safe usage in templates and update validator

- Remove |safe from |tojson in HTML attributes (x-data) - quotes must
  become " for browsers to parse correctly
- Update LANG-002 and LANG-003 architecture rules to document correct
  |tojson usage patterns:
  - HTML attributes: |tojson (no |safe)
  - Script blocks: |tojson|safe
- Fix validator to warn when |tojson|safe is used in x-data (breaks
  HTML attribute parsing)
- Improve code quality across services, APIs, and tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-13 22:59:51 +01:00
parent 94d268f330
commit 9920430b9e
123 changed files with 1408 additions and 840 deletions

View File

@@ -3,6 +3,7 @@
Tests the /api/v1/admin/auth/* endpoints.
"""
from datetime import UTC, datetime, timedelta
import pytest
@@ -46,7 +47,10 @@ class TestAdminAuthAPI:
"""Test login with wrong password."""
response = client.post(
"/api/v1/admin/auth/login",
json={"email_or_username": test_admin.username, "password": "wrongpassword"},
json={
"email_or_username": test_admin.username,
"password": "wrongpassword",
},
)
assert response.status_code == 401
@@ -73,7 +77,10 @@ class TestAdminAuthAPI:
try:
response = client.post(
"/api/v1/admin/auth/login",
json={"email_or_username": test_admin.username, "password": "adminpass123"},
json={
"email_or_username": test_admin.username,
"password": "adminpass123",
},
)
assert response.status_code == 403
@@ -153,7 +160,8 @@ class TestAdminAuthAPI:
)
response = client.get(
"/api/v1/admin/auth/me", headers={"Authorization": f"Bearer {expired_token}"}
"/api/v1/admin/auth/me",
headers={"Authorization": f"Bearer {expired_token}"},
)
assert response.status_code == 401

View File

@@ -4,6 +4,7 @@ Integration tests for admin dashboard and statistics endpoints.
Tests the /api/v1/admin/dashboard/* endpoints.
"""
import pytest
@@ -34,7 +35,9 @@ class TestAdminDashboardAPI:
data = response.json()
assert data["error_code"] == "ADMIN_REQUIRED"
def test_get_comprehensive_stats(self, client, admin_headers, test_marketplace_product):
def test_get_comprehensive_stats(
self, client, admin_headers, test_marketplace_product
):
"""Test getting comprehensive statistics."""
response = client.get("/api/v1/admin/dashboard/stats", headers=admin_headers)
@@ -47,7 +50,9 @@ class TestAdminDashboardAPI:
assert "unique_vendors" in data
assert data["total_products"] >= 0
def test_get_marketplace_stats(self, client, admin_headers, test_marketplace_product):
def test_get_marketplace_stats(
self, client, admin_headers, test_marketplace_product
):
"""Test getting marketplace statistics."""
response = client.get(
"/api/v1/admin/dashboard/stats/marketplace", headers=admin_headers

View File

@@ -9,8 +9,9 @@ Tests cover:
4. Order management for vendors
"""
from unittest.mock import MagicMock, patch
import pytest
from unittest.mock import patch, MagicMock
@pytest.mark.integration
@@ -20,13 +21,9 @@ from unittest.mock import patch, MagicMock
class TestAdminLetzshopVendorsAPI:
"""Test admin Letzshop vendor overview endpoints."""
def test_list_vendors_letzshop_status(
self, client, admin_headers, test_vendor
):
def test_list_vendors_letzshop_status(self, client, admin_headers, test_vendor):
"""Test listing vendors with Letzshop status."""
response = client.get(
"/api/v1/admin/letzshop/vendors", headers=admin_headers
)
response = client.get("/api/v1/admin/letzshop/vendors", headers=admin_headers)
assert response.status_code == 200
data = response.json()
@@ -41,12 +38,10 @@ class TestAdminLetzshopVendorsAPI:
break
# Vendor may not be found if inactive, that's ok
def test_list_vendors_configured_only(
self, client, db, admin_headers, test_vendor
):
def test_list_vendors_configured_only(self, client, db, admin_headers, test_vendor):
"""Test listing only configured vendors."""
from models.database.letzshop import VendorLetzshopCredentials
from app.utils.encryption import encrypt_value
from models.database.letzshop import VendorLetzshopCredentials
# Configure credentials for test vendor
credentials = VendorLetzshopCredentials(
@@ -87,9 +82,7 @@ class TestAdminLetzshopCredentialsAPI:
assert response.status_code == 404
def test_create_vendor_credentials(
self, client, admin_headers, test_vendor
):
def test_create_vendor_credentials(self, client, admin_headers, test_vendor):
"""Test creating credentials for a vendor."""
response = client.post(
f"/api/v1/admin/letzshop/vendors/{test_vendor.id}/credentials",
@@ -129,9 +122,7 @@ class TestAdminLetzshopCredentialsAPI:
data = response.json()
assert data["vendor_id"] == test_vendor.id
def test_update_vendor_credentials(
self, client, admin_headers, test_vendor
):
def test_update_vendor_credentials(self, client, admin_headers, test_vendor):
"""Test partial update of vendor credentials."""
# Create first
client.post(
@@ -151,9 +142,7 @@ class TestAdminLetzshopCredentialsAPI:
data = response.json()
assert data["auto_sync_enabled"] is True
def test_delete_vendor_credentials(
self, client, admin_headers, test_vendor
):
def test_delete_vendor_credentials(self, client, admin_headers, test_vendor):
"""Test deleting vendor credentials."""
# Create first
client.post(
@@ -218,9 +207,7 @@ class TestAdminLetzshopConnectionAPI:
assert data["success"] is True
@patch("app.services.letzshop.client_service.requests.Session.post")
def test_test_api_key_directly(
self, mock_post, client, admin_headers
):
def test_test_api_key_directly(self, mock_post, client, admin_headers):
"""Test any API key without associating with vendor."""
mock_response = MagicMock()
mock_response.status_code = 200
@@ -245,9 +232,7 @@ class TestAdminLetzshopConnectionAPI:
class TestAdminLetzshopOrdersAPI:
"""Test admin Letzshop order management endpoints."""
def test_list_vendor_orders_empty(
self, client, admin_headers, test_vendor
):
def test_list_vendor_orders_empty(self, client, admin_headers, test_vendor):
"""Test listing vendor orders when none exist."""
response = client.get(
f"/api/v1/admin/letzshop/vendors/{test_vendor.id}/orders",
@@ -259,9 +244,7 @@ class TestAdminLetzshopOrdersAPI:
assert data["orders"] == []
assert data["total"] == 0
def test_list_vendor_orders_with_data(
self, client, db, admin_headers, test_vendor
):
def test_list_vendor_orders_with_data(self, client, db, admin_headers, test_vendor):
"""Test listing vendor orders with data."""
from models.database.letzshop import LetzshopOrder
@@ -288,9 +271,7 @@ class TestAdminLetzshopOrdersAPI:
assert data["orders"][0]["customer_email"] == "admin-test@example.com"
@patch("app.services.letzshop.client_service.requests.Session.post")
def test_trigger_vendor_sync(
self, mock_post, client, admin_headers, test_vendor
):
def test_trigger_vendor_sync(self, mock_post, client, admin_headers, test_vendor):
"""Test triggering sync for a vendor."""
# Mock response
mock_response = MagicMock()
@@ -343,9 +324,7 @@ class TestAdminLetzshopOrdersAPI:
class TestAdminLetzshopAccessControl:
"""Test admin access control for Letzshop endpoints."""
def test_non_admin_cannot_access(
self, client, auth_headers, test_vendor
):
def test_non_admin_cannot_access(self, client, auth_headers, test_vendor):
"""Test that non-admin users cannot access admin endpoints."""
response = client.get(
"/api/v1/admin/letzshop/vendors",
@@ -368,9 +347,7 @@ class TestAdminLetzshopAccessControl:
class TestAdminLetzshopExportAPI:
"""Test admin Letzshop product export endpoints."""
def test_export_vendor_products_empty(
self, client, admin_headers, test_vendor
):
def test_export_vendor_products_empty(self, client, admin_headers, test_vendor):
"""Test exporting products when vendor has no products."""
response = client.get(
f"/api/v1/admin/vendors/{test_vendor.id}/export/letzshop",
@@ -387,11 +364,11 @@ class TestAdminLetzshopExportAPI:
self, client, db, admin_headers, test_vendor
):
"""Test exporting products with actual data."""
from models.database.product import Product
from models.database.marketplace_product import MarketplaceProduct
from models.database.marketplace_product_translation import (
MarketplaceProductTranslation,
)
from models.database.product import Product
# Create marketplace product
mp = MarketplaceProduct(
@@ -441,11 +418,11 @@ class TestAdminLetzshopExportAPI:
self, client, db, admin_headers, test_vendor
):
"""Test exporting products in French."""
from models.database.product import Product
from models.database.marketplace_product import MarketplaceProduct
from models.database.marketplace_product_translation import (
MarketplaceProductTranslation,
)
from models.database.product import Product
mp = MarketplaceProduct(
marketplace_product_id="EXPORT-FR-001",
@@ -487,11 +464,11 @@ class TestAdminLetzshopExportAPI:
self, client, db, admin_headers, test_vendor
):
"""Test exporting including inactive products."""
from models.database.product import Product
from models.database.marketplace_product import MarketplaceProduct
from models.database.marketplace_product_translation import (
MarketplaceProductTranslation,
)
from models.database.product import Product
# Create inactive product
mp = MarketplaceProduct(

View File

@@ -3,6 +3,7 @@
Tests the /api/v1/admin/marketplace-import-jobs/* endpoints.
"""
import pytest

View File

@@ -4,6 +4,7 @@ Integration tests for admin marketplace product catalog endpoints.
Tests the /api/v1/admin/products endpoints.
"""
import pytest
@@ -84,9 +85,7 @@ class TestAdminProductsAPI:
data = response.json()
assert data["total"] >= 1
def test_get_products_pagination(
self, client, admin_headers, multiple_products
):
def test_get_products_pagination(self, client, admin_headers, multiple_products):
"""Test admin products pagination."""
# Test first page
response = client.get(

View File

@@ -3,6 +3,7 @@
Tests the /api/v1/admin/users/* endpoints.
"""
import pytest

View File

@@ -4,6 +4,7 @@ Integration tests for admin vendor product catalog endpoints.
Tests the /api/v1/admin/vendor-products endpoints.
"""
import pytest
@@ -88,9 +89,7 @@ class TestAdminVendorProductsAPI:
for product in data["products"]:
assert product["is_featured"] is False
def test_get_vendor_products_pagination(
self, client, admin_headers, test_product
):
def test_get_vendor_products_pagination(self, client, admin_headers, test_product):
"""Test admin vendor products pagination."""
response = client.get(
"/api/v1/admin/vendor-products",
@@ -103,9 +102,7 @@ class TestAdminVendorProductsAPI:
assert data["skip"] == 0
assert data["limit"] == 10
def test_get_vendor_product_stats_admin(
self, client, admin_headers, test_product
):
def test_get_vendor_product_stats_admin(self, client, admin_headers, test_product):
"""Test admin getting vendor product statistics."""
response = client.get(
"/api/v1/admin/vendor-products/stats", headers=admin_headers
@@ -148,9 +145,7 @@ class TestAdminVendorProductsAPI:
vendor_ids = [v["id"] for v in data["vendors"]]
assert test_vendor.id in vendor_ids
def test_get_vendor_product_detail_admin(
self, client, admin_headers, test_product
):
def test_get_vendor_product_detail_admin(self, client, admin_headers, test_product):
"""Test admin getting vendor product detail."""
response = client.get(
f"/api/v1/admin/vendor-products/{test_product.id}",
@@ -175,9 +170,7 @@ class TestAdminVendorProductsAPI:
data = response.json()
assert data["error_code"] == "PRODUCT_NOT_FOUND"
def test_remove_vendor_product_admin(
self, client, admin_headers, test_product, db
):
def test_remove_vendor_product_admin(self, client, admin_headers, test_product, db):
"""Test admin removing product from vendor catalog."""
product_id = test_product.id
@@ -209,9 +202,7 @@ class TestAdminVendorProductsAPI:
data = response.json()
assert data["error_code"] == "PRODUCT_NOT_FOUND"
def test_remove_vendor_product_non_admin(
self, client, auth_headers, test_product
):
def test_remove_vendor_product_non_admin(self, client, auth_headers, test_product):
"""Test non-admin trying to remove product."""
response = client.delete(
f"/api/v1/admin/vendor-products/{test_product.id}",

View File

@@ -3,6 +3,7 @@
Tests the /api/v1/admin/vendors/* endpoints.
"""
import pytest