| from __future__ import annotations |
|
|
| import json |
| import unittest |
| from pathlib import Path |
|
|
| from scripts.run_character_generation_spike import _evaluate_gate |
| from src.character_spike.assets import create_mock_asset_package |
| from src.character_spike.schema import EXPRESSIONS, MODEL_CANDIDATES, candidate_by_id |
| from src.character_spike.tavern_import import convert_tavern_card, load_tavern_json, write_character_draft |
|
|
|
|
| PROJECT_ROOT = Path(__file__).resolve().parents[1] |
| TMP_ROOT = PROJECT_ROOT / ".tmp" / "tests" |
|
|
|
|
| class CharacterSpikeTests(unittest.TestCase): |
| def test_candidate_registry_tracks_required_models(self) -> None: |
| ids = {candidate.id for candidate in MODEL_CANDIDATES} |
| self.assertIn("flux_schnell", ids) |
| self.assertIn("qwen_image", ids) |
| self.assertIn("qwen_image_edit", ids) |
| self.assertIn("qwen_controlnet_union", ids) |
| self.assertIn("instantid_sdxl", ids) |
| self.assertFalse(candidate_by_id("instantid_sdxl").implemented) |
|
|
| def test_tavern_json_import_maps_fields_to_draft_package(self) -> None: |
| card = { |
| "spec": "chara_card_v2", |
| "spec_version": "2.0", |
| "data": { |
| "name": "测试角色", |
| "description": "一名用于导入验证的角色。\n第二行会被保留。", |
| "personality": "冷静、敏锐,温柔", |
| "scenario": "用户正在测试角色卡导入。", |
| "first_mes": "你好,我已经导入完成。", |
| "alternate_greetings": ["备用开场 A", "备用开场 B"], |
| "creator_notes": "只用于测试。", |
| "tags": ["测试", "导入"], |
| "character_book": {"entries": [{"keys": ["地点"], "content": "测试空间"}]}, |
| }, |
| } |
|
|
| package = convert_tavern_card(card) |
|
|
| self.assertEqual(package["display_name"], "测试角色") |
| self.assertEqual(package["first_mes"], "你好,我已经导入完成。") |
| self.assertEqual(package["alternate_greetings"], ["备用开场 A", "备用开场 B"]) |
| self.assertEqual(package["profile"]["core_traits"][:3], ["冷静", "敏锐", "温柔"]) |
| self.assertEqual(package["metadata"]["spec"], "chara_card_v2") |
| self.assertEqual(package["metadata"]["character_book"]["entries"][0]["content"], "测试空间") |
|
|
| def test_load_and_write_tavern_draft(self) -> None: |
| root = TMP_ROOT / "tavern_draft" |
| root.mkdir(parents=True, exist_ok=True) |
| source = root / "card.json" |
| source.write_text(json.dumps({"name": "Plain Card", "first_mes": "Hi"}), encoding="utf-8") |
|
|
| package = convert_tavern_card(load_tavern_json(source), forced_id="plain_card") |
| path = write_character_draft(package, root / "characters") |
|
|
| self.assertTrue(path.exists()) |
| parsed = json.loads(path.read_text(encoding="utf-8")) |
| self.assertEqual(parsed["id"], "plain_card") |
| self.assertEqual(parsed["first_mes"], "Hi") |
|
|
| def test_mock_asset_package_creates_manifest_grid_and_assets(self) -> None: |
| output_root = TMP_ROOT / "mock_assets" |
| output_root.mkdir(parents=True, exist_ok=True) |
| manifest = create_mock_asset_package( |
| character_id="spike_test", |
| display_name="星核", |
| output_root=output_root, |
| seed=7, |
| ) |
| run_dir = Path(manifest["paths"]["run_dir"]) |
|
|
| self.assertEqual(len(manifest["assets"]), len(EXPRESSIONS)) |
| self.assertTrue((run_dir / "generated" / "manifest.json").exists()) |
| self.assertTrue((run_dir / "generated" / "report.md").exists()) |
| self.assertTrue((run_dir / "generated" / "asset_grid.png").exists()) |
| for expression in EXPRESSIONS: |
| self.assertTrue((run_dir / "assets" / "characters" / "spike_test" / f"{expression}.png").exists()) |
|
|
| def test_gate_requires_timing_and_usable_assets(self) -> None: |
| manifest = { |
| "assets": [{"usable": True} for _ in range(6)], |
| "model_results": [ |
| { |
| "benchmark_case": "warm_four", |
| "status": "ok", |
| "loaded_before": True, |
| "duration_seconds": 55.0, |
| }, |
| { |
| "benchmark_case": "eight_expression_total", |
| "status": "ok", |
| "duration_seconds": 170.0, |
| }, |
| ], |
| } |
| gate = _evaluate_gate(manifest) |
| self.assertTrue(gate["ready_for_character_workshop"]) |
|
|
| manifest["model_results"][1]["duration_seconds"] = 240.0 |
| gate = _evaluate_gate(manifest) |
| self.assertFalse(gate["ready_for_character_workshop"]) |
| self.assertIn("导入角色卡", gate["fallback_recommendation"]) |
|
|
|
|
| if __name__ == "__main__": |
| unittest.main() |
|
|