"""Tests for manage_animation tool and CLI commands."""
import asyncio
import json
import pytest
from unittest.mock import patch, MagicMock
from click.testing import CliRunner
from cli.commands.animation import animation
from cli.utils.config import CLIConfig
from services.tools.manage_animation import (
ALL_ACTIONS,
ANIMATOR_ACTIONS,
CONTROLLER_ACTIONS,
CLIP_ACTIONS,
)
# =============================================================================
# Fixtures
# =============================================================================
@pytest.fixture
def runner():
return CliRunner()
@pytest.fixture
def mock_config():
return CLIConfig(
host="127.0.0.1",
port=8080,
timeout=30,
format="text",
unity_instance=None,
)
@pytest.fixture
def mock_success():
return {"success": True, "message": "OK", "data": {}}
# =============================================================================
# Action Lists
# =============================================================================
class TestActionLists:
"""Verify action list completeness and consistency."""
def test_all_actions_includes_all_prefixes(self):
assert set(ALL_ACTIONS) == set(ANIMATOR_ACTIONS + CONTROLLER_ACTIONS + CLIP_ACTIONS)
def test_animator_actions_prefixed(self):
for a in ANIMATOR_ACTIONS:
assert a.startswith("animator_"), f"{a} should start with animator_"
def test_controller_actions_prefixed(self):
for a in CONTROLLER_ACTIONS:
assert a.startswith("controller_"), f"{a} should start with controller_"
def test_clip_actions_prefixed(self):
for a in CLIP_ACTIONS:
assert a.startswith("clip_"), f"{a} should start with clip_"
def test_no_duplicate_actions(self):
assert len(ALL_ACTIONS) == len(set(ALL_ACTIONS))
def test_expected_animator_actions_present(self):
expected = {"animator_get_info", "animator_play", "animator_crossfade",
"animator_set_parameter", "animator_get_parameter",
"animator_set_speed", "animator_set_enabled"}
assert expected.issubset(set(ANIMATOR_ACTIONS))
def test_expected_controller_actions_present(self):
expected = {"controller_create", "controller_add_state", "controller_add_transition",
"controller_add_parameter", "controller_get_info", "controller_assign",
"controller_add_layer", "controller_remove_layer", "controller_set_layer_weight",
"controller_create_blend_tree_1d", "controller_create_blend_tree_2d", "controller_add_blend_tree_child"}
assert expected.issubset(set(CONTROLLER_ACTIONS))
def test_expected_clip_actions_present(self):
expected = {"clip_create", "clip_get_info", "clip_add_curve",
"clip_set_curve", "clip_set_vector_curve",
"clip_create_preset", "clip_assign",
"clip_add_event", "clip_remove_event"}
assert expected.issubset(set(CLIP_ACTIONS))
# =============================================================================
# Tool Validation (Python-side, no Unity)
# =============================================================================
class TestManageAnimationToolValidation:
"""Test action validation in the manage_animation tool function."""
def test_unknown_action_returns_error(self):
from services.tools.manage_animation import manage_animation
ctx = MagicMock()
ctx.get_state = MagicMock(return_value=None)
result = asyncio.run(manage_animation(ctx, action="invalid_action"))
assert result["success"] is False
assert "Unknown action" in result["message"]
def test_unknown_animator_action_suggests_prefix(self):
from services.tools.manage_animation import manage_animation
ctx = MagicMock()
ctx.get_state = MagicMock(return_value=None)
result = asyncio.run(manage_animation(ctx, action="animator_nonexistent"))
assert result["success"] is False
assert "animator_" in result["message"]
def test_unknown_clip_action_suggests_prefix(self):
from services.tools.manage_animation import manage_animation
ctx = MagicMock()
ctx.get_state = MagicMock(return_value=None)
result = asyncio.run(manage_animation(ctx, action="clip_nonexistent"))
assert result["success"] is False
assert "clip_" in result["message"]
def test_unknown_controller_action_suggests_prefix(self):
from services.tools.manage_animation import manage_animation
ctx = MagicMock()
ctx.get_state = MagicMock(return_value=None)
result = asyncio.run(manage_animation(ctx, action="controller_nonexistent"))
assert result["success"] is False
assert "controller_" in result["message"]
def test_no_prefix_action_suggests_valid_prefixes(self):
from services.tools.manage_animation import manage_animation
ctx = MagicMock()
ctx.get_state = MagicMock(return_value=None)
result = asyncio.run(manage_animation(ctx, action="bogus"))
assert result["success"] is False
assert "animator_" in result["message"]
assert "controller_" in result["message"]
assert "clip_" in result["message"]
# =============================================================================
# CLI Command Parameter Building
# =============================================================================
def _get_params(mock_run):
"""Helper to extract the params dict from a mock run_command call."""
return mock_run.call_args[0][1]
class TestAnimatorCLICommands:
"""Verify CLI commands build correct parameter dicts.
Note: _normalize_params moves non-top-level keys into 'properties' sub-dict,
matching the VFX tool pattern. Unity C# side flattens properties into params.
"""
def test_animator_info_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, ["animator", "info", "Player"])
mock_run.assert_called_once()
args = mock_run.call_args
assert args[0][0] == "manage_animation"
params = _get_params(mock_run)
assert params["action"] == "animator_get_info"
assert params["target"] == "Player"
def test_animator_play_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, ["animator", "play", "Player", "Walk"])
params = _get_params(mock_run)
assert params["action"] == "animator_play"
assert params["target"] == "Player"
# stateName goes into properties (non-top-level key)
assert params["properties"]["stateName"] == "Walk"
def test_animator_play_with_layer(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, ["animator", "play", "Player", "Attack", "--layer", "1"])
params = _get_params(mock_run)
assert params["action"] == "animator_play"
assert params["properties"]["layer"] == 1
def test_animator_crossfade_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, ["animator", "crossfade", "Player", "Run", "--duration", "0.5"])
params = _get_params(mock_run)
assert params["action"] == "animator_crossfade"
assert params["target"] == "Player"
assert params["properties"]["stateName"] == "Run"
assert params["properties"]["duration"] == 0.5
def test_animator_set_parameter_float(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, ["animator", "set-parameter", "Player", "Speed", "5.0"])
params = _get_params(mock_run)
assert params["action"] == "animator_set_parameter"
assert params["target"] == "Player"
assert params["properties"]["parameterName"] == "Speed"
assert params["properties"]["value"] == 5.0
def test_animator_set_parameter_with_type(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, ["animator", "set-parameter", "Player", "IsRunning", "true", "--type", "bool"])
params = _get_params(mock_run)
assert params["properties"]["parameterName"] == "IsRunning"
assert params["properties"]["value"] is True
assert params["properties"]["parameterType"] == "bool"
def test_animator_get_parameter(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, ["animator", "get-parameter", "Player", "Speed"])
params = _get_params(mock_run)
assert params["action"] == "animator_get_parameter"
assert params["properties"]["parameterName"] == "Speed"
def test_animator_set_speed(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, ["animator", "set-speed", "Player", "2.0"])
params = _get_params(mock_run)
assert params["action"] == "animator_set_speed"
assert params["properties"]["speed"] == 2.0
def test_search_method_forwarded(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, ["animator", "info", "Player", "--search-method", "by_id"])
params = _get_params(mock_run)
assert params["searchMethod"] == "by_id"
class TestClipCLICommands:
"""Verify clip CLI commands build correct parameter dicts."""
def test_clip_create_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, ["clip", "create", "Assets/Anim/Walk.anim", "--length", "2.0", "--loop"])
params = _get_params(mock_run)
assert params["action"] == "clip_create"
assert params["clipPath"] == "Assets/Anim/Walk.anim"
assert params["properties"]["length"] == 2.0
assert params["properties"]["loop"] is True
def test_clip_info_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, ["clip", "info", "Assets/Anim/Walk.anim"])
params = _get_params(mock_run)
assert params["action"] == "clip_get_info"
assert params["clipPath"] == "Assets/Anim/Walk.anim"
def test_clip_add_curve_parses_keys(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"clip", "add-curve", "Assets/Anim/Bounce.anim",
"--property", "localPosition.y",
"--type", "Transform",
"--keys", "[[0,0],[0.5,2],[1,0]]",
])
params = _get_params(mock_run)
assert params["action"] == "clip_add_curve"
assert params["clipPath"] == "Assets/Anim/Bounce.anim"
# propertyPath, type, keys go into properties (non-top-level)
assert params["properties"]["propertyPath"] == "localPosition.y"
assert params["properties"]["type"] == "Transform"
assert params["properties"]["keys"] == [[0, 0], [0.5, 2], [1, 0]]
def test_clip_assign_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, ["clip", "assign", "Cube", "Assets/Anim/Bounce.anim"])
params = _get_params(mock_run)
assert params["action"] == "clip_assign"
assert params["target"] == "Cube"
assert params["clipPath"] == "Assets/Anim/Bounce.anim"
class TestRawCommand:
"""Verify raw escape-hatch command works correctly."""
def test_raw_with_target_and_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"raw", "animator_play", "Player",
"--params", '{"stateName": "Walk"}',
])
params = _get_params(mock_run)
assert params["action"] == "animator_play"
assert params["target"] == "Player"
assert params["properties"]["stateName"] == "Walk"
def test_raw_with_clip_path(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"raw", "clip_create",
"--clip-path", "Assets/Anim/Test.anim",
"--params", '{"length": 2.0}',
])
params = _get_params(mock_run)
assert params["action"] == "clip_create"
assert params["clipPath"] == "Assets/Anim/Test.anim"
assert params["properties"]["length"] == 2.0
# =============================================================================
# Controller CLI Commands
# =============================================================================
class TestControllerCLICommands:
"""Verify controller CLI commands build correct parameter dicts."""
def test_controller_create_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, ["controller", "create", "Assets/Anim/Player.controller"])
params = _get_params(mock_run)
assert params["action"] == "controller_create"
assert params["controllerPath"] == "Assets/Anim/Player.controller"
def test_controller_add_state_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"controller", "add-state", "Assets/Anim/Player.controller", "Walk",
"--clip-path", "Assets/Anim/Walk.anim",
"--speed", "1.5",
])
params = _get_params(mock_run)
assert params["action"] == "controller_add_state"
assert params["controllerPath"] == "Assets/Anim/Player.controller"
assert params["clipPath"] == "Assets/Anim/Walk.anim"
assert params["properties"]["stateName"] == "Walk"
assert params["properties"]["speed"] == 1.5
def test_controller_add_state_with_default(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"controller", "add-state", "Assets/Anim/Player.controller", "Idle",
"--is-default",
])
params = _get_params(mock_run)
assert params["properties"]["isDefault"] is True
def test_controller_add_transition_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"controller", "add-transition", "Assets/Anim/Player.controller",
"Idle", "Walk",
"--no-exit-time", "--duration", "0.25",
"--conditions", '[{"parameter":"Speed","mode":"greater","threshold":0.1}]',
])
params = _get_params(mock_run)
assert params["action"] == "controller_add_transition"
assert params["controllerPath"] == "Assets/Anim/Player.controller"
assert params["properties"]["fromState"] == "Idle"
assert params["properties"]["toState"] == "Walk"
assert params["properties"]["hasExitTime"] is False
assert params["properties"]["duration"] == 0.25
assert len(params["properties"]["conditions"]) == 1
assert params["properties"]["conditions"][0]["parameter"] == "Speed"
def test_controller_add_parameter_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"controller", "add-parameter", "Assets/Anim/Player.controller",
"Speed", "--type", "float", "--default-value", "0.0",
])
params = _get_params(mock_run)
assert params["action"] == "controller_add_parameter"
assert params["controllerPath"] == "Assets/Anim/Player.controller"
assert params["properties"]["parameterName"] == "Speed"
assert params["properties"]["parameterType"] == "float"
assert params["properties"]["defaultValue"] == 0.0
def test_controller_add_parameter_trigger(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"controller", "add-parameter", "Assets/Anim/Player.controller",
"Jump", "--type", "trigger",
])
params = _get_params(mock_run)
assert params["properties"]["parameterType"] == "trigger"
def test_controller_info_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, ["controller", "info", "Assets/Anim/Player.controller"])
params = _get_params(mock_run)
assert params["action"] == "controller_get_info"
assert params["controllerPath"] == "Assets/Anim/Player.controller"
def test_controller_assign_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"controller", "assign", "Assets/Anim/Player.controller", "Player",
])
params = _get_params(mock_run)
assert params["action"] == "controller_assign"
assert params["controllerPath"] == "Assets/Anim/Player.controller"
assert params["target"] == "Player"
def test_controller_assign_with_search_method(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"controller", "assign", "Assets/Anim/Player.controller", "Player",
"--search-method", "by_name",
])
params = _get_params(mock_run)
assert params["searchMethod"] == "by_name"
# =============================================================================
# Vector Curve and Preset CLI Commands
# =============================================================================
class TestVectorCurveAndPresetCLICommands:
"""Verify vector curve and preset CLI commands build correct parameter dicts."""
def test_clip_set_vector_curve_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"clip", "set-vector-curve", "Assets/Anim/Move.anim",
"--property", "localPosition",
"--keys", '[{"time":0,"value":[0,1,-10]},{"time":1,"value":[2,1,-10]}]',
])
params = _get_params(mock_run)
assert params["action"] == "clip_set_vector_curve"
assert params["clipPath"] == "Assets/Anim/Move.anim"
assert params["properties"]["property"] == "localPosition"
assert params["properties"]["keys"] == [
{"time": 0, "value": [0, 1, -10]},
{"time": 1, "value": [2, 1, -10]},
]
def test_clip_set_vector_curve_with_type(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"clip", "set-vector-curve", "Assets/Anim/Scale.anim",
"--property", "localScale",
"--type", "Transform",
"--keys", '[{"time":0,"value":[1,1,1]},{"time":1,"value":[2,2,2]}]',
])
params = _get_params(mock_run)
assert params["properties"]["type"] == "Transform"
def test_clip_create_preset_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"clip", "create-preset", "Assets/Anim/Bounce.anim", "bounce",
"--duration", "2.0", "--amplitude", "0.5",
])
params = _get_params(mock_run)
assert params["action"] == "clip_create_preset"
assert params["clipPath"] == "Assets/Anim/Bounce.anim"
assert params["properties"]["preset"] == "bounce"
assert params["properties"]["duration"] == 2.0
assert params["properties"]["amplitude"] == 0.5
def test_clip_create_preset_no_loop(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"clip", "create-preset", "Assets/Anim/Spin.anim", "spin", "--no-loop",
])
params = _get_params(mock_run)
assert params["properties"]["loop"] is False
def test_clip_create_preset_all_presets_accepted(self, runner, mock_config, mock_success):
"""Verify all preset names are accepted by the CLI."""
presets = ["bounce", "rotate", "pulse", "fade", "shake", "hover", "spin",
"sway", "bob", "wiggle", "blink", "slide_in", "elastic"]
for preset in presets:
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
result = runner.invoke(animation, [
"clip", "create-preset", f"Assets/Anim/{preset}.anim", preset,
])
assert result.exit_code == 0, f"Preset '{preset}' failed: {result.output}"
def test_clip_add_event_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"clip", "add-event", "Assets/Anim/Attack.anim",
"--function", "OnAttackHit", "--time", "0.5",
"--string-param", "sword", "--float-param", "10.5", "--int-param", "2"
])
params = _get_params(mock_run)
assert params["action"] == "clip_add_event"
assert params["clipPath"] == "Assets/Anim/Attack.anim"
assert params["properties"]["functionName"] == "OnAttackHit"
assert params["properties"]["time"] == 0.5
assert params["properties"]["stringParameter"] == "sword"
assert params["properties"]["floatParameter"] == 10.5
assert params["properties"]["intParameter"] == 2
def test_clip_remove_event_by_index(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"clip", "remove-event", "Assets/Anim/Attack.anim",
"--event-index", "0"
])
params = _get_params(mock_run)
assert params["action"] == "clip_remove_event"
assert params["properties"]["eventIndex"] == 0
def test_clip_remove_event_by_function(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"clip", "remove-event", "Assets/Anim/Attack.anim",
"--function", "OnAttackHit", "--time", "0.5"
])
params = _get_params(mock_run)
assert params["action"] == "clip_remove_event"
assert params["properties"]["functionName"] == "OnAttackHit"
assert params["properties"]["time"] == 0.5
class TestLayerCLICommands:
"""Test layer management CLI commands."""
def test_controller_add_layer_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"controller", "add-layer", "Assets/Anim/Player.controller", "UpperBody",
"--weight", "0.8", "--blending-mode", "additive"
])
params = _get_params(mock_run)
assert params["action"] == "controller_add_layer"
assert params["properties"]["layerName"] == "UpperBody"
assert params["properties"]["weight"] == 0.8
assert params["properties"]["blendingMode"] == "additive"
def test_controller_remove_layer_by_index(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"controller", "remove-layer", "Assets/Anim/Player.controller",
"--layer-index", "1"
])
params = _get_params(mock_run)
assert params["action"] == "controller_remove_layer"
assert params["properties"]["layerIndex"] == 1
def test_controller_set_layer_weight(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"controller", "set-layer-weight", "Assets/Anim/Player.controller", "0.5",
"--layer-name", "UpperBody"
])
params = _get_params(mock_run)
assert params["action"] == "controller_set_layer_weight"
assert params["properties"]["weight"] == 0.5
assert params["properties"]["layerName"] == "UpperBody"
class TestBlendTreeCLICommands:
"""Test blend tree CLI commands."""
def test_controller_create_blend_tree_1d_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"controller", "create-blend-tree-1d", "Assets/Anim/Player.controller", "Locomotion",
"--blend-param", "Speed", "--layer-index", "0"
])
params = _get_params(mock_run)
assert params["action"] == "controller_create_blend_tree_1d"
assert params["properties"]["stateName"] == "Locomotion"
assert params["properties"]["blendParameter"] == "Speed"
assert params["properties"]["layerIndex"] == 0
def test_controller_create_blend_tree_2d_builds_correct_params(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"controller", "create-blend-tree-2d", "Assets/Anim/Player.controller", "Movement",
"--blend-param-x", "VelocityX", "--blend-param-y", "VelocityZ",
"--blend-type", "freeformdirectional2d"
])
params = _get_params(mock_run)
assert params["action"] == "controller_create_blend_tree_2d"
assert params["properties"]["stateName"] == "Movement"
assert params["properties"]["blendParameterX"] == "VelocityX"
assert params["properties"]["blendParameterY"] == "VelocityZ"
assert params["properties"]["blendType"] == "freeformdirectional2d"
def test_controller_add_blend_tree_child_1d(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"controller", "add-blend-tree-child", "Assets/Anim/Player.controller", "Locomotion",
"--clip-path", "Assets/Anim/Walk.anim", "--threshold", "1.0"
])
params = _get_params(mock_run)
assert params["action"] == "controller_add_blend_tree_child"
# clipPath is a top-level key
assert params["clipPath"] == "Assets/Anim/Walk.anim"
assert params["properties"]["stateName"] == "Locomotion"
assert params["properties"]["threshold"] == 1.0
def test_controller_add_blend_tree_child_2d(self, runner, mock_config, mock_success):
with patch("cli.commands.animation.get_config", return_value=mock_config):
with patch("cli.commands.animation.run_command", return_value=mock_success) as mock_run:
runner.invoke(animation, [
"controller", "add-blend-tree-child", "Assets/Anim/Player.controller", "Movement",
"--clip-path", "Assets/Anim/WalkForward.anim", "--position", "0", "1"
])
params = _get_params(mock_run)
assert params["action"] == "controller_add_blend_tree_child"
assert params["properties"]["stateName"] == "Movement"
assert params["properties"]["position"] == [0, 1]