package pipeline import ( "bytes" "encoding/json" "os" "path/filepath" "testing" "git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/erf" "git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/gff" "git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project" ) const paletteTestSkeleton = `{ "file_type": "ITP ", "file_version": "V3.2", "root": { "struct_type": 4294967295, "fields": [ { "label": "MAIN", "type": "List", "value": [ { "struct_type": 0, "fields": [ {"label": "STRREF", "type": "DWord", "value": 500}, { "label": "LIST", "type": "List", "value": [ { "struct_type": 0, "fields": [ {"label": "STRREF", "type": "DWord", "value": 6699}, {"label": "ID", "type": "Byte", "value": 23}, { "label": "LIST", "type": "List", "value": [ { "struct_type": 0, "fields": [ {"label": "NAME", "type": "CExoString", "value": "Stale Junk"}, {"label": "RESREF", "type": "ResRef", "value": "stalejunk"} ] } ] } ] }, { "struct_type": 0, "fields": [ {"label": "STRREF", "type": "DWord", "value": 6753}, {"label": "ID", "type": "Byte", "value": 24} ] } ] } ] } ] } ] } } ` func paletteTestItem(resref, name string, paletteID int) string { return `{ "file_type": "UTI ", "file_version": "V3.2", "root": { "struct_type": 4294967295, "fields": [ {"label": "TemplateResRef", "type": "ResRef", "value": "` + resref + `"}, { "label": "LocalizedName", "type": "CExoLocString", "value": {"string_ref": 4294967295, "entries": [{"id": 0, "value": "` + name + `"}]} }, {"label": "PaletteID", "type": "Byte", "value": ` + itoa(paletteID) + `} ] } } ` } func itoa(v int) string { data, _ := json.Marshal(v) return string(data) } func TestBuildProjectsPaletteDescriptorsAndExtractSkipsThem(t *testing.T) { root := t.TempDir() mustMkdir(t, filepath.Join(root, "src", "module")) mustMkdir(t, filepath.Join(root, "src", "palettes")) mustMkdir(t, filepath.Join(root, "src", "blueprints", "items")) mustMkdir(t, filepath.Join(root, "build")) mustWriteFile(t, filepath.Join(root, "nwn-tool.json"), `{ "module": {"name": "Test Module", "resref": "testmod"}, "paths": {"source": "src", "build": "build"} } `) mustWriteFile(t, filepath.Join(root, "src", "module", "module.ifo.json"), `{ "file_type": "IFO ", "file_version": "V3.2", "root": {"struct_type": 4294967295, "fields": [{"label": "Mod_Name", "type": "CExoString", "value": "Test Module"}]} } `) mustWriteFile(t, filepath.Join(root, "src", "palettes", "itempalcus.itp.json"), paletteTestSkeleton) mustWriteFile(t, filepath.Join(root, "src", "blueprints", "items", "i_b.uti.json"), paletteTestItem("i_b", "Bravo Item", 23)) mustWriteFile(t, filepath.Join(root, "src", "blueprints", "items", "i_a.uti.json"), paletteTestItem("i_a", "Alpha Item", 23)) mustWriteFile(t, filepath.Join(root, "src", "blueprints", "items", "i_hidden.uti.json"), paletteTestItem("i_hidden", "Hidden Item", 255)) p, err := project.Load(root) if err != nil { t.Fatalf("load project: %v", err) } if err := p.Scan(); err != nil { t.Fatalf("scan: %v", err) } if _, err := BuildModule(p); err != nil { t.Fatalf("build: %v", err) } archiveFile, err := os.Open(p.ModuleArchivePath()) if err != nil { t.Fatalf("open module archive: %v", err) } defer archiveFile.Close() archive, err := erf.Read(archiveFile) if err != nil { t.Fatalf("read module archive: %v", err) } var palette *gff.Document for _, resource := range archive.Resources { if resource.Name == "itempalcus" { document, err := gff.Read(bytes.NewReader(resource.Data)) if err != nil { t.Fatalf("decode itempalcus: %v", err) } palette = &document } } if palette == nil { t.Fatal("itempalcus missing from built module") } canonical, err := json.Marshal(palette) if err != nil { t.Fatalf("marshal palette: %v", err) } text := string(canonical) if bytes.Contains(canonical, []byte("stalejunk")) { t.Fatalf("stale descriptor survived projection: %s", text) } for _, resref := range []string{"i_a", "i_b"} { if !bytes.Contains(canonical, []byte(resref)) { t.Fatalf("descriptor %s missing from projection: %s", resref, text) } } if bytes.Contains(canonical, []byte("i_hidden")) { t.Fatalf("PaletteID 255 blueprint leaked into projection: %s", text) } if a, b := bytes.Index(canonical, []byte("i_a")), bytes.Index(canonical, []byte("i_b")); a > b { t.Fatalf("descriptors not name-sorted: %s", text) } if _, err := Compare(p); err != nil { t.Fatalf("compare after build: %v", err) } // Extraction must never write palcus files back into source. if err := os.Remove(filepath.Join(root, "src", "palettes", "itempalcus.itp.json")); err != nil { t.Fatalf("remove skeleton: %v", err) } if err := p.Scan(); err != nil { t.Fatalf("rescan: %v", err) } if _, err := Extract(p); err != nil { t.Fatalf("extract: %v", err) } if _, err := os.Stat(filepath.Join(root, "src", "palettes", "itempalcus.itp.json")); !os.IsNotExist(err) { t.Fatalf("extract wrote palcus file back (stat err: %v)", err) } }