Files
orion/tests/unit/models/schema/test_vendor.py
Samir Boulahtit d7a0ff8818 refactor: complete module-driven architecture migration
This commit completes the migration to a fully module-driven architecture:

## Models Migration
- Moved all domain models from models/database/ to their respective modules:
  - tenancy: User, Admin, Vendor, Company, Platform, VendorDomain, etc.
  - cms: MediaFile, VendorTheme
  - messaging: Email, VendorEmailSettings, VendorEmailTemplate
  - core: AdminMenuConfig
- models/database/ now only contains Base and TimestampMixin (infrastructure)

## Schemas Migration
- Moved all domain schemas from models/schema/ to their respective modules:
  - tenancy: company, vendor, admin, team, vendor_domain
  - cms: media, image, vendor_theme
  - messaging: email
- models/schema/ now only contains base.py and auth.py (infrastructure)

## Routes Migration
- Moved admin routes from app/api/v1/admin/ to modules:
  - menu_config.py -> core module
  - modules.py -> tenancy module
  - module_config.py -> tenancy module
- app/api/v1/admin/ now only aggregates auto-discovered module routes

## Menu System
- Implemented module-driven menu system with MenuDiscoveryService
- Extended FrontendType enum: PLATFORM, ADMIN, VENDOR, STOREFRONT
- Added MenuItemDefinition and MenuSectionDefinition dataclasses
- Each module now defines its own menu items in definition.py
- MenuService integrates with MenuDiscoveryService for template rendering

## Documentation
- Updated docs/architecture/models-structure.md
- Updated docs/architecture/menu-management.md
- Updated architecture validation rules for new exceptions

## Architecture Validation
- Updated MOD-019 rule to allow base.py in models/schema/
- Created core module exceptions.py and schemas/ directory
- All validation errors resolved (only warnings remain)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 21:02:56 +01:00

317 lines
10 KiB
Python

# tests/unit/models/schema/test_vendor.py
"""Unit tests for vendor Pydantic schemas."""
import pytest
from pydantic import ValidationError
from app.modules.tenancy.schemas.vendor import (
VendorCreate,
VendorDetailResponse,
VendorListResponse,
VendorResponse,
VendorSummary,
VendorUpdate,
)
@pytest.mark.unit
@pytest.mark.schema
class TestVendorCreateSchema:
"""Test VendorCreate schema validation."""
def test_valid_vendor_create(self):
"""Test valid vendor creation data."""
vendor = VendorCreate(
company_id=1,
vendor_code="TECHSTORE",
subdomain="techstore",
name="Tech Store",
)
assert vendor.company_id == 1
assert vendor.vendor_code == "TECHSTORE"
assert vendor.subdomain == "techstore"
assert vendor.name == "Tech Store"
def test_company_id_required(self):
"""Test company_id is required."""
with pytest.raises(ValidationError) as exc_info:
VendorCreate(
vendor_code="TECHSTORE",
subdomain="techstore",
name="Tech Store",
)
assert "company_id" in str(exc_info.value).lower()
def test_company_id_must_be_positive(self):
"""Test company_id must be > 0."""
with pytest.raises(ValidationError) as exc_info:
VendorCreate(
company_id=0,
vendor_code="TECHSTORE",
subdomain="techstore",
name="Tech Store",
)
assert "company_id" in str(exc_info.value).lower()
def test_vendor_code_required(self):
"""Test vendor_code is required."""
with pytest.raises(ValidationError) as exc_info:
VendorCreate(
company_id=1,
subdomain="techstore",
name="Tech Store",
)
assert "vendor_code" in str(exc_info.value).lower()
def test_vendor_code_uppercase_normalized(self):
"""Test vendor_code is normalized to uppercase."""
vendor = VendorCreate(
company_id=1,
vendor_code="techstore",
subdomain="techstore",
name="Tech Store",
)
assert vendor.vendor_code == "TECHSTORE"
def test_vendor_code_min_length(self):
"""Test vendor_code minimum length (2)."""
with pytest.raises(ValidationError) as exc_info:
VendorCreate(
company_id=1,
vendor_code="T",
subdomain="techstore",
name="Tech Store",
)
assert "vendor_code" in str(exc_info.value).lower()
def test_subdomain_required(self):
"""Test subdomain is required."""
with pytest.raises(ValidationError) as exc_info:
VendorCreate(
company_id=1,
vendor_code="TECHSTORE",
name="Tech Store",
)
assert "subdomain" in str(exc_info.value).lower()
def test_subdomain_uppercase_invalid(self):
"""Test subdomain with uppercase is invalid (validated before normalization)."""
with pytest.raises(ValidationError) as exc_info:
VendorCreate(
company_id=1,
vendor_code="TECHSTORE",
subdomain="TechStore",
name="Tech Store",
)
assert "subdomain" in str(exc_info.value).lower()
def test_subdomain_valid_format(self):
"""Test subdomain with valid format."""
vendor = VendorCreate(
company_id=1,
vendor_code="TECHSTORE",
subdomain="tech-store-123",
name="Tech Store",
)
assert vendor.subdomain == "tech-store-123"
def test_subdomain_invalid_format(self):
"""Test subdomain with invalid characters raises ValidationError."""
with pytest.raises(ValidationError) as exc_info:
VendorCreate(
company_id=1,
vendor_code="TECHSTORE",
subdomain="tech_store!",
name="Tech Store",
)
assert "subdomain" in str(exc_info.value).lower()
def test_name_required(self):
"""Test name is required."""
with pytest.raises(ValidationError) as exc_info:
VendorCreate(
company_id=1,
vendor_code="TECHSTORE",
subdomain="techstore",
)
assert "name" in str(exc_info.value).lower()
def test_name_min_length(self):
"""Test name minimum length (2)."""
with pytest.raises(ValidationError) as exc_info:
VendorCreate(
company_id=1,
vendor_code="TECHSTORE",
subdomain="techstore",
name="T",
)
assert "name" in str(exc_info.value).lower()
def test_optional_fields(self):
"""Test optional fields."""
vendor = VendorCreate(
company_id=1,
vendor_code="TECHSTORE",
subdomain="techstore",
name="Tech Store",
description="Best tech store",
letzshop_csv_url_fr="https://example.com/fr.csv",
contact_email="contact@techstore.com",
website="https://techstore.com",
)
assert vendor.description == "Best tech store"
assert vendor.letzshop_csv_url_fr == "https://example.com/fr.csv"
assert vendor.contact_email == "contact@techstore.com"
@pytest.mark.unit
@pytest.mark.schema
class TestVendorUpdateSchema:
"""Test VendorUpdate schema validation."""
def test_partial_update(self):
"""Test partial update with only some fields."""
update = VendorUpdate(name="New Tech Store")
assert update.name == "New Tech Store"
assert update.subdomain is None
assert update.is_active is None
def test_empty_update_is_valid(self):
"""Test empty update is valid."""
update = VendorUpdate()
assert update.model_dump(exclude_unset=True) == {}
def test_subdomain_normalized_to_lowercase(self):
"""Test subdomain is normalized to lowercase."""
update = VendorUpdate(subdomain="NewSubdomain")
assert update.subdomain == "newsubdomain"
def test_subdomain_stripped(self):
"""Test subdomain is stripped of whitespace."""
update = VendorUpdate(subdomain=" newsubdomain ")
assert update.subdomain == "newsubdomain"
def test_name_min_length(self):
"""Test name minimum length (2)."""
with pytest.raises(ValidationError):
VendorUpdate(name="X")
def test_is_active_update(self):
"""Test is_active can be updated."""
update = VendorUpdate(is_active=False)
assert update.is_active is False
def test_is_verified_update(self):
"""Test is_verified can be updated."""
update = VendorUpdate(is_verified=True)
assert update.is_verified is True
def test_reset_contact_to_company_flag(self):
"""Test reset_contact_to_company flag."""
update = VendorUpdate(reset_contact_to_company=True)
assert update.reset_contact_to_company is True
@pytest.mark.unit
@pytest.mark.schema
class TestVendorResponseSchema:
"""Test VendorResponse schema."""
def test_from_dict(self):
"""Test creating response from dict."""
from datetime import datetime
data = {
"id": 1,
"vendor_code": "TECHSTORE",
"subdomain": "techstore",
"name": "Tech Store",
"description": "Best tech store",
"company_id": 1,
"letzshop_csv_url_fr": None,
"letzshop_csv_url_en": None,
"letzshop_csv_url_de": None,
"is_active": True,
"is_verified": False,
"created_at": datetime.now(),
"updated_at": datetime.now(),
}
response = VendorResponse(**data)
assert response.id == 1
assert response.vendor_code == "TECHSTORE"
assert response.is_active is True
@pytest.mark.unit
@pytest.mark.schema
class TestVendorDetailResponseSchema:
"""Test VendorDetailResponse schema."""
def test_from_dict(self):
"""Test creating detail response from dict."""
from datetime import datetime
data = {
"id": 1,
"vendor_code": "TECHSTORE",
"subdomain": "techstore",
"name": "Tech Store",
"description": None,
"company_id": 1,
"letzshop_csv_url_fr": None,
"letzshop_csv_url_en": None,
"letzshop_csv_url_de": None,
"is_active": True,
"is_verified": True,
"created_at": datetime.now(),
"updated_at": datetime.now(),
# Additional detail fields
"company_name": "Tech Corp",
"owner_email": "owner@techcorp.com",
"owner_username": "owner",
"contact_email": "contact@techstore.com",
"contact_email_inherited": False,
}
response = VendorDetailResponse(**data)
assert response.company_name == "Tech Corp"
assert response.owner_email == "owner@techcorp.com"
assert response.contact_email_inherited is False
@pytest.mark.unit
@pytest.mark.schema
class TestVendorListResponseSchema:
"""Test VendorListResponse schema."""
def test_valid_list_response(self):
"""Test valid list response structure."""
response = VendorListResponse(
vendors=[],
total=0,
skip=0,
limit=10,
)
assert response.vendors == []
assert response.total == 0
@pytest.mark.unit
@pytest.mark.schema
class TestVendorSummarySchema:
"""Test VendorSummary schema."""
def test_from_dict(self):
"""Test creating summary from dict."""
data = {
"id": 1,
"vendor_code": "TECHSTORE",
"subdomain": "techstore",
"name": "Tech Store",
"company_id": 1,
"is_active": True,
}
summary = VendorSummary(**data)
assert summary.id == 1
assert summary.vendor_code == "TECHSTORE"
assert summary.is_active is True