Files
Timmy-time-dashboard/tests/test_creative_director.py
Claude 1103da339c feat: add full creative studio + DevOps tools (Pixel, Lyra, Reel personas)
Adds 3 new personas (Pixel, Lyra, Reel) and 5 new tool modules:

- Git/DevOps tools (GitPython): clone, status, diff, log, blame, branch,
  add, commit, push, pull, stash — wired to Forge and Helm personas
- Image generation (FLUX via diffusers): text-to-image, storyboards,
  variations — Pixel persona
- Music generation (ACE-Step 1.5): full songs with vocals+instrumentals,
  instrumental tracks, vocal-only tracks — Lyra persona
- Video generation (Wan 2.1 via diffusers): text-to-video, image-to-video
  clips — Reel persona
- Creative Director pipeline: multi-step orchestration that chains
  storyboard → music → video → assembly into 3+ minute final videos
- Video assembler (MoviePy + FFmpeg): stitch clips, overlay audio,
  title cards, subtitles, final export

Also includes:
- Spark Intelligence tool-level + creative pipeline event capture
- Creative Studio dashboard page (/creative/ui) with 4 tabs
- Config settings for all new models and output directories
- pyproject.toml creative optional extra for GPU dependencies
- 107 new tests covering all modules (624 total, all passing)

https://claude.ai/code/session_01KJm6jQkNi3aA3yoQJn636c
2026-02-24 16:31:47 +00:00

191 lines
6.3 KiB
Python

"""Tests for creative.director — Creative Director pipeline.
Tests project management, pipeline orchestration, and tool catalogue.
All AI model calls are mocked.
"""
import pytest
from unittest.mock import patch, MagicMock
from creative.director import (
create_project,
get_project,
list_projects,
run_storyboard,
run_music,
run_video_generation,
run_assembly,
run_full_pipeline,
CreativeProject,
DIRECTOR_TOOL_CATALOG,
_projects,
)
@pytest.fixture(autouse=True)
def clear_projects():
"""Clear project store between tests."""
_projects.clear()
yield
_projects.clear()
@pytest.fixture
def sample_project(tmp_path):
"""Create a sample project with scenes."""
with patch("creative.director._project_dir", return_value=tmp_path):
result = create_project(
title="Test Video",
description="A test creative project",
scenes=[
{"description": "A sunrise over mountains"},
{"description": "A river flowing through a valley"},
{"description": "A sunset over the ocean"},
],
lyrics="La la la, the sun rises high",
)
return result["project"]["id"]
class TestCreateProject:
def test_creates_project(self, tmp_path):
with patch("creative.director._project_dir", return_value=tmp_path):
result = create_project("My Video", "A cool video")
assert result["success"]
assert result["project"]["title"] == "My Video"
assert result["project"]["status"] == "planning"
def test_project_has_id(self, tmp_path):
with patch("creative.director._project_dir", return_value=tmp_path):
result = create_project("Test", "Test")
assert len(result["project"]["id"]) == 12
def test_project_with_scenes(self, tmp_path):
with patch("creative.director._project_dir", return_value=tmp_path):
result = create_project(
"Scenes", "With scenes",
scenes=[{"description": "Scene 1"}, {"description": "Scene 2"}],
)
assert result["project"]["scene_count"] == 2
class TestGetProject:
def test_get_existing(self, sample_project):
result = get_project(sample_project)
assert result is not None
assert result["title"] == "Test Video"
def test_get_nonexistent(self):
assert get_project("bogus") is None
class TestListProjects:
def test_empty(self):
assert list_projects() == []
def test_with_projects(self, sample_project, tmp_path):
with patch("creative.director._project_dir", return_value=tmp_path):
create_project("Second", "desc")
assert len(list_projects()) == 2
class TestRunStoryboard:
def test_fails_without_project(self):
result = run_storyboard("bogus")
assert not result["success"]
assert "not found" in result["error"]
def test_fails_without_scenes(self, tmp_path):
with patch("creative.director._project_dir", return_value=tmp_path):
result = create_project("Empty", "No scenes")
pid = result["project"]["id"]
result = run_storyboard(pid)
assert not result["success"]
assert "No scenes" in result["error"]
def test_generates_frames(self, sample_project, tmp_path):
mock_result = {
"success": True,
"frame_count": 3,
"frames": [
{"path": "/fake/1.png", "scene_index": 0, "prompt": "sunrise"},
{"path": "/fake/2.png", "scene_index": 1, "prompt": "river"},
{"path": "/fake/3.png", "scene_index": 2, "prompt": "sunset"},
],
}
with patch("tools.image_tools.generate_storyboard", return_value=mock_result):
with patch("creative.director._save_project"):
result = run_storyboard(sample_project)
assert result["success"]
assert result["frame_count"] == 3
class TestRunMusic:
def test_fails_without_project(self):
result = run_music("bogus")
assert not result["success"]
def test_generates_track(self, sample_project):
mock_result = {
"success": True, "path": "/fake/song.wav",
"genre": "pop", "duration": 60,
}
with patch("tools.music_tools.generate_song", return_value=mock_result):
with patch("creative.director._save_project"):
result = run_music(sample_project, genre="pop")
assert result["success"]
assert result["path"] == "/fake/song.wav"
class TestRunVideoGeneration:
def test_fails_without_project(self):
result = run_video_generation("bogus")
assert not result["success"]
def test_generates_clips(self, sample_project):
mock_clip = {
"success": True, "path": "/fake/clip.mp4",
"duration": 5,
}
with patch("tools.video_tools.generate_video_clip", return_value=mock_clip):
with patch("tools.video_tools.image_to_video", return_value=mock_clip):
with patch("creative.director._save_project"):
result = run_video_generation(sample_project)
assert result["success"]
assert result["clip_count"] == 3
class TestRunAssembly:
def test_fails_without_project(self):
result = run_assembly("bogus")
assert not result["success"]
def test_fails_without_clips(self, sample_project):
result = run_assembly(sample_project)
assert not result["success"]
assert "No video clips" in result["error"]
class TestCreativeProject:
def test_to_dict(self):
p = CreativeProject(title="Test", description="Desc")
d = p.to_dict()
assert d["title"] == "Test"
assert d["status"] == "planning"
assert d["scene_count"] == 0
assert d["has_storyboard"] is False
assert d["has_music"] is False
class TestDirectorToolCatalog:
def test_catalog_has_all_tools(self):
expected = {
"create_project", "run_storyboard", "run_music",
"run_video_generation", "run_assembly", "run_full_pipeline",
}
assert expected == set(DIRECTOR_TOOL_CATALOG.keys())
def test_catalog_entries_callable(self):
for tool_id, info in DIRECTOR_TOOL_CATALOG.items():
assert callable(info["fn"])