Files
Momento/.agent/skills/suno-style-prompt-builder/scripts/tests/test-validate-prompt.py
Antigravity bd495be965
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 12s
feat: design system overhaul — sidebar, AI chats, settings, brainstorm, color cleanup
- Sidebar: dynamic brand-accent colors, brainstorm section restyled
- AI chat general: popup panel with expand/collapse, hides when contextual AI open
- AI chat contextual: tabs reordered (Actions first), X close button, height fix
- Settings: all tabs restyled, 6 new color presets (sage, terracotta, iron, etc.)
- Global color cleanup: emerald/orange hardcoded → brand-accent dynamic
- Brainstorm page: orange → brand-accent throughout
- PageEntry animation component added to key pages
- Floating AI button: bg-brand-accent instead of hardcoded black
- i18n: all 15 locales updated with new AI/billing keys
- Billing: freemium quota tracking, BYOK, stripe subscription scaffolding
- Admin: integrated into new design
- AGENTS.md + CLAUDE.md project rules added
2026-05-16 12:59:30 +00:00

225 lines
9.8 KiB
Python

#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = ["pytest>=7.0"]
# ///
"""Tests for validate-prompt.py"""
import importlib.util
import json
import subprocess
import sys
from pathlib import Path
# Load the script as a module
SCRIPT_PATH = Path(__file__).parent.parent / "validate-prompt.py"
spec = importlib.util.spec_from_file_location("validate_prompt", SCRIPT_PATH)
validate_prompt = importlib.util.module_from_spec(spec)
spec.loader.exec_module(validate_prompt)
class TestValidateStylePrompt:
"""Tests for style prompt validation."""
def test_valid_prompt_passes(self):
"""A well-formed prompt under the limit should pass."""
prompt = "indie folk-rock, melancholic warmth, acoustic guitar over ambient pads, breathy male vocal, intimate lo-fi mix"
findings = validate_prompt.validate_style_prompt(prompt)
critical = [f for f in findings if f["severity"] == "critical"]
assert len(critical) == 0
def test_over_1000_chars_is_critical(self):
"""Prompts over 1,000 chars should produce a critical finding."""
prompt = "rock, " * 200 # ~1200 chars
findings = validate_prompt.validate_style_prompt(prompt)
critical = [f for f in findings if f["severity"] == "critical"]
assert len(critical) == 1
assert "1,000" in critical[0]["issue"]
def test_v4_pro_200_char_limit(self):
"""v4 Pro should have a 200-char limit, not 1,000."""
prompt = "rock, warm vocals, gentle acoustic guitar, melancholic mood, wide stereo field, intimate mix, layered harmonies, subtle percussion" * 2
assert len(prompt) > 200
assert len(prompt) < 1000
findings = validate_prompt.validate_style_prompt(prompt, model="v4 Pro")
critical = [f for f in findings if f["severity"] == "critical"]
assert len(critical) == 1
assert "200" in critical[0]["issue"]
assert "v4 Pro" in critical[0]["issue"]
def test_v5_pro_uses_1000_limit(self):
"""v5 Pro should use 1,000-char limit."""
prompt = "rock " + "x" * 300
findings = validate_prompt.validate_style_prompt(prompt, model="v5 Pro")
critical = [f for f in findings if f["severity"] == "critical"]
assert len(critical) == 0
def test_critical_zone_warning(self):
"""Prompts with substantial content beyond 200 chars should warn about critical zone."""
prompt = "rock, warm vocals, " + "x" * 350
findings = validate_prompt.validate_style_prompt(prompt)
zone_warnings = [f for f in findings if "critical zone" in f.get("issue", "").lower()]
assert len(zone_warnings) == 1
def test_near_limit_is_low(self):
"""Prompts at 90-100% of limit should produce a low finding."""
prompt = "x" * 950
# Add a genre keyword to avoid the front-loading warning
prompt = "rock " + "x" * 945
findings = validate_prompt.validate_style_prompt(prompt)
low = [f for f in findings if f["severity"] == "low" and "near" in f.get("issue", "").lower()]
assert len(low) == 1
def test_empty_prompt_is_critical(self):
"""Empty prompts should be critical."""
findings = validate_prompt.validate_style_prompt("")
critical = [f for f in findings if f["severity"] == "critical"]
assert len(critical) == 1
def test_whitespace_only_is_critical(self):
"""Whitespace-only prompts should be critical."""
findings = validate_prompt.validate_style_prompt(" \n ")
critical = [f for f in findings if f["severity"] == "critical"]
assert len(critical) == 1
def test_no_genre_frontloading_warning(self):
"""Prompts without genre in first 200 chars should warn."""
prompt = "warm and beautiful with layered textures and organic feel throughout the production"
findings = validate_prompt.validate_style_prompt(prompt)
medium = [f for f in findings if f["severity"] == "medium" and "genre" in f["issue"].lower()]
assert len(medium) == 1
def test_genre_present_no_warning(self):
"""Prompts with genre early should not warn about front-loading."""
prompt = "indie rock, melancholic, warm production"
findings = validate_prompt.validate_style_prompt(prompt)
genre_warnings = [f for f in findings if "genre" in f.get("issue", "").lower()]
assert len(genre_warnings) == 0
def test_lyric_metatags_detected(self):
"""Section tags in style prompts should be flagged."""
prompt = "indie rock [Verse] warm vocals [Chorus] big harmonies"
findings = validate_prompt.validate_style_prompt(prompt)
high = [f for f in findings if f["severity"] == "high"]
assert len(high) >= 1
assert "metatag" in high[0]["issue"].lower() or "lyric" in high[0]["issue"].lower()
def test_asterisks_detected(self):
"""Asterisks in style prompts should be flagged."""
prompt = "indie rock, *bold vocals*, warm production"
findings = validate_prompt.validate_style_prompt(prompt)
asterisk = [f for f in findings if "asterisk" in f["issue"].lower()]
assert len(asterisk) == 1
class TestValidateExclusionPrompt:
"""Tests for exclusion prompt validation."""
def test_empty_exclusion_is_info(self):
"""Empty exclusion prompts should produce an info finding (optional)."""
findings = validate_prompt.validate_exclusion_prompt("")
assert len(findings) == 1
assert findings[0]["severity"] == "info"
def test_valid_exclusion_passes(self):
"""A reasonable exclusion prompt should pass cleanly."""
findings = validate_prompt.validate_exclusion_prompt("no autotune, no screaming")
high_or_critical = [f for f in findings if f["severity"] in ("critical", "high")]
assert len(high_or_critical) == 0
def test_very_long_exclusion_is_high(self):
"""Exclusion prompts over 300 chars should produce a high finding."""
prompt = "no " + ", no ".join([f"thing{i}" for i in range(60)])
findings = validate_prompt.validate_exclusion_prompt(prompt)
high = [f for f in findings if f["severity"] == "high"]
assert len(high) >= 1
def test_too_many_items_warns(self):
"""More than 5 exclusion items should produce a medium warning."""
prompt = "no guitar, no piano, no drums, no bass, no synth, no vocals"
findings = validate_prompt.validate_exclusion_prompt(prompt)
medium = [f for f in findings if f["severity"] == "medium" and "many" in f["issue"].lower()]
assert len(medium) == 1
def test_vague_terms_caught(self):
"""Vague exclusion terms should be flagged."""
prompt = "no instruments, nothing bad"
findings = validate_prompt.validate_exclusion_prompt(prompt)
vague = [f for f in findings if "vague" in f["issue"].lower()]
assert len(vague) >= 1
class TestBuildReport:
"""Tests for report generation."""
def test_report_structure(self):
"""Report should have all required fields."""
report = validate_prompt.build_report([], [], "test", "", "/test/path")
assert report["script"] == "validate-prompt"
assert report["version"] == "1.1.0"
assert report["status"] == "pass"
assert "findings" in report
assert "summary" in report
assert "metrics" in report
def test_critical_finding_sets_fail(self):
"""Critical findings should set status to fail."""
findings = [{"severity": "critical", "category": "structure", "issue": "test", "fix": "test"}]
report = validate_prompt.build_report(findings, [], "test", "")
assert report["status"] == "fail"
def test_high_finding_sets_warning(self):
"""High findings (without critical) should set status to warning."""
findings = [{"severity": "high", "category": "structure", "issue": "test", "fix": "test"}]
report = validate_prompt.build_report(findings, [], "test", "")
assert report["status"] == "warning"
def test_metrics_include_char_counts(self):
"""Metrics should include character counts."""
report = validate_prompt.build_report([], [], "hello world", "no guitar")
assert report["metrics"]["style_prompt_chars"] == 11
assert report["metrics"]["exclusion_prompt_chars"] == 9
class TestCLI:
"""Tests for command-line interface."""
def test_help_flag(self):
"""--help should exit 0 with usage info."""
result = subprocess.run(
[sys.executable, str(SCRIPT_PATH), "--help"],
capture_output=True, text=True
)
assert result.returncode == 0
assert "validate" in result.stdout.lower()
def test_style_flag_produces_json(self):
"""--style should produce valid JSON output."""
result = subprocess.run(
[sys.executable, str(SCRIPT_PATH), "--style", "indie rock, warm vocals"],
capture_output=True, text=True
)
output = json.loads(result.stdout)
assert output["script"] == "validate-prompt"
assert "findings" in output
def test_model_flag_v4_pro(self):
"""--model 'v4 Pro' should apply 200-char limit."""
prompt = "rock, " * 40 # ~240 chars, over 200 but under 1000
result = subprocess.run(
[sys.executable, str(SCRIPT_PATH), "--style", prompt, "--model", "v4 Pro"],
capture_output=True, text=True
)
output = json.loads(result.stdout)
critical = [f for f in output["findings"] if f["severity"] == "critical"]
assert len(critical) >= 1
assert "200" in critical[0]["issue"]
def test_no_args_exits_2(self):
"""No arguments should exit with code 2."""
result = subprocess.run(
[sys.executable, str(SCRIPT_PATH)],
capture_output=True, text=True
)
assert result.returncode == 2