Add testing infrastructure and refactor DAG transformation

Testing setup:
- Add pyproject.toml with mypy and pytest configuration
- Add requirements-dev.txt for development dependencies
- Create tests/ directory with test fixtures
- Add 17 unit tests for DAG transformation pipeline

Type annotations:
- Add app/types.py with TypedDict definitions for node configs
- Add typed helper functions: transform_node, build_input_name_mapping,
  bind_inputs, prepare_dag_for_execution
- Refactor run_recipe to use the new typed helpers

Regression tests for today's bugs:
- test_effect_cid_key_not_effect_hash: Verifies CID uses 'cid' key
- test_source_cid_binding_persists: Verifies bound CIDs in final DAG

Run tests with: pytest tests/ -v

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-01-12 09:37:06 +00:00
parent 0ba1d6e82d
commit 56009c391d
7 changed files with 996 additions and 110 deletions

93
tests/conftest.py Normal file
View File

@@ -0,0 +1,93 @@
"""
Pytest fixtures for art-celery tests.
"""
import pytest
from typing import Any, Dict, List
@pytest.fixture
def sample_compiled_nodes() -> List[Dict[str, Any]]:
"""Sample nodes as produced by the S-expression compiler."""
return [
{
"id": "source_1",
"type": "SOURCE",
"config": {"asset": "cat"},
"inputs": [],
"name": None,
},
{
"id": "source_2",
"type": "SOURCE",
"config": {
"input": True,
"name": "Second Video",
"description": "A user-provided video",
},
"inputs": [],
"name": "second-video",
},
{
"id": "effect_1",
"type": "EFFECT",
"config": {"effect": "identity"},
"inputs": ["source_1"],
"name": None,
},
{
"id": "effect_2",
"type": "EFFECT",
"config": {"effect": "invert", "intensity": 1.0},
"inputs": ["source_2"],
"name": None,
},
{
"id": "sequence_1",
"type": "SEQUENCE",
"config": {},
"inputs": ["effect_1", "effect_2"],
"name": None,
},
]
@pytest.fixture
def sample_registry() -> Dict[str, Dict[str, Any]]:
"""Sample registry with assets and effects."""
return {
"assets": {
"cat": {
"cid": "QmXrj6tSSn1vQXxxEY2Tyoudvt4CeeqR9gGQwSt7WFrhMZ",
"url": "https://example.com/cat.jpg",
},
},
"effects": {
"identity": {
"cid": "QmcWhw6wbHr1GDmorM2KDz8S3yCGTfjuyPR6y8khS2tvko",
},
"invert": {
"cid": "QmPWaW5E5WFrmDjT6w8enqvtJhM8c5jvQu7XN1doHA3Z7J",
},
},
}
@pytest.fixture
def sample_recipe(
sample_compiled_nodes: List[Dict[str, Any]],
sample_registry: Dict[str, Dict[str, Any]],
) -> Dict[str, Any]:
"""Sample compiled recipe."""
return {
"name": "test-recipe",
"version": "1.0",
"description": "A test recipe",
"owner": "@test@example.com",
"registry": sample_registry,
"dag": {
"nodes": sample_compiled_nodes,
"output": "sequence_1",
},
"recipe_id": "Qmtest123",
}