forked from Rockachopa/Timmy-time-dashboard
Move 97 test files from flat tests/ into 13 subdirectories: tests/dashboard/ (8 files — routes, mobile, mission control) tests/swarm/ (17 files — coordinator, docker, routing, tasks) tests/timmy/ (12 files — agent, backends, CLI, tools) tests/self_coding/ (14 files — git safety, indexer, self-modify) tests/lightning/ (3 files — L402, LND, interface) tests/creative/ (8 files — assembler, director, image/music/video) tests/integrations/ (10 files — chat bridge, telegram, voice, websocket) tests/mcp/ (4 files — bootstrap, discovery, executor) tests/spark/ (3 files — engine, tools, events) tests/hands/ (3 files — registry, oracle, phase5) tests/scripture/ (1 file) tests/infrastructure/ (3 files — router cascade, API) tests/security/ (3 files — XSS, regression) Fix Path(__file__) reference in test_mobile_scenarios.py for new depth. Add __init__.py to all test subdirectories. Tests: 1503 passed, 9 failed (pre-existing), 53 errors (pre-existing) https://claude.ai/code/session_019oMFNvD8uSGSSmBMGkBfQN
191 lines
6.3 KiB
Python
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"])
|