package topdata import ( "bytes" "os" "path/filepath" "strings" "testing" ) func TestRunConvertCommandInfersTemplateNamespaceAndOutput(t *testing.T) { root := testProjectRoot(t) writeFile(t, filepath.Join(root, "nwn-tool.json"), `{ "module": {"name": "Test", "resref": "test"}, "paths": {"source": "src", "assets": "assets", "build": "build"}, "topdata": {"source": "topdata", "build": "build/topdata"} }`+"\n") mkdirAll(t, filepath.Join(root, "topdata", "templates")) mkdirAll(t, filepath.Join(root, "topdata", "data", "appearance", "modules")) writeFile(t, filepath.Join(root, "topdata", "templates", "config.json"), `{ "tables": { "template_appearance": { "namespace": "appearance", "key_fields": ["LABEL"] } } }`+"\n") writeFile(t, filepath.Join(root, "topdata", "templates", "template_appearance.2da"), "2DA V2.0\n\nLABEL STRING_REF NAME\n960 \"Zombie, Ash, Hot\" **** Zombie_Ash_Hot\n961 \"Zombie, Ash, Done\" **** Zombie_Ash_Done\n") oldCwd, err := os.Getwd() if err != nil { t.Fatalf("getwd: %v", err) } t.Cleanup(func() { _ = os.Chdir(oldCwd) }) if err := os.Chdir(root); err != nil { t.Fatalf("chdir: %v", err) } var stdout bytes.Buffer if err := RunConvertCommand([]string{ "2da-to-module", "--name", "ashzombies", "topdata/templates/template_appearance.2da", }, &stdout); err != nil { t.Fatalf("RunConvertCommand failed: %v", err) } outputPath := filepath.Join(root, "topdata", "data", "appearance", "modules", "add_appearance_ashzombies.json") raw, err := os.ReadFile(outputPath) if err != nil { t.Fatalf("read output: %v", err) } text := string(raw) if !strings.Contains(text, `"appearance:zombie_ash_hot"`) || !strings.Contains(text, `"appearance:zombie_ash_done"`) { t.Fatalf("unexpected output:\n%s", text) } if !strings.Contains(stdout.String(), "converted entries: 2") { t.Fatalf("unexpected stdout: %s", stdout.String()) } } func TestRunConvertCommandFailsOnUnkeyedRows(t *testing.T) { root := testProjectRoot(t) writeFile(t, filepath.Join(root, "nwn-tool.json"), `{ "module": {"name": "Test", "resref": "test"}, "paths": {"source": "src", "assets": "assets", "build": "build"}, "topdata": {"source": "topdata", "build": "build/topdata"} }`+"\n") mkdirAll(t, filepath.Join(root, "topdata", "templates")) writeFile(t, filepath.Join(root, "topdata", "templates", "config.json"), `{ "tables": { "template_appearance": { "namespace": "appearance", "key_fields": ["STRING_REF"] } } }`+"\n") writeFile(t, filepath.Join(root, "topdata", "templates", "template_appearance.2da"), "2DA V2.0\n\nLABEL STRING_REF NAME\n960 \"Zombie, Ash, Hot\" **** Zombie_Ash_Hot\n") oldCwd, err := os.Getwd() if err != nil { t.Fatalf("getwd: %v", err) } t.Cleanup(func() { _ = os.Chdir(oldCwd) }) if err := os.Chdir(root); err != nil { t.Fatalf("chdir: %v", err) } err = RunConvertCommand([]string{ "2da-to-module", "--name", "ashzombies", "topdata/templates/template_appearance.2da", }, &bytes.Buffer{}) if err == nil || !strings.Contains(err.Error(), "unable to generate keys for row ids 960") { t.Fatalf("expected unkeyed row failure, got %v", err) } } func TestConvert2DAToModuleFailsOnDuplicateGeneratedKeysByDefault(t *testing.T) { _, err := convert2DAToModule(parsed2DA{ Columns: []string{"LABEL"}, Rows: []map[string]any{ {"id": 1, "LABEL": "Zombie"}, {"id": 2, "LABEL": "Zombie"}, }, }, "topdata/data/appearance/modules/add_appearance_ashzombies.json", convertOptions{ Namespace: "appearance", CollisionMode: "error", }) if err == nil || !strings.Contains(err.Error(), `duplicate generated key "appearance:zombie"`) { t.Fatalf("expected duplicate generated key error, got %v", err) } } func TestParse2DAFileRejectsRowsWithTooManyFields(t *testing.T) { root := t.TempDir() inputPath := filepath.Join(root, "bad.2da") writeFile(t, inputPath, "2DA V2.0\n\nLABEL NAME\n0 one two three\n") _, err := parse2DAFile(inputPath) if err == nil || !strings.Contains(err.Error(), "has 3 values but only 2 columns are declared") { t.Fatalf("expected row width error, got %v", err) } }