Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license

This commit is contained in:
2026-09-05 13:26:46 +03:00
commit 03634b1ca3
11340 changed files with 3442369 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
"""Make tests/plugins/video_gen a package."""
@@ -0,0 +1,130 @@
"""Tests for the bundled DeepInfra video_gen plugin.
Invariants only — no snapshots of specific model ids. The plugin is a thin
subclass of ``agent.video_gen_provider.OpenAICompatibleVideoGenProvider``;
these tests pin the plugin-specific bits (tag filtering, identity) and the
shared base behaviour exercised through it (OpenAI ``videos`` call shape,
t2v vs i2v routing, download → save).
"""
from __future__ import annotations
from contextlib import contextmanager
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
import pytest
import plugins.video_gen.deepinfra as deepinfra_plugin
@pytest.fixture(autouse=True)
def _isolation(tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
import hermes_cli.models as _models_mod
monkeypatch.setattr(_models_mod, "_deepinfra_catalog_cache", {})
monkeypatch.setenv("DEEPINFRA_API_KEY", "test-key")
yield
def test_identity_and_availability(monkeypatch):
p = deepinfra_plugin.DeepInfraVideoGenProvider()
assert p.name == "deepinfra"
assert p.display_name == "DeepInfra"
assert p._base_url() == "https://api.deepinfra.com/v1/openai"
assert p.is_available() is True
monkeypatch.delenv("DEEPINFRA_API_KEY", raising=False)
assert p.is_available() is False
def test_list_models_filters_by_video_gen_tag(monkeypatch):
"""list_models() returns only ``video-gen``-tagged catalog entries."""
import hermes_cli.models as _models_mod
def _fake_by_tag(tag, **kw):
assert tag == "video-gen"
return [
{"id": "vendor/p-video", "metadata": {"description": "fast t2v"}},
{"id": "vendor/wan-t2v", "metadata": {}},
]
monkeypatch.setattr(_models_mod, "_fetch_deepinfra_models_by_tag", _fake_by_tag)
rows = deepinfra_plugin.DeepInfraVideoGenProvider().list_models()
ids = {row["id"] for row in rows}
assert ids == {"vendor/p-video", "vendor/wan-t2v"}
assert all("display" in r for r in rows)
def _fake_openai_with_capture(captured: dict, *, status="succeeded",
data=None, download=b"\x00\x00mp4bytes"):
"""Build a fake ``openai`` module whose videos resource records the call.
Defaults mirror the real DeepInfra job shape: status ``"succeeded"`` and a
``data`` list carrying the delivery URL.
"""
if data is None:
data = [{"url": "https://cdn.example/out.mp4"}]
class _FakeVideos:
def create(self, **kwargs):
captured["kwargs"] = kwargs
# Return a terminal status immediately so the bounded poll in
# OpenAICompatibleVideoGenProvider._create_and_poll exits without
# calling retrieve() or sleeping.
return SimpleNamespace(status=status, id="vid_123", error=None, data=data)
def retrieve(self, video_id):
return SimpleNamespace(status=status, id=video_id, error=None, data=data)
def download_content(self, video_id):
captured["downloaded_id"] = video_id
return SimpleNamespace(read=lambda: download)
class _FakeClient:
def __init__(self, api_key=None, base_url=None):
captured["api_key"] = api_key
captured["base_url"] = base_url
self.videos = _FakeVideos()
fake = MagicMock()
fake.OpenAI = _FakeClient
return fake
@contextmanager
def _mock_url_download(captured: dict, raise_exc: Exception | None = None):
"""Patch the shared ``save_url_video`` helper the base provider calls."""
import agent.video_gen_provider as base
from pathlib import Path
def _fake_save_url_video(url, *, prefix="video", **kw):
captured["url"] = url
if raise_exc:
raise raise_exc
return Path(f"/home/x/.hermes/cache/videos/{prefix}_test.mp4")
with patch.object(base, "save_url_video", _fake_save_url_video):
yield
def test_generate_text_to_video_downloads_url_and_saves_locally():
"""t2v happy path: SDK called with DeepInfra base_url + key; status
'succeeded' + data[].url → bytes downloaded and saved to a local file."""
captured: dict = {}
with patch.dict("sys.modules", {"openai": _fake_openai_with_capture(captured)}), \
_mock_url_download(captured):
result = deepinfra_plugin.DeepInfraVideoGenProvider().generate(
prompt="a red cube rotating", model="vendor/test-vid", duration=5,
)
assert result["success"] is True
assert result["modality"] == "text"
assert result["video"].endswith(".mp4") and "cache/videos" in result["video"]
assert captured["url"] == "https://cdn.example/out.mp4"
assert "deepinfra" in captured["base_url"]
assert captured["api_key"] == "test-key"
assert captured["kwargs"]["model"] == "vendor/test-vid"
assert captured["kwargs"]["seconds"] == "5"
# No image_url ⇒ no image-to-video field passed through.
assert "image_url" not in captured["kwargs"].get("extra_body", {})
+629
View File
@@ -0,0 +1,629 @@
"""Tests for the FAL video gen plugin — family routing, payload shape."""
from __future__ import annotations
from unittest.mock import Mock
import pytest
from agent import video_gen_registry
@pytest.fixture(autouse=True)
def _reset_registry():
video_gen_registry._reset_for_tests()
yield
video_gen_registry._reset_for_tests()
def test_fal_provider_registers():
from plugins.video_gen.fal import FALVideoGenProvider, DEFAULT_MODEL
provider = FALVideoGenProvider()
video_gen_registry.register_provider(provider)
assert video_gen_registry.get_provider("fal") is provider
assert provider.display_name == "FAL"
# DEFAULT_MODEL is the cheap-tier default
assert provider.default_model() == DEFAULT_MODEL
assert DEFAULT_MODEL in {"pixverse-v6", "ltx-2.3"}
def test_kling_4k_uses_start_image_url():
"""Kling v3 4K's image-to-video endpoint expects start_image_url,
not image_url. The family must declare image_param_key='start_image_url'."""
from plugins.video_gen.fal import FAL_FAMILIES, _build_payload
meta = FAL_FAMILIES["kling-v3-4k"]
assert meta.get("image_param_key") == "start_image_url"
payload = _build_payload(
meta,
prompt="x",
image_url="https://example.com/i.png",
duration=5,
aspect_ratio="16:9",
resolution="720p",
negative_prompt=None,
audio=None,
seed=None,
)
assert payload.get("start_image_url") == "https://example.com/i.png"
assert "image_url" not in payload
def test_minimax_h3_int_duration_and_resolution_alias():
"""MiniMax H3 requires duration as a JSON integer and uses the
768P/2K/4K resolution enum — the tool's 720p/1080p values must map."""
from plugins.video_gen.fal import FAL_FAMILIES, _build_payload
meta = FAL_FAMILIES["minimax-h3"]
payload = _build_payload(
meta,
prompt="x",
image_url=None,
duration=7,
aspect_ratio="16:9",
resolution="720p",
negative_prompt=None,
audio=True,
seed=None,
)
assert payload["duration"] == 7 and isinstance(payload["duration"], int)
assert payload["resolution"] == "768P"
assert payload["aspect_ratio"] == "16:9"
# H3 has no generate_audio key (audio is native/always-on)
assert "generate_audio" not in payload
hi = _build_payload(
meta, prompt="x", image_url=None, duration=5, aspect_ratio="16:9",
resolution="1080p", negative_prompt=None, audio=None, seed=None,
)
assert hi["resolution"] == "2K"
def test_image_drop_keys_strips_aspect_ratio_on_i2v():
"""Seedance 2.5 / MiniMax H3 / Grok 1.5 i2v endpoints derive the
aspect ratio from the input image; sending the key is rejected."""
from plugins.video_gen.fal import FAL_FAMILIES, _build_payload
for fid in ("seedance-2.5", "minimax-h3", "grok-imagine-1.5"):
meta = FAL_FAMILIES[fid]
i2v = _build_payload(
meta, prompt="x", image_url="https://example.com/i.png",
duration=5, aspect_ratio="16:9", resolution="480p",
negative_prompt=None, audio=None, seed=None,
)
assert "aspect_ratio" not in i2v, fid
# ...but text-to-video keeps it
t2v = _build_payload(
meta, prompt="x", image_url=None, duration=5,
aspect_ratio="16:9", resolution="480p",
negative_prompt=None, audio=None, seed=None,
)
assert t2v.get("aspect_ratio") == "16:9", fid
def test_seedance_25_string_duration_up_to_30():
"""Seedance 2.5 keeps the stringified duration convention and supports
the full 4-30s range."""
from plugins.video_gen.fal import FAL_FAMILIES, _build_payload
meta = FAL_FAMILIES["seedance-2.5"]
payload = _build_payload(
meta, prompt="x", image_url=None, duration=30, aspect_ratio="1:1",
resolution="480p", negative_prompt=None, audio=True, seed=None,
)
assert payload["duration"] == "30"
assert payload["generate_audio"] is True
def test_gemini_omni_flash_is_image_only():
"""Gemini Omni Flash has no t2v endpoint on FAL — text jobs must
error cleanly instead of submitting to a None endpoint."""
from plugins.video_gen.fal import FAL_FAMILIES
meta = FAL_FAMILIES["gemini-omni-flash"]
assert meta.get("text_endpoint") is None
assert meta.get("image_endpoint")
def test_every_family_has_required_metadata():
"""Invariant: every family entry carries the picker-facing metadata and
at least one endpoint."""
from plugins.video_gen.fal import FAL_FAMILIES
for fid, meta in FAL_FAMILIES.items():
assert meta.get("display"), fid
assert meta.get("tier") in {"cheap", "premium"}, fid
assert meta.get("text_endpoint") or meta.get("image_endpoint"), fid
class TestFamilyRouting:
"""The headline behavior: image_url presence picks the endpoint."""
@pytest.fixture
def with_fake_fal(self, monkeypatch):
"""Stub fal_client.submit to capture which endpoint we hit."""
import sys
import types
captured = {"endpoint": None, "arguments": None}
class FakeHandle:
def get(self):
return {"video": {"url": "https://fake/out.mp4"}}
fake = types.ModuleType("fal_client")
def _submit(endpoint, arguments=None, headers=None):
captured["endpoint"] = endpoint
captured["arguments"] = arguments
return FakeHandle()
fake.submit = _submit # type: ignore
monkeypatch.setitem(sys.modules, "fal_client", fake)
# Reset the lazy global so it picks up our stub
from plugins.video_gen import fal as fal_plugin
fal_plugin._fal_client = None
# Also reset the managed client cache
fal_plugin._managed_fal_video_client = None
fal_plugin._managed_fal_video_client_config = None
monkeypatch.setenv("FAL_KEY", "test")
# Force direct mode — no managed gateway
monkeypatch.setattr(fal_plugin, "_resolve_managed_fal_video_gateway", lambda: None)
return captured
def test_text_to_video_routes_to_text_endpoint(self, with_fake_fal):
from plugins.video_gen.fal import FALVideoGenProvider
result = FALVideoGenProvider().generate(
"a dog running",
model="pixverse-v6",
)
assert result["success"] is True
assert with_fake_fal["endpoint"] == "fal-ai/pixverse/v6/text-to-video"
assert result["modality"] == "text"
assert with_fake_fal["arguments"]["prompt"] == "a dog running"
assert "image_url" not in with_fake_fal["arguments"]
def test_image_to_video_routes_to_image_endpoint(self, with_fake_fal):
from plugins.video_gen.fal import FALVideoGenProvider
result = FALVideoGenProvider().generate(
"animate this dog",
model="pixverse-v6",
image_url="https://example.com/dog.png",
)
assert result["success"] is True
assert with_fake_fal["endpoint"] == "fal-ai/pixverse/v6/image-to-video"
assert result["modality"] == "image"
assert with_fake_fal["arguments"]["image_url"] == "https://example.com/dog.png"
def test_default_family_text_routing(self, with_fake_fal):
"""No model arg → DEFAULT_MODEL → text-to-video endpoint."""
from plugins.video_gen.fal import FALVideoGenProvider, FAL_FAMILIES, DEFAULT_MODEL
result = FALVideoGenProvider().generate("a dog")
assert result["success"] is True
expected_endpoint = FAL_FAMILIES[DEFAULT_MODEL]["text_endpoint"]
assert with_fake_fal["endpoint"] == expected_endpoint
def test_unknown_family_falls_back_to_default(self, with_fake_fal):
from plugins.video_gen.fal import FALVideoGenProvider, FAL_FAMILIES, DEFAULT_MODEL
result = FALVideoGenProvider().generate(
"x",
model="not-a-real-family",
)
assert result["success"] is True
expected_endpoint = FAL_FAMILIES[DEFAULT_MODEL]["text_endpoint"]
assert with_fake_fal["endpoint"] == expected_endpoint
def test_premium_seedance_routing(self, with_fake_fal):
"""Sanity check the premium-tier seedance routes correctly."""
from plugins.video_gen.fal import FALVideoGenProvider
result = FALVideoGenProvider().generate(
"a dog",
model="seedance-2.0",
image_url="https://example.com/dog.png",
)
assert result["success"] is True
assert with_fake_fal["endpoint"] == "bytedance/seedance-2.0/image-to-video"
# Seedance uses regular image_url (not start_image_url)
assert with_fake_fal["arguments"]["image_url"] == "https://example.com/dog.png"
class TestFamilyKeyNormalization:
def test_full_endpoint_paths_resolve_to_their_own_family(self):
"""A configured endpoint path must resolve to the family that declares
it. The segment scan alone reads the "seedance-2.0" in
".../seedance-2.0/mini/..." and bills the full-price family."""
from plugins.video_gen.fal import FAL_FAMILIES, _normalize_family_key
for fid, meta in FAL_FAMILIES.items():
for key in ("text_endpoint", "image_endpoint"):
endpoint = meta.get(key)
if endpoint:
assert _normalize_family_key(endpoint) == fid, endpoint
def test_bare_and_prefixed_ids_still_resolve(self):
from plugins.video_gen.fal import _normalize_family_key
assert _normalize_family_key("seedance-2.5") == "seedance-2.5"
assert _normalize_family_key("bytedance/seedance-2.5") == "seedance-2.5"
assert _normalize_family_key(" pixverse-v6 ") == "pixverse-v6"
assert _normalize_family_key("nonsense/thing") is None
def test_truncated_endpoint_stems_resolve(self):
"""Config often stores the FAL app path without the modality leaf."""
from plugins.video_gen.fal import _normalize_family_key
assert _normalize_family_key("bytedance/seedance-2.0/mini") == "seedance-2.0-mini"
assert _normalize_family_key("bytedance/seedance-2.0") == "seedance-2.0"
assert _normalize_family_key("minimax/h3") == "minimax-h3"
assert _normalize_family_key("xai/grok-imagine-video/v1.5") == "grok-imagine-1.5"
assert _normalize_family_key("google/gemini-omni-flash") == "gemini-omni-flash"
assert _normalize_family_key("blackforestlabs/flux-3") == "flux-3"
def test_capabilities_span_longest_family_duration(self):
"""capabilities() is active-MODEL-aware (#95681 diet): it reports
the resolved family's real window, so the schema doesn't overstate
short families or understate Seedance 2.5. The union fallback
(resolution failure) must still span the 30s ceiling."""
from unittest.mock import patch as _patch
import plugins.video_gen.fal as _fp
from plugins.video_gen.fal import FAL_FAMILIES, FALVideoGenProvider
# Active model resolved → that family's actual window.
meta = FAL_FAMILIES["seedance-2.5"]
with _patch.object(_fp, "_resolve_family",
return_value=("seedance-2.5", meta)):
caps = FALVideoGenProvider().capabilities()
assert caps["max_duration"] >= 30
# A short family must NOT be inflated to the union ceiling.
short = FAL_FAMILIES["pixverse-v6"]
durs = short.get("durations")
hi = durs[1] if isinstance(durs, tuple) else max(durs)
with _patch.object(_fp, "_resolve_family",
return_value=("pixverse-v6", short)):
caps = FALVideoGenProvider().capabilities()
assert caps["max_duration"] == hi
# Resolution failure → union fallback still spans the ceiling.
with _patch.object(_fp, "_resolve_family",
side_effect=RuntimeError("no config")):
caps = FALVideoGenProvider().capabilities()
assert caps["max_duration"] >= 30
assert caps["min_duration"] <= 1
class TestPayloadBuilder:
def test_drops_unsupported_keys(self):
"""Veo enum-clamps duration, supports aspect+resolution+audio+neg."""
from plugins.video_gen.fal import FAL_FAMILIES, _build_payload
meta = FAL_FAMILIES["veo3.1"]
p = _build_payload(
meta,
prompt="x",
image_url=None,
duration=12, # not in enum (4,6,8) — snap to 8
aspect_ratio="16:9",
resolution="720p",
negative_prompt="ugly",
audio=True,
seed=42,
)
assert p["prompt"] == "x"
assert p["duration"] == "8s" # veo3.1 uses "Ns" format per FAL API
assert p["aspect_ratio"] == "16:9"
assert p["resolution"] == "720p"
assert p["generate_audio"] is True
assert p["negative_prompt"] == "ugly"
assert p["seed"] == 42
def test_pixverse_range_clamps_correctly(self):
from plugins.video_gen.fal import FAL_FAMILIES, _build_payload
meta = FAL_FAMILIES["pixverse-v6"]
p = _build_payload(
meta,
prompt="x",
image_url="https://i.png",
duration=99, # over max → 15
aspect_ratio="16:9",
resolution="540p",
negative_prompt=None,
audio=None,
seed=None,
)
assert p["duration"] == "15"
@pytest.mark.parametrize(
"family_id",
[
"seedance-2.0",
"seedance-2.0-mini",
"seedance-2.5",
"minimax-h3",
"flux-3",
"grok-imagine-1.5",
"gemini-omni-flash",
],
)
def test_seed_dropped_for_families_without_seed_support(self, family_id):
"""These FAL endpoints declare no `seed`; the gateway forwards whatever
we send, so an unknown key would reach the vendor."""
from plugins.video_gen.fal import FAL_FAMILIES, _build_payload
p = _build_payload(
FAL_FAMILIES[family_id],
prompt="x",
image_url="https://i.png",
duration=None,
aspect_ratio="16:9",
resolution="720p",
negative_prompt=None,
audio=None,
seed=42,
)
assert "seed" not in p
def test_minimax_h3_uses_uppercase_resolution_enum(self):
"""FAL spells MiniMax H3 resolutions "768P"/"2K"/"4K"; tool-style
values like "720p" are aliased via resolution_aliases."""
from plugins.video_gen.fal import FAL_FAMILIES, _build_payload
meta = FAL_FAMILIES["minimax-h3"]
accepted = _build_payload(
meta, prompt="x", image_url=None, duration=7, aspect_ratio="16:9",
resolution="2K", negative_prompt=None, audio=None, seed=None,
)
assert accepted["resolution"] == "2K"
assert accepted["duration"] == 7
aliased = _build_payload(
meta, prompt="x", image_url=None, duration=7, aspect_ratio="16:9",
resolution="720p", negative_prompt=None, audio=None, seed=None,
)
assert aliased["resolution"] == "768P"
def test_audio_only_sent_for_families_that_declare_it(self):
"""minimax-h3 and the i2v-only families have no generate_audio field."""
from plugins.video_gen.fal import FAL_FAMILIES, _build_payload
for family_id in ("minimax-h3", "grok-imagine-1.5", "gemini-omni-flash"):
p = _build_payload(
FAL_FAMILIES[family_id],
prompt="x", image_url="https://i.png", duration=None,
aspect_ratio="16:9", resolution="720p", negative_prompt="ugly",
audio=True, seed=None,
)
assert "generate_audio" not in p, family_id
assert "negative_prompt" not in p, family_id
@pytest.mark.parametrize(
"family_id,expected",
[
("minimax-h3", 7), # FAL types duration as an integer
("flux-3", 7), # mixed ["auto", 5, 6, ...] literal enum
("grok-imagine-1.5", 7),
("gemini-omni-flash", 7),
("seedance-2.5", "7"), # FAL enum is strings: "auto","4",...
("seedance-2.0-mini", "7"),
("pixverse-v6", "7"), # unchanged legacy string form
("veo3.1", "6s"), # unchanged suffix form (7 snaps to 6)
],
)
def test_duration_is_emitted_in_the_form_fal_declares(self, family_id, expected):
from plugins.video_gen.fal import FAL_FAMILIES, _build_payload
p = _build_payload(
FAL_FAMILIES[family_id],
prompt="x", image_url=None, duration=7, aspect_ratio="16:9",
resolution="720p", negative_prompt=None, audio=None, seed=None,
)
assert p["duration"] == expected
assert type(p["duration"]) is type(expected)
def test_i2v_only_families_declare_no_text_endpoint(self):
"""Catalog invariant: Gemini Omni Flash animates an existing image only."""
from plugins.video_gen.fal import FAL_FAMILIES
meta = FAL_FAMILIES["gemini-omni-flash"]
assert meta.get("text_endpoint") is None
assert meta["image_endpoint"]
def test_ltx_omits_duration_aspect_resolution(self):
"""LTX 2.3 doesn't declare duration/aspect/resolution enums —
the payload should NOT include those keys (let FAL default)."""
from plugins.video_gen.fal import FAL_FAMILIES, _build_payload
meta = FAL_FAMILIES["ltx-2.3"]
p = _build_payload(
meta,
prompt="x",
image_url=None,
duration=8,
aspect_ratio="16:9",
resolution="720p",
negative_prompt="ugly",
audio=True,
seed=None,
)
assert "duration" not in p
assert "aspect_ratio" not in p
assert "resolution" not in p
# But audio + negative are advertised
assert p["generate_audio"] is True
assert p["negative_prompt"] == "ugly"
def test_range_families_omit_duration_when_unspecified(self):
"""Range-based families must omit `duration` when the caller doesn't
specify one so FAL applies its endpoint default, not the minimum."""
from plugins.video_gen.fal import FAL_FAMILIES, _build_payload
for family_id in ("pixverse-v6", "seedance-2.0", "kling-v3-4k"):
meta = FAL_FAMILIES[family_id]
p = _build_payload(
meta,
prompt="x",
image_url=None,
duration=None,
aspect_ratio="16:9",
resolution="720p",
negative_prompt=None,
audio=None,
seed=None,
)
assert "duration" not in p, (
f"{family_id}: duration=None should omit the field, "
f"got {p.get('duration')!r}"
)
def test_happy_horse_minimal_payload(self):
"""Happy Horse has sparse docs — payload should be minimal."""
from plugins.video_gen.fal import FAL_FAMILIES, _build_payload
meta = FAL_FAMILIES["happy-horse"]
p = _build_payload(
meta,
prompt="a horse galloping",
image_url=None,
duration=8,
aspect_ratio="16:9",
resolution="720p",
negative_prompt="watermark",
audio=True,
seed=None,
)
# Only prompt — no payload bloat for fields we can't verify
assert p == {"prompt": "a horse galloping"}
class TestUpscalePass:
"""Opt-in SeedVR2 upscale chain after generation."""
@pytest.fixture
def with_fake_fal(self, monkeypatch):
"""Stub fal_client.submit, capturing every endpoint hit in order."""
import sys
import types
captured = {"calls": []}
class FakeHandle:
def __init__(self, endpoint):
self._endpoint = endpoint
def get(self):
if self._endpoint.endswith("upscale/video"):
return {"video": {"url": "https://fake/upscaled.mp4"}}
return {"video": {"url": "https://fake/native.mp4"}}
fake = types.ModuleType("fal_client")
def _submit(endpoint, arguments=None, headers=None):
captured["calls"].append((endpoint, arguments))
return FakeHandle(endpoint)
fake.submit = _submit # type: ignore
monkeypatch.setitem(sys.modules, "fal_client", fake)
from plugins.video_gen import fal as fal_plugin
fal_plugin._fal_client = None
fal_plugin._managed_fal_video_client = None
fal_plugin._managed_fal_video_client_config = None
monkeypatch.setenv("FAL_KEY", "test")
monkeypatch.setattr(fal_plugin, "_resolve_managed_fal_video_gateway", lambda: None)
return captured
def test_upscale_chains_seedvr(self, with_fake_fal):
from plugins.video_gen.fal import FALVideoGenProvider, UPSCALER_ENDPOINT
result = FALVideoGenProvider().generate(
"a dog", model="pixverse-v6", upscale=True,
)
assert result["success"] is True
assert result["video"] == "https://fake/upscaled.mp4"
assert result["upscaled"] is True
assert result["upscale_factor"] == 2
endpoints = [c[0] for c in with_fake_fal["calls"]]
assert endpoints == ["fal-ai/pixverse/v6/text-to-video", UPSCALER_ENDPOINT]
# Upscale request carries the native URL + factor mode.
upscale_args = with_fake_fal["calls"][1][1]
assert upscale_args["video_url"] == "https://fake/native.mp4"
assert upscale_args["upscale_mode"] == "factor"
def test_no_upscale_by_default(self, with_fake_fal):
from plugins.video_gen.fal import FALVideoGenProvider
result = FALVideoGenProvider().generate("a dog", model="pixverse-v6")
assert result["success"] is True
assert result["video"] == "https://fake/native.mp4"
assert result["upscaled"] is False
assert len(with_fake_fal["calls"]) == 1
def test_upscale_failure_falls_back_to_native(self, with_fake_fal, monkeypatch):
from plugins.video_gen import fal as fal_plugin
from plugins.video_gen.fal import FALVideoGenProvider
monkeypatch.setattr(
fal_plugin,
"_upscale_video",
lambda url, source_request_id=None: None,
)
result = FALVideoGenProvider().generate(
"a dog", model="pixverse-v6", upscale=True,
)
assert result["success"] is True
assert result["video"] == "https://fake/native.mp4"
assert result["upscaled"] is False
def test_managed_upscale_binds_the_source_request(self, monkeypatch):
from plugins.video_gen import fal as fal_plugin
captured = {}
class FakeHandle:
def get(self):
return {"video": {"url": "https://fake/upscaled.mp4"}}
monkeypatch.setattr(
fal_plugin,
"_resolve_managed_fal_video_gateway",
lambda: object(),
)
monkeypatch.setattr(
fal_plugin,
"_submit_fal_video_request",
lambda endpoint, arguments: (
captured.update(endpoint=endpoint, arguments=arguments)
or FakeHandle()
),
)
assert (
fal_plugin._upscale_video(
"https://fake/native.mp4",
"source-request-1",
)
== "https://fake/upscaled.mp4"
)
assert captured["arguments"]["source_request_id"] == "source-request-1"
def test_managed_upscale_without_source_request_falls_back(self, monkeypatch):
from plugins.video_gen import fal as fal_plugin
submit = Mock()
monkeypatch.setattr(
fal_plugin,
"_resolve_managed_fal_video_gateway",
lambda: object(),
)
monkeypatch.setattr(fal_plugin, "_submit_fal_video_request", submit)
assert fal_plugin._upscale_video("https://fake/native.mp4") is None
submit.assert_not_called()
@@ -0,0 +1,82 @@
"""Smoke tests for the xAI video gen plugin — load & register surface."""
from __future__ import annotations
import pytest
from agent import video_gen_registry
@pytest.fixture(autouse=True)
def _reset_registry():
video_gen_registry._reset_for_tests()
yield
video_gen_registry._reset_for_tests()
def test_xai_provider_registers():
from plugins.video_gen.xai import XAIVideoGenProvider
provider = XAIVideoGenProvider()
video_gen_registry.register_provider(provider)
assert video_gen_registry.get_provider("xai") is provider
assert provider.display_name == "xAI"
assert provider.default_model() == "grok-imagine-video"
def test_xai_resolved_credentials_threaded_through_request(monkeypatch):
"""OAuth-resolved creds must reach the HTTP layer — bug class where
``is_available()`` says yes but the request still hits with no key.
"""
import plugins.video_gen.xai as xai_plugin
monkeypatch.delenv("XAI_API_KEY", raising=False)
monkeypatch.setattr(
"tools.xai_http.resolve_xai_http_credentials",
lambda: {
"provider": "xai-oauth",
"api_key": "oauth-bearer-token",
"base_url": "https://api.x.ai/v1",
},
)
api_key, base_url = xai_plugin._resolve_xai_credentials()
assert api_key == "oauth-bearer-token"
assert base_url == "https://api.x.ai/v1"
headers = xai_plugin._xai_headers(api_key)
assert headers["Authorization"] == "Bearer oauth-bearer-token"
@pytest.mark.asyncio
async def test_video_input_from_public_url_uses_url_field():
from plugins.video_gen.xai import _video_input_from_public_url
url = "https://files-cdn.x.ai/kRQVP6PRQlioVAUNC3GAdg/file_1faca9c3-9411-46ad-bb41-b9b8527789e6.mp4"
result = await _video_input_from_public_url(
url,
api_key="test-key",
base_url="https://api.x.ai/v1",
)
assert result == {"url": url}
def test_xai_video_image_input_blocks_credential_store_symlink(tmp_path, monkeypatch):
from plugins.video_gen.xai import _image_ref_to_xai_input
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
auth_json = hermes_home / "auth.json"
auth_json.write_text('{"api_key":"sk-secret"}', encoding="utf-8")
image_link = hermes_home / "leak.png"
try:
image_link.symlink_to(auth_json)
except OSError as exc:
pytest.skip(f"symlink unavailable on this platform: {exc}")
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
with pytest.raises(ValueError, match="credential store"):
_image_ref_to_xai_input(str(image_link))
@@ -0,0 +1,182 @@
"""Integration tests for the xAI video gen plugin's simplified surface.
xAI exposes only text-to-video and image-to-video through the unified
``video_generate`` tool. We assert the endpoint hit and the payload shape
because routing is the part most likely to break silently.
"""
from __future__ import annotations
import asyncio
import json
from typing import Any, Dict, List, Optional
import pytest
from agent import video_gen_registry
@pytest.fixture(autouse=True)
def _reset_registry():
video_gen_registry._reset_for_tests()
yield
video_gen_registry._reset_for_tests()
class _FakeResponse:
def __init__(self, status: int = 200, payload: Optional[Dict[str, Any]] = None):
self.status_code = status
self._payload = payload or {}
self.text = json.dumps(self._payload)
def raise_for_status(self):
if self.status_code >= 400:
import httpx
raise httpx.HTTPStatusError("err", request=None, response=self) # type: ignore
def json(self):
return self._payload
class _FakeAsyncClient:
def __init__(self):
self.posts: List[Dict[str, Any]] = []
async def __aenter__(self):
return self
async def __aexit__(self, *args):
return None
async def post(self, url, headers=None, json=None, timeout=None):
self.posts.append({"url": url, "json": json})
return _FakeResponse(200, {"request_id": "req-123"})
async def get(self, url, headers=None, timeout=None):
return _FakeResponse(200, {
"status": "done",
"video": {"url": "https://xai-cdn/out.mp4", "duration": 8},
"model": self.posts[-1]["json"]["model"],
})
@pytest.fixture
def xai_provider(monkeypatch):
monkeypatch.setenv("XAI_API_KEY", "test-key")
import plugins.video_gen.xai as xai_plugin
captured: Dict[str, _FakeAsyncClient] = {}
def _client_factory():
captured["client"] = _FakeAsyncClient()
return captured["client"]
monkeypatch.setattr(xai_plugin.httpx, "AsyncClient", _client_factory)
async def _no_sleep(*a, **k):
return None
monkeypatch.setattr(asyncio, "sleep", _no_sleep)
provider = xai_plugin.XAIVideoGenProvider()
return provider, captured
def _last_post(captured) -> Dict[str, Any]:
return captured["client"].posts[-1]
class TestXAIEndpoint:
"""xAI uses one endpoint — ``/videos/generations`` — for both modes."""
def test_text_to_video_hits_generations(self, xai_provider):
provider, captured = xai_provider
result = provider.generate("a dog on a skateboard")
assert result["success"] is True
assert _last_post(captured)["url"].endswith("/videos/generations")
assert result["modality"] == "text"
def test_image_to_video_hits_generations(self, xai_provider):
provider, captured = xai_provider
result = provider.generate(
"animate this",
image_url="https://example.com/cat.png",
)
assert result["success"] is True
assert _last_post(captured)["url"].endswith("/videos/generations")
assert result["modality"] == "image"
class TestXAIPayload:
def test_local_image_path_is_sent_as_data_uri(self, xai_provider, tmp_path):
provider, captured = xai_provider
image_path = tmp_path / "frame.png"
image_path.write_bytes(b"\x89PNG\r\n\x1a\nfake")
provider.generate("animate this", image_url=str(image_path))
payload = _last_post(captured)["json"]
assert payload["model"] == "grok-imagine-video-1.5"
assert payload["image"]["url"].startswith("data:image/png;base64,")
def test_explicit_model_override_is_honored_for_image(self, xai_provider):
provider, captured = xai_provider
provider.generate(
"animate this",
image_url="https://example.com/cat.png",
model="grok-imagine-video",
_model_override_explicit=True,
)
payload = _last_post(captured)["json"]
assert payload["model"] == "grok-imagine-video"
def test_reference_images_payload(self, xai_provider):
provider, captured = xai_provider
provider.generate(
"keep this character",
reference_image_urls=[
"https://example.com/a.png",
"https://example.com/b.png",
],
)
payload = _last_post(captured)["json"]
assert payload["reference_images"] == [
{"url": "https://example.com/a.png"},
{"url": "https://example.com/b.png"},
]
class TestXAIValidation:
def test_missing_prompt_rejects(self, xai_provider):
provider, captured = xai_provider
result = provider.generate("")
assert result["success"] is False
assert result["error_type"] == "missing_prompt"
# Never hit the network
assert "client" not in captured or not captured["client"].posts
def test_too_many_references_rejects(self, xai_provider):
provider, captured = xai_provider
result = provider.generate(
"x",
reference_image_urls=[f"https://example.com/r{i}.png" for i in range(8)],
)
assert result["success"] is False
assert result["error_type"] == "too_many_references"
class TestXAIClamping:
def test_duration_clamped_to_15(self, xai_provider):
provider, captured = xai_provider
provider.generate("x", duration=30)
assert _last_post(captured)["json"]["duration"] == 15
def test_invalid_aspect_ratio_soft_clamps(self, xai_provider):
provider, captured = xai_provider
provider.generate("x", aspect_ratio="21:9")
assert _last_post(captured)["json"]["aspect_ratio"] == "16:9"