Backend: - conftest with async SQLite DB, factory fixtures for all models - pytest-asyncio config in pyproject.toml - Tests: auth (JWT, dev login), RBAC (access service), audit (query, export), brand enforcement (colors, fonts, logos, contrast), retention (cleanup, purge), content intelligence (regex classifiers), slide mapping, review workflow, analytics data queries Frontend: - Cypress E2E config with baseUrl and viewport settings - Custom commands (devLogin, createPresentation) - E2E specs: login flow, wizard navigation, admin panel, review workflow - Test scripts in package.json Infrastructure: - Makefile: test-e2e and test-all targets Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
168 lines
5.8 KiB
Python
168 lines
5.8 KiB
Python
"""Tests for BrandEnforcementService: color utilities, font/color/logo enforcement."""
|
|
import uuid
|
|
|
|
import pytest
|
|
|
|
from models.pptx_models import (
|
|
PptxAutoShapeBoxModel,
|
|
PptxChartBoxModel,
|
|
PptxFillModel,
|
|
PptxFontModel,
|
|
PptxParagraphModel,
|
|
PptxPositionModel,
|
|
PptxPresentationModel,
|
|
PptxSlideModel,
|
|
PptxTextBoxModel,
|
|
PptxTextRunModel,
|
|
)
|
|
from models.sql.brand_config import BrandConfigModel
|
|
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE
|
|
from services.brand_enforcement_service import (
|
|
BrandEnforcementService,
|
|
_contrast_ratio,
|
|
_hex_to_rgb,
|
|
_relative_luminance,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def svc():
|
|
return BrandEnforcementService()
|
|
|
|
|
|
@pytest.fixture
|
|
def brand() -> BrandConfigModel:
|
|
return BrandConfigModel(
|
|
client_id=uuid.uuid4(),
|
|
primary_colors=["#FF0000", "#00FF00"],
|
|
secondary_colors=["#0000FF"],
|
|
fonts={"heading": "Montserrat", "body": "Open Sans"},
|
|
logo_paths=["/logos/acme.png"],
|
|
)
|
|
|
|
|
|
class TestColorUtilities:
|
|
def test_hex_to_rgb_six_chars(self):
|
|
assert _hex_to_rgb("FF0000") == (255, 0, 0)
|
|
assert _hex_to_rgb("00FF00") == (0, 255, 0)
|
|
|
|
def test_hex_to_rgb_with_hash(self):
|
|
assert _hex_to_rgb("#0000FF") == (0, 0, 255)
|
|
|
|
def test_hex_to_rgb_three_chars(self):
|
|
assert _hex_to_rgb("F00") == (255, 0, 0)
|
|
|
|
def test_white_has_high_luminance(self):
|
|
assert _relative_luminance("FFFFFF") > 0.9
|
|
|
|
def test_black_has_low_luminance(self):
|
|
assert _relative_luminance("000000") < 0.01
|
|
|
|
def test_contrast_ratio_black_vs_white(self):
|
|
white_lum = _relative_luminance("FFFFFF")
|
|
black_lum = _relative_luminance("000000")
|
|
ratio = _contrast_ratio(white_lum, black_lum)
|
|
assert ratio > 20 # Should be ~21:1
|
|
|
|
|
|
class TestBrandColors:
|
|
def test_get_brand_colors_list(self, svc, brand):
|
|
colors = svc.get_brand_colors_list(brand)
|
|
assert "FF0000" in colors
|
|
assert "00FF00" in colors
|
|
assert "0000FF" in colors
|
|
|
|
def test_fallback_when_no_colors(self, svc):
|
|
brand = BrandConfigModel(
|
|
client_id=uuid.uuid4(),
|
|
primary_colors=None,
|
|
secondary_colors=None,
|
|
)
|
|
colors = svc.get_brand_colors_list(brand)
|
|
assert len(colors) > 0 # Defaults
|
|
assert "4472C4" in colors
|
|
|
|
|
|
class TestEnforceOnPptxModel:
|
|
def _make_text_slide(self, font_name="Arial", font_size=14):
|
|
return PptxSlideModel(shapes=[
|
|
PptxTextBoxModel(
|
|
position=PptxPositionModel(left=10, top=10, width=200, height=50),
|
|
paragraphs=[
|
|
PptxParagraphModel(
|
|
text="Hello World",
|
|
font=PptxFontModel(name=font_name, size=font_size, color="000000"),
|
|
)
|
|
],
|
|
)
|
|
])
|
|
|
|
def test_fonts_replaced_with_brand_fonts(self, svc, brand):
|
|
model = PptxPresentationModel(slides=[
|
|
self._make_text_slide("Arial", 32), # Title slide (idx=0) → heading font
|
|
self._make_text_slide("Times", 14), # Content slide → body font
|
|
])
|
|
result = svc.enforce_on_pptx_model(model, brand)
|
|
# Slide 0 (title): heading font
|
|
assert result.slides[0].shapes[0].paragraphs[0].font.name == "Montserrat"
|
|
# Slide 1 (content): body font
|
|
assert result.slides[1].shapes[0].paragraphs[0].font.name == "Open Sans"
|
|
|
|
def test_logo_added_to_content_slides_only(self, svc, brand):
|
|
model = PptxPresentationModel(slides=[
|
|
self._make_text_slide(), # Title slide — no logo
|
|
self._make_text_slide(), # Content slide — should get logo
|
|
self._make_text_slide(), # Another content slide
|
|
])
|
|
result = svc.enforce_on_pptx_model(model, brand)
|
|
|
|
# Slide 0: no logo shape added (still 1 shape)
|
|
assert len(result.slides[0].shapes) == 1
|
|
# Slides 1-2: logo added (2 shapes each)
|
|
assert len(result.slides[1].shapes) == 2
|
|
assert len(result.slides[2].shapes) == 2
|
|
|
|
def test_no_logo_when_no_logo_paths(self, svc):
|
|
brand_no_logo = BrandConfigModel(
|
|
client_id=uuid.uuid4(),
|
|
fonts={"heading": "Inter", "body": "Inter"},
|
|
logo_paths=None,
|
|
)
|
|
model = PptxPresentationModel(slides=[
|
|
self._make_text_slide(),
|
|
self._make_text_slide(),
|
|
])
|
|
result = svc.enforce_on_pptx_model(model, brand_no_logo)
|
|
assert len(result.slides[1].shapes) == 1
|
|
|
|
def test_chart_gets_brand_colors(self, svc, brand):
|
|
chart = PptxChartBoxModel(
|
|
position=PptxPositionModel(left=0, top=0, width=400, height=300),
|
|
chart_type="bar",
|
|
chart_data={"categories": ["A"], "series": [{"name": "S", "values": [1]}]},
|
|
)
|
|
model = PptxPresentationModel(slides=[
|
|
PptxSlideModel(shapes=[chart]),
|
|
])
|
|
result = svc.enforce_on_pptx_model(model, brand)
|
|
assert result.slides[0].shapes[0].brand_colors is not None
|
|
assert "FF0000" in result.slides[0].shapes[0].brand_colors
|
|
|
|
def test_contrast_fix_white_on_white(self, svc, brand):
|
|
"""White text on white bg should be auto-corrected to black."""
|
|
slide = PptxSlideModel(shapes=[
|
|
PptxTextBoxModel(
|
|
position=PptxPositionModel(left=0, top=0, width=100, height=50),
|
|
paragraphs=[
|
|
PptxParagraphModel(
|
|
text="Invisible",
|
|
font=PptxFontModel(name="Arial", size=14, color="FFFFFF"),
|
|
)
|
|
],
|
|
)
|
|
])
|
|
# Default bg is white (FFFFFF)
|
|
model = PptxPresentationModel(slides=[slide])
|
|
result = svc.enforce_on_pptx_model(model, brand)
|
|
# After contrast fix, text should be dark
|
|
assert result.slides[0].shapes[0].paragraphs[0].font.color == "000000"
|