Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/xskill/ecosystems/_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,14 @@ def submit_trajectory(
``ingest.mask_patterns``(默认空 = 不替换)。命中段在写 md 之前替换为
占位符——剥掉评测 harness 的固定外壳,防聚类被任务外壳吸住。
"""
if not isinstance(content, str) or not content.strip():
raise ValueError("trajectory content must be non-empty")

if format == "json":
data = json.loads(content)
if not data:
raise ValueError("trajectory content must be non-empty")

traj_dir = Path(traj_dir) if traj_dir else get_traj_dir()
traj_dir.mkdir(parents=True, exist_ok=True)

Expand Down
72 changes: 72 additions & 0 deletions tests/test_trajectory_submit_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from __future__ import annotations

from types import SimpleNamespace

import pytest
from fastapi import HTTPException

from xskill.ecosystems import submit_trajectory


@pytest.mark.parametrize(
("content", "format"),
[
("", "markdown"),
(" \n\t", "markdown"),
("", "raw"),
(" \n\t", "raw"),
("{}", "json"),
],
)
def test_submit_rejects_empty_trajectories_without_writing_files(
tmp_path, content, format
):
with pytest.raises(ValueError, match="content"):
submit_trajectory(content=content, format=format, traj_dir=tmp_path)

assert list(tmp_path.iterdir()) == []


def test_submit_accepts_json_with_a_message(tmp_path):
result = submit_trajectory(
content='{"messages":[{"role":"user","content":"hello"}]}',
format="json",
traj_dir=tmp_path,
)

assert result["status"] == "stored"
assert "hello" in (tmp_path / "traj_0001.md").read_text(encoding="utf-8")


@pytest.mark.asyncio
@pytest.mark.parametrize("content", ["", " \n\t"])
async def test_api_rejects_empty_content_before_resolving_watch_directory(
tmp_path, monkeypatch, content
):
import xskill.api.app as api_app
import xskill.ecosystems._shared as shared

monkeypatch.setattr(api_app, "_ensure_loaded", lambda: None)
monkeypatch.setattr(api_app, "_config", {})
monkeypatch.setattr(api_app, "_skill_dir", tmp_path / "skill")
def fail_get_traj_dir():
raise AssertionError("invalid content should fail before resolving a watch directory")

monkeypatch.setattr(shared, "get_traj_dir", fail_get_traj_dir)

app = api_app.create_app(home_root=tmp_path)
route = next(
route for route in app.routes
if getattr(route, "path", None) == "/api/v1/trajectories/submit"
)

with pytest.raises(HTTPException) as exc_info:
await route.endpoint(SimpleNamespace(
content=content,
format="markdown",
metadata=None,
traj_id=None,
))

assert exc_info.value.status_code == 400
assert list(tmp_path.iterdir()) == []
Loading