ppt-tool/backend/tests/test_brand_enforcement.py
Vadym Samoilenko 76a4e41e3b Phase 7: Testing Suite — backend unit tests + Cypress E2E framework
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>
2026-02-26 16:49:23 +00:00

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"