418 lines
11 KiB
Go
418 lines
11 KiB
Go
package music
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestUniqueNameHandlesShortStemCollisions(t *testing.T) {
|
|
used := map[string]struct{}{
|
|
"mus_wg_mystc": {},
|
|
}
|
|
|
|
got := UniqueName("mus_wg_mystc", used)
|
|
if got != "mus_wg_mystc_1" {
|
|
t.Fatalf("unexpected collision result: got %q want %q", got, "mus_wg_mystc_1")
|
|
}
|
|
}
|
|
|
|
func TestUniqueNameNoCollision(t *testing.T) {
|
|
used := map[string]struct{}{}
|
|
got := UniqueName("new_stem", used)
|
|
if got != "new_stem" {
|
|
t.Fatalf("expected 'new_stem', got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestUniqueNameMultipleCollisions(t *testing.T) {
|
|
used := map[string]struct{}{
|
|
"stem": {},
|
|
"stem_1": {},
|
|
"stem_2": {},
|
|
}
|
|
got := UniqueName("stem", used)
|
|
if got != "stem_3" {
|
|
t.Fatalf("expected 'stem_3', got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestGenerateStem(t *testing.T) {
|
|
used := map[string]struct{}{
|
|
"existing": {},
|
|
}
|
|
stem, err := GenerateStem("My Cool Track.mp3", "mus_", used)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if stem == "" {
|
|
t.Fatal("expected non-empty stem")
|
|
}
|
|
if len(stem) > MaxStemLen {
|
|
t.Fatalf("stem %q exceeds max length %d", stem, MaxStemLen)
|
|
}
|
|
if _, exists := used[stem]; !exists {
|
|
t.Fatal("expected stem to be registered in used set")
|
|
}
|
|
}
|
|
|
|
func TestGenerateStemPrefixTooLong(t *testing.T) {
|
|
_, err := GenerateStem("test.mp3", "this_prefix_is_way_too_long_for_stem_", nil)
|
|
if err == nil {
|
|
t.Fatal("expected error for too-long prefix")
|
|
}
|
|
}
|
|
|
|
func TestSlugWords(t *testing.T) {
|
|
words := SlugWords("My Cool Music Track (Official) [HD]")
|
|
if len(words) == 0 {
|
|
t.Fatal("expected non-empty words")
|
|
}
|
|
}
|
|
|
|
func TestSlugWordsStripsBrackets(t *testing.T) {
|
|
words := SlugWords("Song [Explicit] (Remix)")
|
|
for _, w := range words {
|
|
if w == "explicit" || w == "remix" {
|
|
t.Fatalf("bracket content should be removed, got word %q", w)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSanitizePrefix(t *testing.T) {
|
|
if got := SanitizePrefix("Mus_WG_"); got != "mus_wg_" {
|
|
t.Fatalf("unexpected prefix: %q", got)
|
|
}
|
|
if got := SanitizePrefix(" Test "); got != "test" {
|
|
t.Fatalf("unexpected prefix: %q", got)
|
|
}
|
|
if got := SanitizePrefix(""); got != "" {
|
|
t.Fatalf("expected empty prefix, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestReserveName(t *testing.T) {
|
|
used := map[string]struct{}{}
|
|
if err := ReserveName("testname", used); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if _, exists := used["testname"]; !exists {
|
|
t.Fatal("expected name to be reserved")
|
|
}
|
|
if err := ReserveName("testname", used); err == nil {
|
|
t.Fatal("expected error for duplicate name")
|
|
}
|
|
}
|
|
|
|
func TestValidateManualOutputFile(t *testing.T) {
|
|
if err := ValidateManualOutputFile("test.bmu"); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if err := ValidateManualOutputFile("test.txt"); err == nil {
|
|
t.Fatal("expected error for non-bmu extension")
|
|
}
|
|
if err := ValidateManualOutputFile(".bmu"); err == nil {
|
|
t.Fatal("expected error for empty stem")
|
|
}
|
|
}
|
|
|
|
func TestParseCreditsMarkdownMissingFile(t *testing.T) {
|
|
entries, err := ParseCreditsMarkdown("nonexistent.md")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error for missing file: %v", err)
|
|
}
|
|
if entries != nil {
|
|
t.Fatalf("expected nil entries for missing file, got %v", entries)
|
|
}
|
|
}
|
|
|
|
func TestParseCreditsMarkdownRealContent(t *testing.T) {
|
|
content := "# Header\n\n| Artist | Title | Output File | Original File | Album | Date | License / Copyright | Notes |\n|---|---|---|---|---|---|---|---|\n| Test Artist | Test Title | `output.bmu` | `original.mp3` | Test Album | 2024 | MIT | test |\n"
|
|
path := filepath.Join(t.TempDir(), "CREDITS.md")
|
|
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
|
t.Fatalf("write test credits: %v", err)
|
|
}
|
|
|
|
entries, err := ParseCreditsMarkdown(path)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(entries) != 1 {
|
|
t.Fatalf("expected 1 entry, got %d", len(entries))
|
|
}
|
|
if entries[0].Artist != "Test Artist" {
|
|
t.Fatalf("expected 'Test Artist', got %q", entries[0].Artist)
|
|
}
|
|
if entries[0].Title != "Test Title" {
|
|
t.Fatalf("expected 'Test Title', got %q", entries[0].Title)
|
|
}
|
|
if entries[0].OutputFile != "output.bmu" {
|
|
t.Fatalf("expected 'output.bmu', got %q", entries[0].OutputFile)
|
|
}
|
|
}
|
|
|
|
func TestRenderCreditsMarkdown(t *testing.T) {
|
|
entries := []CreditsEntry{
|
|
{
|
|
Artist: "Test Artist",
|
|
Title: "Test Title",
|
|
OutputFile: "output.bmu",
|
|
OriginalFile: "original.mp3",
|
|
Album: "Test Album",
|
|
Date: "2024",
|
|
Rights: "MIT",
|
|
Notes: "test note",
|
|
},
|
|
}
|
|
|
|
result := RenderCreditsMarkdown(entries)
|
|
if !strings.Contains(result, "Test Artist") {
|
|
t.Fatal("expected rendered output to contain artist")
|
|
}
|
|
if !strings.Contains(result, "output.bmu") {
|
|
t.Fatal("expected rendered output to contain output file")
|
|
}
|
|
}
|
|
|
|
func TestParseCreditsOverlay(t *testing.T) {
|
|
content := "# Header\n\n| Artist | Title | Output File | Original File |\n|---|---|---|---|\n| Overlay Artist | Ovr Title | `override.bmu` | `original.mp3` |\n"
|
|
path := filepath.Join(t.TempDir(), "CREDITS.md")
|
|
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
|
t.Fatalf("write test overlay: %v", err)
|
|
}
|
|
|
|
overlay, err := ParseCreditsOverlay(path)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(overlay.Entries) != 1 {
|
|
t.Fatalf("expected 1 overlay entry, got %d", len(overlay.Entries))
|
|
}
|
|
|
|
matched := overlay.Match("original.mp3", "")
|
|
if matched == nil {
|
|
t.Fatal("expected overlay match by original file")
|
|
}
|
|
if matched.Artist != "Overlay Artist" {
|
|
t.Fatalf("expected 'Overlay Artist', got %q", matched.Artist)
|
|
}
|
|
}
|
|
|
|
func TestApplyCreditsOverlay(t *testing.T) {
|
|
entry := &CreditsEntry{
|
|
Artist: "Original Artist",
|
|
Title: "Original Title",
|
|
}
|
|
overlay := &CreditsEntry{
|
|
Artist: "Overlay Artist",
|
|
}
|
|
|
|
ApplyCreditsOverlay(entry, overlay)
|
|
if entry.Artist != "Overlay Artist" {
|
|
t.Fatalf("expected 'Overlay Artist', got %q", entry.Artist)
|
|
}
|
|
if entry.Title != "Original Title" {
|
|
t.Fatalf("expected 'Original Title' preserved, got %q", entry.Title)
|
|
}
|
|
|
|
ApplyCreditsOverlay(entry, nil)
|
|
if entry.Artist != "Overlay Artist" {
|
|
t.Fatalf("expected nil overlay to be no-op, got %q", entry.Artist)
|
|
}
|
|
}
|
|
|
|
func TestValidateOverlayEntries(t *testing.T) {
|
|
generated := []CreditsEntry{
|
|
{OriginalFile: "track1.mp3", OutputFile: "track1.bmu"},
|
|
}
|
|
overlay := CreditsOverlay{
|
|
Entries: []CreditsEntry{
|
|
{OriginalFile: "track1.mp3"},
|
|
},
|
|
}
|
|
if err := ValidateOverlayEntries("test", overlay, generated); err != nil {
|
|
t.Fatalf("unexpected validation error: %v", err)
|
|
}
|
|
|
|
badOverlay := CreditsOverlay{
|
|
Entries: []CreditsEntry{
|
|
{OriginalFile: "nonexistent.mp3"},
|
|
},
|
|
}
|
|
if err := ValidateOverlayEntries("test", badOverlay, generated); err == nil {
|
|
t.Fatal("expected validation error for unknown original file")
|
|
}
|
|
}
|
|
|
|
func TestIsMusicAssetPath(t *testing.T) {
|
|
if !IsMusicAssetPath("envi/music") {
|
|
t.Fatal("expected 'envi/music' to be music path")
|
|
}
|
|
if !IsMusicAssetPath("envi/music/westgate") {
|
|
t.Fatal("expected 'envi/music/westgate' to be music path")
|
|
}
|
|
if IsMusicAssetPath("envi/textures") {
|
|
t.Fatal("expected 'envi/textures' not to be music path")
|
|
}
|
|
if IsMusicAssetPath("") {
|
|
t.Fatal("expected empty string not to be music path")
|
|
}
|
|
}
|
|
|
|
func TestFirstNonEmpty(t *testing.T) {
|
|
if got := FirstNonEmpty("", "hello", "world"); got != "hello" {
|
|
t.Fatalf("expected 'hello', got %q", got)
|
|
}
|
|
if got := FirstNonEmpty("", "", ""); got != "" {
|
|
t.Fatalf("expected empty, got %q", got)
|
|
}
|
|
if got := FirstNonEmpty("first"); got != "first" {
|
|
t.Fatalf("expected 'first', got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestJoinNonEmpty(t *testing.T) {
|
|
if got := JoinNonEmpty(", ", "a", "", "b"); got != "a, b" {
|
|
t.Fatalf("expected 'a, b', got %q", got)
|
|
}
|
|
if got := JoinNonEmpty(", "); got != "" {
|
|
t.Fatalf("expected empty, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestTrimSlashes(t *testing.T) {
|
|
if got := TrimSlashes("/foo/bar/"); got != "foo/bar" {
|
|
t.Fatalf("expected 'foo/bar', got %q", got)
|
|
}
|
|
if got := TrimSlashes("///"); got != "" {
|
|
t.Fatalf("expected empty, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestEscapeMarkdownCell(t *testing.T) {
|
|
result := EscapeMarkdownCell("a|b\nc")
|
|
if !strings.Contains(result, `\|`) {
|
|
t.Fatal("expected pipe to be escaped")
|
|
}
|
|
if !strings.Contains(result, "<br>") {
|
|
t.Fatal("expected newline to be replaced with <br>")
|
|
}
|
|
}
|
|
|
|
func TestWrapCodeCell(t *testing.T) {
|
|
if got := WrapCodeCell("test.bmu"); got != "`test.bmu`" {
|
|
t.Fatalf("expected '`test.bmu`', got %q", got)
|
|
}
|
|
if got := WrapCodeCell(""); got != "" {
|
|
t.Fatalf("expected empty string, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestSyncGeneratedCreditsArtifactsWritesNewFiles(t *testing.T) {
|
|
root := t.TempDir()
|
|
desired := map[string][]byte{
|
|
filepath.Join(root, "test.md"): []byte("content"),
|
|
}
|
|
changed, err := SyncGeneratedCreditsArtifacts(root, desired)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if changed != 1 {
|
|
t.Fatalf("expected 1 changed file, got %d", changed)
|
|
}
|
|
}
|
|
|
|
func TestSyncGeneratedCreditsArtifactsNoChanges(t *testing.T) {
|
|
root := t.TempDir()
|
|
path := filepath.Join(root, "test.md")
|
|
if err := os.WriteFile(path, []byte("content"), 0o644); err != nil {
|
|
t.Fatalf("write file: %v", err)
|
|
}
|
|
desired := map[string][]byte{
|
|
path: []byte("content"),
|
|
}
|
|
changed, err := SyncGeneratedCreditsArtifacts(root, desired)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if changed != 0 {
|
|
t.Fatalf("expected 0 changed files, got %d", changed)
|
|
}
|
|
}
|
|
|
|
func TestSyncGeneratedCreditsArtifactsRemovesStale(t *testing.T) {
|
|
root := t.TempDir()
|
|
stalePath := filepath.Join(root, "stale.md")
|
|
if err := os.WriteFile(stalePath, []byte("stale"), 0o644); err != nil {
|
|
t.Fatalf("write stale file: %v", err)
|
|
}
|
|
desired := map[string][]byte{}
|
|
changed, err := SyncGeneratedCreditsArtifacts(root, desired)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if changed != 1 {
|
|
t.Fatalf("expected 1 removed file, got %d", changed)
|
|
}
|
|
}
|
|
|
|
func TestPrefixForDir(t *testing.T) {
|
|
prefixes := map[string]string{
|
|
"envi/music/westgate": "mus_wg_",
|
|
}
|
|
if got := PrefixForDir(prefixes, "envi/music/westgate"); got != "mus_wg_" {
|
|
t.Fatalf("expected 'mus_wg_', got %q", got)
|
|
}
|
|
if got := PrefixForDir(prefixes, "envi/music/other"); got != "" {
|
|
t.Fatalf("expected empty prefix, got %q", got)
|
|
}
|
|
if got := PrefixForDir(nil, "envi/music"); got != "" {
|
|
t.Fatalf("expected empty prefix for nil map, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestMaxMin(t *testing.T) {
|
|
if got := Min(3, 5); got != 3 {
|
|
t.Fatalf("Min(3,5) = %d, want 3", got)
|
|
}
|
|
if got := Max(3, 5); got != 5 {
|
|
t.Fatalf("Max(3,5) = %d, want 5", got)
|
|
}
|
|
}
|
|
|
|
func TestResolveFFmpegEnv(t *testing.T) {
|
|
t.Setenv("SOW_FFMPEG", "/custom/ffmpeg")
|
|
path, err := ResolveFFmpeg()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if path != "/custom/ffmpeg" {
|
|
t.Fatalf("expected '/custom/ffmpeg', got %q", path)
|
|
}
|
|
}
|
|
|
|
func TestResolveFFprobeEnv(t *testing.T) {
|
|
t.Setenv("SOW_FFPROBE", "/custom/ffprobe")
|
|
path, err := ResolveFFprobe()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if path != "/custom/ffprobe" {
|
|
t.Fatalf("expected '/custom/ffprobe', got %q", path)
|
|
}
|
|
}
|
|
|
|
func TestFFmpegMissingErrHasInstallHints(t *testing.T) {
|
|
msg := ffmpegMissingErr("ffmpeg").Error()
|
|
for _, want := range []string{"ffmpeg", "winget", "brew", "apt", "SOW_FFMPEG"} {
|
|
if !strings.Contains(msg, want) {
|
|
t.Errorf("ffmpeg error missing %q; got:\n%s", want, msg)
|
|
}
|
|
}
|
|
probe := ffmpegMissingErr("ffprobe").Error()
|
|
if !strings.Contains(probe, "SOW_FFPROBE") {
|
|
t.Errorf("ffprobe error should mention SOW_FFPROBE; got:\n%s", probe)
|
|
}
|
|
}
|