package app import ( "bytes" "os" "path/filepath" "strings" "testing" "time" ) func TestRunBuildTopPackageUsesCachedCompiledOutputs(t *testing.T) { root := t.TempDir() mkdirAll(t, filepath.Join(root, "build")) mkdirAll(t, filepath.Join(root, ".cache", "2da")) mkdirAll(t, filepath.Join(root, "topdata", "assets", "gui")) mkdirAll(t, filepath.Join(root, "topdata", "data", "repadjust")) writeFile(t, filepath.Join(root, "nwn-tool.json"), `{ "module": { "name": "Test Module", "resref": "test_module" }, "topdata": { "source": "topdata", "build": ".cache", "package_hak": "sow_top.hak", "package_tlk": "sow_tlk.tlk" } }`+"\n") writeFile(t, filepath.Join(root, ".cache", "2da", "repadjust.2da"), "2DA V2.0\n\n Label\n0 TEST_LABEL\n") writeFile(t, filepath.Join(root, "build", "sow_tlk.tlk"), "compiled tlk") writeFile(t, filepath.Join(root, "topdata", "assets", "gui", "testicon.png"), "icon-data") writeFile(t, filepath.Join(root, "topdata", "data", "repadjust", "base.json"), "{ this is intentionally invalid json }\n") sourceTime := time.Now().Add(-2 * time.Hour) outputTime := time.Now().Add(-1 * time.Hour) setFileTime(t, filepath.Join(root, "topdata", "assets", "gui", "testicon.png"), sourceTime) setFileTime(t, filepath.Join(root, "topdata", "data", "repadjust", "base.json"), sourceTime) setFileTime(t, filepath.Join(root, ".cache", "2da", "repadjust.2da"), outputTime) setFileTime(t, filepath.Join(root, "build", "sow_tlk.tlk"), outputTime) var stdout bytes.Buffer ctx := context{ stdout: &stdout, stderr: &bytes.Buffer{}, cwd: root, args: []string{"build-top-package"}, } if err := runBuildTopPackage(ctx); err != nil { t.Fatalf("runBuildTopPackage failed: %v", err) } if !strings.Contains(stdout.String(), "top package hak:") { t.Fatalf("expected build-top-package output, got %q", stdout.String()) } if _, err := os.Stat(filepath.Join(root, "build", "sow_top.hak")); err != nil { t.Fatalf("expected packaged hak output: %v", err) } } func TestRunBuildHAKsEmitsCompactSummary(t *testing.T) { root := t.TempDir() mkdirAll(t, filepath.Join(root, "assets", "envi", "music", "westgate")) mkdirAll(t, filepath.Join(root, "build")) mkdirAll(t, filepath.Join(root, "src")) writeFile(t, filepath.Join(root, "nwn-tool.json"), `{ "module": { "name": "Test Module", "resref": "testmod" }, "paths": { "source": "src", "assets": "assets", "build": "build" }, "haks": [ { "name": "envi", "priority": 1, "max_bytes": 1048576, "split": false, "include": ["envi/**"] } ] }`+"\n") writeFile(t, filepath.Join(root, "assets", "envi", "music", "westgate", "AleandAnecdotes.mp3"), "source-mp3") writeFile(t, filepath.Join(root, "assets", "envi", "music", "westgate", "CREDITS.md"), "# NWN Music Pack Credits\n\n| Artist | Title | Output File | Original File | Album | Date | License / Copyright | Notes |\n|---|---|---|---|---|---|---|---|\n| Darren Curtis | Ale and Anecdotes | `mus_wg_andnc.bmu` | `AleandAnecdotes.mp3` | Darren's Commercially Free Music | | Darren Curtis Music | |\n") ffprobePath := filepath.Join(root, "ffprobe") writeFile(t, ffprobePath, "#!/bin/sh\nprintf '%s\\n' '{\"format\":{\"tags\":{\"artist\":\"Unknown\",\"title\":\"Broken Metadata\"}}}'\n") if err := os.Chmod(ffprobePath, 0o755); err != nil { t.Fatalf("chmod ffprobe: %v", err) } ffmpegPath := filepath.Join(root, "ffmpeg") writeFile(t, ffmpegPath, "#!/bin/sh\nfor last; do :; done\nprintf 'bmu-data' > \"$last\"\n") if err := os.Chmod(ffmpegPath, 0o755); err != nil { t.Fatalf("chmod ffmpeg: %v", err) } t.Setenv("SOW_FFMPEG", ffmpegPath) t.Setenv("SOW_FFPROBE", ffprobePath) var stdout bytes.Buffer ctx := context{ stdout: &stdout, stderr: &bytes.Buffer{}, cwd: root, args: []string{"build-haks"}, } if err := runBuildHAKs(ctx); err != nil { t.Fatalf("runBuildHAKs failed: %v", err) } output := stdout.String() if !strings.Contains(output, "Build HAKs ----------") { t.Fatalf("expected build header, got %q", output) } if !strings.Contains(output, "mapped: 1 music file(s); use --verbose to list mappings") { t.Fatalf("expected compact mapping summary, got %q", output) } if strings.Contains(output, "AleandAnecdotes.mp3 -> mus_wg_andnc.bmu") { t.Fatalf("did not expect verbose mapping in normal mode, got %q", output) } if !strings.Contains(output, "manifest: build/haks.json") { t.Fatalf("expected relative manifest path, got %q", output) } } func TestRunBuildHAKsVerboseListsMappings(t *testing.T) { root := t.TempDir() mkdirAll(t, filepath.Join(root, "assets", "envi", "music", "westgate")) mkdirAll(t, filepath.Join(root, "build")) mkdirAll(t, filepath.Join(root, "src")) writeFile(t, filepath.Join(root, "nwn-tool.json"), `{ "module": { "name": "Test Module", "resref": "testmod" }, "paths": { "source": "src", "assets": "assets", "build": "build" }, "haks": [ { "name": "envi", "priority": 1, "max_bytes": 1048576, "split": false, "include": ["envi/**"] } ] }`+"\n") writeFile(t, filepath.Join(root, "assets", "envi", "music", "westgate", "AleandAnecdotes.mp3"), "source-mp3") writeFile(t, filepath.Join(root, "assets", "envi", "music", "westgate", "CREDITS.md"), "# NWN Music Pack Credits\n\n| Artist | Title | Output File | Original File | Album | Date | License / Copyright | Notes |\n|---|---|---|---|---|---|---|---|\n| Darren Curtis | Ale and Anecdotes | `mus_wg_andnc.bmu` | `AleandAnecdotes.mp3` | Darren's Commercially Free Music | | Darren Curtis Music | |\n") ffprobePath := filepath.Join(root, "ffprobe") writeFile(t, ffprobePath, "#!/bin/sh\nprintf '%s\\n' '{\"format\":{\"tags\":{\"artist\":\"Unknown\",\"title\":\"Broken Metadata\"}}}'\n") if err := os.Chmod(ffprobePath, 0o755); err != nil { t.Fatalf("chmod ffprobe: %v", err) } ffmpegPath := filepath.Join(root, "ffmpeg") writeFile(t, ffmpegPath, "#!/bin/sh\nfor last; do :; done\nprintf 'bmu-data' > \"$last\"\n") if err := os.Chmod(ffmpegPath, 0o755); err != nil { t.Fatalf("chmod ffmpeg: %v", err) } t.Setenv("SOW_FFMPEG", ffmpegPath) t.Setenv("SOW_FFPROBE", ffprobePath) var stdout bytes.Buffer ctx := context{ stdout: &stdout, stderr: &bytes.Buffer{}, cwd: root, args: []string{"build-haks", "--verbose"}, } if err := runBuildHAKs(ctx); err != nil { t.Fatalf("runBuildHAKs failed: %v", err) } output := stdout.String() if !strings.Contains(output, "mappings:") { t.Fatalf("expected verbose mappings header, got %q", output) } if !strings.Contains(output, "AleandAnecdotes.mp3 -> mus_wg_andnc.bmu") { t.Fatalf("expected verbose mapping output, got %q", output) } if !strings.Contains(output, "wrote: envi (1 assets)") { t.Fatalf("expected verbose archive action, got %q", output) } } func mkdirAll(t *testing.T, path string) { t.Helper() if err := os.MkdirAll(path, 0o755); err != nil { t.Fatalf("mkdir %s: %v", path, err) } } func writeFile(t *testing.T, path, content string) { t.Helper() if err := os.WriteFile(path, []byte(content), 0o644); err != nil { t.Fatalf("write %s: %v", path, err) } } func setFileTime(t *testing.T, path string, modTime time.Time) { t.Helper() if err := os.Chtimes(path, modTime, modTime); err != nil { t.Fatalf("set file time %s: %v", path, err) } }