All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 12s
- 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
197 lines
6.9 KiB
Python
197 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
|
# /// script
|
|
# requires-python = ">=3.10"
|
|
# dependencies = ["pytest>=7.0"]
|
|
# ///
|
|
"""Tests for parse-feedback.py"""
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
SCRIPT = str(Path(__file__).parent.parent / "parse-feedback.py")
|
|
|
|
|
|
def run_script(input_data: dict | str | None = None, extra_args: list[str] | None = None) -> tuple[int, dict]:
|
|
"""Run parse-feedback.py with stdin input and return (exit_code, parsed_json)."""
|
|
cmd = [sys.executable, SCRIPT, "--stdin"]
|
|
if extra_args:
|
|
cmd.extend(extra_args)
|
|
|
|
input_str = json.dumps(input_data) if isinstance(input_data, dict) else (input_data or "")
|
|
result = subprocess.run(cmd, input=input_str, capture_output=True, text=True)
|
|
try:
|
|
output = json.loads(result.stdout)
|
|
except json.JSONDecodeError:
|
|
output = {"raw_stdout": result.stdout, "raw_stderr": result.stderr}
|
|
return result.returncode, output
|
|
|
|
|
|
def test_valid_minimal_input():
|
|
"""Minimal valid input: just feedback_text."""
|
|
code, output = run_script({"feedback_text": "The guitar is too loud"})
|
|
assert code == 0, f"Expected exit 0, got {code}: {output}"
|
|
assert output["status"] == "pass"
|
|
assert output["parsed"]["feedback_text"] == "The guitar is too loud"
|
|
assert output["summary"]["total"] == 0
|
|
|
|
|
|
def test_valid_full_input():
|
|
"""Full valid input with all optional fields."""
|
|
data = {
|
|
"feedback_text": "It feels too polished",
|
|
"original_style_prompt": "indie folk, acoustic, warm",
|
|
"original_lyrics": "[Verse]\nSome lyrics here",
|
|
"band_profile": "midnight-wanderers",
|
|
"model": "v5 Pro",
|
|
"slider_settings": {"weirdness": 45, "style_influence": 60},
|
|
"intent": "I wanted a raw, intimate feel",
|
|
"feedback_type": "clear",
|
|
"dimensions": ["production", "vocals"],
|
|
}
|
|
code, output = run_script(data)
|
|
assert code == 0
|
|
assert output["status"] == "pass"
|
|
assert output["parsed"]["context"]["model"] == "v5 Pro"
|
|
assert output["parsed"]["context"]["band_profile"] == "midnight-wanderers"
|
|
assert output["parsed"]["pre_categorized"]["feedback_type"] == "clear"
|
|
assert output["parsed"]["pre_categorized"]["dimensions"] == ["production", "vocals"]
|
|
|
|
|
|
def test_missing_feedback_text():
|
|
"""Missing feedback_text should fail."""
|
|
code, output = run_script({"model": "v5 Pro"})
|
|
assert code == 1
|
|
assert output["status"] == "fail"
|
|
assert output["summary"]["critical"] >= 1
|
|
|
|
|
|
def test_empty_feedback_text():
|
|
"""Empty feedback_text should fail."""
|
|
code, output = run_script({"feedback_text": " "})
|
|
assert code == 1
|
|
assert output["status"] == "fail"
|
|
assert output["summary"]["critical"] >= 1
|
|
|
|
|
|
def test_unrecognized_model_info():
|
|
"""Unrecognized model should produce an info finding and still pass."""
|
|
code, output = run_script({"feedback_text": "Sounds off", "model": "v99 Ultra"})
|
|
assert code == 0
|
|
assert output["status"] == "pass", f"Expected pass (info-only findings), got {output['status']}"
|
|
info_findings = [f for f in output["findings"] if f["severity"] == "info"]
|
|
assert len(info_findings) >= 1
|
|
assert "Unrecognized model" in info_findings[0]["issue"]
|
|
assert "informational" in info_findings[0]["fix"]
|
|
|
|
|
|
def test_invalid_dimension():
|
|
"""Invalid dimension should produce a low-severity finding but pass."""
|
|
code, output = run_script({"feedback_text": "Too bright", "dimensions": ["brightness"]})
|
|
assert code == 0
|
|
assert output["status"] == "warning"
|
|
assert output["summary"]["low"] >= 1
|
|
|
|
|
|
def test_invalid_feedback_type():
|
|
"""Invalid feedback_type should produce a warning."""
|
|
code, output = run_script({"feedback_text": "Hmm", "feedback_type": "confused"})
|
|
assert code == 0
|
|
assert output["status"] == "warning"
|
|
|
|
|
|
def test_invalid_slider_range():
|
|
"""Slider value out of range should warn."""
|
|
code, output = run_script({
|
|
"feedback_text": "Off",
|
|
"slider_settings": {"weirdness": 150},
|
|
})
|
|
assert code == 0
|
|
assert output["status"] == "warning"
|
|
assert output["summary"]["medium"] >= 1
|
|
|
|
|
|
def test_invalid_json_input():
|
|
"""Non-JSON input should fail."""
|
|
code, output = run_script("this is not json")
|
|
assert code == 1
|
|
assert output["status"] == "fail"
|
|
|
|
|
|
def test_non_object_json():
|
|
"""JSON array (not object) should fail."""
|
|
cmd = [sys.executable, SCRIPT, "--stdin"]
|
|
result = subprocess.run(cmd, input="[1, 2, 3]", capture_output=True, text=True)
|
|
assert result.returncode == 1
|
|
output = json.loads(result.stdout)
|
|
assert output["status"] == "fail"
|
|
|
|
|
|
def test_dimensions_not_array():
|
|
"""dimensions as non-array should produce high severity finding."""
|
|
code, output = run_script({"feedback_text": "Bad", "dimensions": "vocals"})
|
|
assert code == 1
|
|
assert output["status"] == "fail"
|
|
assert output["summary"]["high"] >= 1
|
|
|
|
|
|
def test_empty_context_stripped():
|
|
"""Empty optional context fields should be stripped from output."""
|
|
code, output = run_script({"feedback_text": "Good stuff"})
|
|
assert code == 0
|
|
# Context should only have non-empty fields
|
|
assert "model" not in output["parsed"]["context"]
|
|
assert "band_profile" not in output["parsed"]["context"]
|
|
|
|
|
|
def test_technical_feedback_type():
|
|
"""'technical' should be a valid feedback type."""
|
|
code, output = run_script({"feedback_text": "There are artifacts", "feedback_type": "technical"})
|
|
assert code == 0
|
|
assert output["status"] == "pass"
|
|
assert output["summary"]["total"] == 0
|
|
|
|
|
|
def test_length_dimension_valid():
|
|
"""'length' should be a valid dimension."""
|
|
code, output = run_script({"feedback_text": "Song is too short", "dimensions": ["length"]})
|
|
assert code == 0
|
|
assert output["status"] == "pass"
|
|
assert output["summary"]["low"] == 0
|
|
|
|
|
|
def test_quality_dimension_valid():
|
|
"""'quality' should be a valid dimension."""
|
|
code, output = run_script({"feedback_text": "Audio has clipping", "dimensions": ["quality"]})
|
|
assert code == 0
|
|
assert output["status"] == "pass"
|
|
assert output["summary"]["low"] == 0
|
|
|
|
|
|
def test_unrecognized_model_passes_through():
|
|
"""Unrecognized model should still appear in parsed output context."""
|
|
code, output = run_script({"feedback_text": "Test", "model": "v99 Ultra"})
|
|
assert code == 0
|
|
assert output["parsed"]["context"]["model"] == "v99 Ultra"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
tests = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
|
|
passed = 0
|
|
failed = 0
|
|
for test in tests:
|
|
try:
|
|
test()
|
|
passed += 1
|
|
print(f" PASS: {test.__name__}")
|
|
except AssertionError as e:
|
|
failed += 1
|
|
print(f" FAIL: {test.__name__}: {e}")
|
|
except Exception as e:
|
|
failed += 1
|
|
print(f" ERROR: {test.__name__}: {e}")
|
|
|
|
print(f"\n{passed} passed, {failed} failed out of {len(tests)} tests")
|
|
sys.exit(1 if failed else 0)
|