文件预览

test_migrate_3json.py

查看 lobster-novel 技能包中的文件内容。

文件内容

tests/test_migrate_3json.py

#!/usr/bin/env python3
"""Tests for migrate_3json.py — 3JSON → story-state.json migration."""
import sys, json, tempfile, os
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "core"))
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))

from migrate_3json import (
    migrate_project, check_project,
    _parse_chapter_num, _clean_title, _name_to_id,
    _normalize_characters, _normalize_chapters, _normalize_hooks,
)


def test_parse_chapter_num():
    assert _parse_chapter_num(1) == 1
    assert _parse_chapter_num("Ch001") == 1
    assert _parse_chapter_num("v10ch001") == 1
    assert _parse_chapter_num("V5Ch001") == 1
    assert _parse_chapter_num("ch20") == 20
    assert _parse_chapter_num("") == 0
    assert _parse_chapter_num("abc") == 0
    print("✅ test_parse_chapter_num")


def test_clean_title():
    assert _clean_title("第1章 测试") == "第1章 测试"
    assert _clean_title("[V5Ch001] 标题") == "标题"
    assert _clean_title("V5Ch001 标题") == "标题"
    print("✅ test_clean_title")


def test_name_to_id():
    assert _name_to_id("理查德·泰森") == "char_理查德_泰森"
    assert _name_to_id("梅丽安") == "char_梅丽安"
    print("✅ test_name_to_id")


def test_normalize_characters_v10():
    """V10 format: {characters: {name: {...}}, carry_over: {...}}"""
    raw = {
        "characters": {
            "林风": {
                "role": "主角",
                "first_appearance": "v10ch001",
                "last_appearance": "v10ch020",
                "status": "active",
                "state": "寻找星辰碎片",
                "key_items": ["星辰剑"],
            }
        }
    }
    result = _normalize_characters(raw, "dict_v10")
    assert "char_林风" in result
    assert result["char_林风"]["first_appearance"] == 1
    assert result["char_林风"]["last_appearance"] == 20
    assert "星辰剑" in result["char_林风"]["key_items"]
    print("✅ test_normalize_characters_v10")


def test_normalize_characters_v5():
    """V5 format: {name: {...}}"""
    raw = {
        "林风": {
            "role": "配角",
            "first_appearance": "ch001",
            "last_appearance": "ch030",
        }
    }
    result = _normalize_characters(raw, "dict_v5")
    assert "char_林风" in result
    assert result["char_林风"]["first_appearance"] == 1
    print("✅ test_normalize_characters_v5")


def test_normalize_chapters_list_v5():
    """list_v5: [{chapter, title, characters, key_events}]"""
    raw = [
        {"chapter": 1, "title": "开始", "characters": ["林风"], "key_events": ["出发"]},
        {"chapter": 2, "title": "路上", "characters": ["林风", "冷月"], "key_events": ["相遇"]},
    ]
    result = _normalize_chapters(raw, "list_v5")
    assert 1 in result
    assert 2 in result
    assert result[1]["title"] == "开始"
    assert "冷月" in result[2]["characters_present"]
    print("✅ test_normalize_chapters_list_v5")


def test_normalize_chapters_dict_v9():
    """dict_v9: {entries: [{chapter, title, characters, key_events}]}"""
    raw = {
        "entries": [
            {"chapter": "Ch001", "title": "测试", "characters": ["林风"],
             "key_events": ["事件1"]},
        ]
    }
    result = _normalize_chapters(raw, "dict_v9")
    assert 1 in result
    assert result[1]["key_events"] == ["事件1"]
    print("✅ test_normalize_chapters_dict_v9")


def test_normalize_chapters_dict_v10():
    """dict_v10: {volume: {Ch001: {title, characters: {name: {...}}, ...}}}"""
    raw = {
        "V10_测试": {
            "Ch001": {
                "title": "开始",
                "characters": {"林风": {"role": "登场", "scene": "森林", "interaction": ""}},
                "key_events": ["出发"],
            }
        }
    }
    result = _normalize_chapters(raw, "dict_v10")
    assert 1 in result
    assert result[1]["title"] == "开始"
    assert "林风" in result[1]["characters_present"]
    print("✅ test_normalize_chapters_dict_v10")


def test_normalize_hooks_list():
    """List hooks format."""
    raw = [
        {"id": "h1", "type": "悬念", "description": "星辰碎片的下落",
         "chapter": "Ch001", "status": "活跃", "expected_payoff": "Ch010"},
    ]
    result = _normalize_hooks(raw, "list")
    assert len(result) == 1
    assert result[0]["chapter_created"] == 1
    assert result[0]["description"] == "星辰碎片的下落"
    print("✅ test_normalize_hooks_list")


def test_normalize_hooks_dict_v9():
    """V9 dict hooks: {hook, desc, planted, payoff}"""
    raw = [
        {"hook": "蓝色羽毛的秘密", "desc": "羽毛从蓝色变为金色",
         "planted": "ch001", "payoff": "ch030", "status": "active"},
    ]
    result = _normalize_hooks(raw, "list")
    assert result[0]["chapter_created"] == 1
    print("✅ test_normalize_hooks_dict_v9")


def test_migrate_project_v10():
    """Full migration pipeline with V10-format data."""
    with tempfile.TemporaryDirectory() as tmp:
        # Create V10-format data
        ca = {"V10_测试": {"Ch001": {"title": "开始", "characters": {"林风": {"role": "登场", "scene": "森林"}}, "key_events": ["出发"]}}}
        cr = {"characters": {"林风": {"role": "主角", "first_appearance": "v10ch001", "last_appearance": "v10ch001", "status": "active"}}}
        hk = [{"id": "h1", "type": "悬念", "description": "秘密", "chapter": "Ch001", "status": "活跃"}]
        for name, data in [("chapter_appearances.json", ca), ("character_roster.json", cr), ("hooks.json", hk)]:
            (Path(tmp) / name).write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")

        report = migrate_project(tmp, dry_run=False)
        assert report["characters_migrated"] == 1
        assert report["chapters_migrated"] == 1
        assert report["hooks_migrated"] == 1
        assert (Path(tmp) / "story-state.json").exists()
        print("✅ test_migrate_project_v10")


def test_migrate_project_v9():
    """V9 format migration."""
    with tempfile.TemporaryDirectory() as tmp:
        ca = {"entries": [{"chapter": "Ch001", "title": "开始", "characters": ["林风"], "key_events": ["出发"]}],
              "v9ch001": ["林风"]}
        cr = {"characters": {"林风": {"role": "主角", "first_appearance": "ch001", "last_appearance": "ch020", "status": "active"}}}
        hk = [{"hook": "秘密", "desc": "测试", "planted": "ch001", "payoff": "", "status": "active"}]
        for name, data in [("chapter_appearances.json", ca), ("character_roster.json", cr), ("hooks.json", hk)]:
            (Path(tmp) / name).write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")

        report = migrate_project(tmp, dry_run=False)
        assert report["chapters_migrated"] >= 1
        assert report["characters_migrated"] >= 1
        assert (Path(tmp) / "story-state.json").exists()
        print("✅ test_migrate_project_v9")


def test_empty_project():
    """No JSON files should produce empty story-state."""
    with tempfile.TemporaryDirectory() as tmp:
        report = migrate_project(tmp, dry_run=False)
        assert report["characters_migrated"] == 0
        assert report["chapters_migrated"] == 0
        assert report["hooks_migrated"] == 0
        print("✅ test_empty_project")


def test_corrupted_hooks():
    """Corrupted hooks.json should not crash."""
    with tempfile.TemporaryDirectory() as tmp:
        (Path(tmp) / "hooks.json").write_text("{invalid json!!!", encoding="utf-8")
        report = migrate_project(tmp, dry_run=False)
        assert len(report["warnings"]) >= 1
        print("✅ test_corrupted_hooks")


def test_check_project():
    """Check command should report file status."""
    with tempfile.TemporaryDirectory() as tmp:
        (Path(tmp) / "bible.json").write_text('{"title": "测试"}', encoding="utf-8")
        (Path(tmp) / "chapter_appearances.json").write_text('[]', encoding="utf-8")
        result = check_project(tmp)
        assert "files" in result
        assert "chapter_appearances.json" in result["files"]
        assert "story-state.json" in result["files"]
        print("✅ test_check_project")


if __name__ == "__main__":
    test_parse_chapter_num()
    test_clean_title()
    test_name_to_id()
    test_normalize_characters_v10()
    test_normalize_characters_v5()
    test_normalize_chapters_list_v5()
    test_normalize_chapters_dict_v9()
    test_normalize_chapters_dict_v10()
    test_normalize_hooks_list()
    test_normalize_hooks_dict_v9()
    test_migrate_project_v10()
    test_migrate_project_v9()
    test_empty_project()
    test_corrupted_hooks()
    test_check_project()
    print("\n🎉 All migration tests passed!")