package validate import ( "os" "path/filepath" "strings" "testing" "thehouseoficarus/internal/behavior" "thehouseoficarus/internal/item" "thehouseoficarus/internal/object" "thehouseoficarus/internal/world" ) // Regression: a talk config with nodes but no "start" node is dead content at // runtime; validation must report it. func TestValidateTalkConfigRequiresStartNode(t *testing.T) { cfg := &behavior.TalkConfig{Nodes: map[string]behavior.TalkNode{ "greet": {Messages: []string{"hi"}}, }} issues := validateTalkConfig("test", cfg, nil, nil, nil, nil) found := false for _, iss := range issues { if strings.Contains(iss.Message, "start") { found = true } } if !found { t.Fatalf("missing start node not reported: %+v", issues) } clean := &behavior.TalkConfig{Nodes: map[string]behavior.TalkNode{"start": {}}} for _, iss := range validateTalkConfig("test", clean, nil, nil, nil, nil) { if strings.Contains(iss.Message, "start") { t.Fatalf("start node wrongly reported: %+v", iss) } } } // Regression: an unknown craft type/subtype leaves the action set but never // advancing, so the player appears stuck; it must be an ERROR. func TestValidateCraftBlocksRejectsUnknownType(t *testing.T) { dir := t.TempDir() if err := os.MkdirAll(filepath.Join(dir, "items"), 0755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "items", "thing.yaml"), []byte("name: thing\ncraft:\n - type: smiting\n ingredients:\n - items: [ore]\n quantity: 1\n"), 0644); err != nil { t.Fatal(err) } src := Source{ DataDir: dir, Items: item.NewItemStore(dir), Objects: object.NewObjectStore(dir), Mobs: world.NewMobStore(dir), World: world.New(dir), } found := false for _, issue := range validateCraftBlocks(src) { if strings.Contains(issue.Message, "unknown type/subtype") { found = true if issue.Level != "ERROR" { t.Fatalf("unknown craft type must be an ERROR: %+v", issue) } } } if !found { t.Fatal("expected an ERROR for an unknown craft type/subtype") } } // Regression: an unknown safespot max_block_size silently disables cover; it // must be an ERROR. func TestValidateObjectsRejectsUnknownSafespotSize(t *testing.T) { dir := t.TempDir() if err := os.MkdirAll(filepath.Join(dir, "objects"), 0755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "objects", "rock.yaml"), []byte("name: rock\nsafespot:\n max_block_size: huge\n"), 0644); err != nil { t.Fatal(err) } src := Source{ DataDir: dir, Items: item.NewItemStore(dir), Objects: object.NewObjectStore(dir), Mobs: world.NewMobStore(dir), World: world.New(dir), } found := false for _, issue := range validateObjects(src) { if strings.Contains(issue.Message, "max_block_size") { found = true if issue.Level != "ERROR" { t.Fatalf("unknown safespot size must be an ERROR: %+v", issue) } } } if !found { t.Fatal("expected an ERROR for an unknown safespot.max_block_size") } } // Regression: a gather drop with weight <= 0 can never be selected (the // weighted roll has zero total); it must be flagged. func TestValidateGatherWarnsOnWeightlessDrop(t *testing.T) { cfg := &behavior.GatherConfig{Drops: []behavior.DropEntry{{ItemID: "ore", Weight: 0}}} issues := validateGather("test", cfg, map[string]bool{"ore": true}, map[string]bool{}) found := false for _, iss := range issues { if strings.Contains(iss.Message, "weight") { found = true } } if !found { t.Fatalf("weightless drop not warned: %+v", issues) } }