This commit is contained in:
voocel
2026-03-07 21:25:55 +08:00
commit 27bd85ef90
60 changed files with 5658 additions and 0 deletions

140
state/world_rules_test.go Normal file
View File

@@ -0,0 +1,140 @@
package state
import (
"os"
"path/filepath"
"testing"
"github.com/voocel/ainovel-cli/domain"
)
func TestSaveAndLoadWorldRules(t *testing.T) {
dir := t.TempDir()
store := NewStore(dir)
rules := []domain.WorldRule{
{Category: "magic", Rule: "法术消耗精神力", Boundary: "精神力耗尽会昏迷"},
{Category: "magic", Rule: "禁咒需要三人合力", Boundary: "单人强行施放会死亡"},
{Category: "society", Rule: "贵族拥有领地裁判权", Boundary: "不得越权审判其他领地居民"},
}
if err := store.SaveWorldRules(rules); err != nil {
t.Fatalf("SaveWorldRules: %v", err)
}
// 验证 JSON 文件存在
if _, err := os.Stat(filepath.Join(dir, "world_rules.json")); err != nil {
t.Fatalf("world_rules.json not created: %v", err)
}
// 验证 Markdown 文件存在
if _, err := os.Stat(filepath.Join(dir, "world_rules.md")); err != nil {
t.Fatalf("world_rules.md not created: %v", err)
}
loaded, err := store.LoadWorldRules()
if err != nil {
t.Fatalf("LoadWorldRules: %v", err)
}
if len(loaded) != 3 {
t.Fatalf("expected 3 rules, got %d", len(loaded))
}
if loaded[0].Category != "magic" || loaded[0].Rule != "法术消耗精神力" {
t.Errorf("first rule mismatch: %+v", loaded[0])
}
if loaded[2].Category != "society" {
t.Errorf("third rule category mismatch: %+v", loaded[2])
}
}
func TestLoadWorldRules_Empty(t *testing.T) {
dir := t.TempDir()
store := NewStore(dir)
rules, err := store.LoadWorldRules()
if err != nil {
t.Fatalf("LoadWorldRules on empty dir: %v", err)
}
if rules != nil {
t.Fatalf("expected nil for missing file, got %v", rules)
}
}
func TestSaveWorldRules_Overwrite(t *testing.T) {
dir := t.TempDir()
store := NewStore(dir)
v1 := []domain.WorldRule{{Category: "old", Rule: "旧规则", Boundary: "旧边界"}}
if err := store.SaveWorldRules(v1); err != nil {
t.Fatalf("SaveWorldRules v1: %v", err)
}
v2 := []domain.WorldRule{
{Category: "new", Rule: "新规则A", Boundary: "新边界A"},
{Category: "new", Rule: "新规则B", Boundary: "新边界B"},
}
if err := store.SaveWorldRules(v2); err != nil {
t.Fatalf("SaveWorldRules v2: %v", err)
}
loaded, err := store.LoadWorldRules()
if err != nil {
t.Fatalf("LoadWorldRules: %v", err)
}
if len(loaded) != 2 {
t.Fatalf("expected 2 rules after overwrite, got %d", len(loaded))
}
if loaded[0].Category != "new" {
t.Errorf("expected new category, got %s", loaded[0].Category)
}
}
func TestRenderWorldRules(t *testing.T) {
rules := []domain.WorldRule{
{Category: "magic", Rule: "法术消耗精神力", Boundary: "精神力耗尽会昏迷"},
{Category: "society", Rule: "贵族有裁判权", Boundary: ""},
{Category: "magic", Rule: "禁咒需三人", Boundary: "单人施放会死"},
}
md := renderWorldRules(rules)
// 验证标题
if got := md[:len("# 世界观规则")]; got != "# 世界观规则" {
t.Errorf("missing title, got: %s", got)
}
// 验证分组magic 应该出现在 society 之前(按输入顺序)
magicPos := indexOf(md, "## magic")
societyPos := indexOf(md, "## society")
if magicPos < 0 || societyPos < 0 {
t.Fatalf("missing category headers in:\n%s", md)
}
if magicPos >= societyPos {
t.Errorf("magic should appear before society")
}
// 验证边界渲染:有 boundary 的条目应包含"边界"字样
if indexOf(md, "边界:精神力耗尽会昏迷") < 0 {
t.Errorf("missing boundary in:\n%s", md)
}
// 验证无 boundary 的条目不输出边界行
if indexOf(md, "边界:\n") >= 0 {
t.Errorf("empty boundary should not be rendered")
}
}
func TestRenderWorldRules_EmptyCategoryFallback(t *testing.T) {
rules := []domain.WorldRule{
{Category: "", Rule: "无分类规则", Boundary: "边界"},
}
md := renderWorldRules(rules)
if indexOf(md, "## other") < 0 {
t.Errorf("empty category should fall back to 'other', got:\n%s", md)
}
}
func indexOf(s, substr string) int {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return i
}
}
return -1
}