package utils
import (
"testing"
)
func TestParseSelectionFilename(t *testing.T) {
tests := []struct {
name string
filename string
expectedBase string
expectedStart float64
expectedEnd float64
expectError bool
errorContains string
}{
{
name: "Simple filename with integer times",
filename: "20250517_214501-102-133.wav",
expectedBase: "20250517_214501",
expectedStart: 102.0,
expectedEnd: 133.0,
expectError: false,
},
{
name: "Filename with decimal times",
filename: "20250517_214501-102.5-133.7.wav",
expectedBase: "20250517_214501",
expectedStart: 102.5,
expectedEnd: 133.7,
expectError: false,
},
{
name: "Filename with prefix and dashes",
filename: "A05-20250517_214501-102-133.wav",
expectedBase: "A05-20250517_214501",
expectedStart: 102.0,
expectedEnd: 133.0,
expectError: false,
},
{
name: "Filename with multiple dashes in base",
filename: "Site-A-05-20250517_214501-102.5-133.7.wav",
expectedBase: "Site-A-05-20250517_214501",
expectedStart: 102.5,
expectedEnd: 133.7,
expectError: false,
},
{
name: "PNG extension",
filename: "A05-20250517_214501-102-133.png",
expectedBase: "A05-20250517_214501",
expectedStart: 102.0,
expectedEnd: 133.0,
expectError: false,
},
{
name: "Zero start time",
filename: "20250517_214501-0-10.5.wav",
expectedBase: "20250517_214501",
expectedStart: 0.0,
expectedEnd: 10.5,
expectError: false,
},
{
name: "Invalid format - too few parts",
filename: "20250517_214501-102.wav",
expectError: true,
errorContains: "expected at least 3 dash-separated parts",
},
{
name: "Invalid format - no dashes",
filename: "20250517_214501.wav",
expectError: true,
errorContains: "expected at least 3 dash-separated parts",
},
{
name: "Invalid start time - not a number",
filename: "20250517_214501-abc-133.wav",
expectError: true,
errorContains: "invalid start time",
},
{
name: "Invalid end time - not a number",
filename: "20250517_214501-102-xyz.wav",
expectError: true,
errorContains: "invalid end time",
},
{
name: "Start time equals end time",
filename: "20250517_214501-102-102.wav",
expectError: true,
errorContains: "start time",
},
{
name: "Start time greater than end time",
filename: "20250517_214501-133-102.wav",
expectError: true,
errorContains: "start time",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
base, start, end, err := ParseSelectionFilename(tt.filename)
if tt.expectError {
if err == nil {
t.Errorf("Expected error containing '%s', got nil", tt.errorContains)
} else if tt.errorContains != "" && !containsString(err.Error(), tt.errorContains) {
t.Errorf("Expected error containing '%s', got: %v", tt.errorContains, err)
}
return
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}
if base != tt.expectedBase {
t.Errorf("Expected base '%s', got '%s'", tt.expectedBase, base)
}
if start != tt.expectedStart {
t.Errorf("Expected start %.2f, got %.2f", tt.expectedStart, start)
}
if end != tt.expectedEnd {
t.Errorf("Expected end %.2f, got %.2f", tt.expectedEnd, end)
}
})
}
}
func TestParseMLFolderName(t *testing.T) {
tests := []struct {
name string
folderName string
expectedFilterName string
expectedDate string
expectError bool
errorContains string
}{
{
name: "Standard folder name",
folderName: "Clips_opensoundscape-kiwi-1.0_2025-11-14",
expectedFilterName: "opensoundscape-kiwi-1.0",
expectedDate: "2025-11-14",
expectError: false,
},
{
name: "Filter name with underscores",
folderName: "Clips_my_model_v2_2025-01-15",
expectedFilterName: "my_model_v2",
expectedDate: "2025-01-15",
expectError: false,
},
{
name: "Simple filter name",
folderName: "Clips_kiwi_2024-12-31",
expectedFilterName: "kiwi",
expectedDate: "2024-12-31",
expectError: false,
},
{
name: "Missing Clips_ prefix",
folderName: "opensoundscape-kiwi-1.0_2025-11-14",
expectError: true,
errorContains: "invalid ML folder name format",
},
{
name: "Missing date",
folderName: "Clips_opensoundscape-kiwi-1.0",
expectError: true,
errorContains: "invalid ML folder name format",
},
{
name: "Invalid date format",
folderName: "Clips_opensoundscape-kiwi-1.0_11-14-2025",
expectError: true,
errorContains: "invalid ML folder name format",
},
{
name: "Missing filter name",
folderName: "Clips__2025-11-14",
expectError: true,
errorContains: "invalid ML folder name format",
},
{
name: "Extra underscores at end",
folderName: "Clips_kiwi_2025-11-14_extra",
expectError: true,
errorContains: "invalid ML folder name format",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filterName, date, err := ParseMLFolderName(tt.folderName)
if tt.expectError {
if err == nil {
t.Errorf("Expected error containing '%s', got nil", tt.errorContains)
} else if tt.errorContains != "" && !containsString(err.Error(), tt.errorContains) {
t.Errorf("Expected error containing '%s', got: %v", tt.errorContains, err)
}
return
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}
if filterName != tt.expectedFilterName {
t.Errorf("Expected filter name '%s', got '%s'", tt.expectedFilterName, filterName)
}
if date != tt.expectedDate {
t.Errorf("Expected date '%s', got '%s'", tt.expectedDate, date)
}
})
}
}
func TestValidateWAVPNGPairs(t *testing.T) {
tests := []struct {
name string
wavFiles []string
pngFiles []string
expectedPaired []string
expectedMismatched []string
}{
{
name: "All files paired",
wavFiles: []string{"file1-10-20.wav", "file2-30-40.wav"},
pngFiles: []string{"file1-10-20.png", "file2-30-40.png"},
expectedPaired: []string{"file1-10-20", "file2-30-40"},
expectedMismatched: []string{},
},
{
name: "Missing PNG for one WAV",
wavFiles: []string{"file1-10-20.wav", "file2-30-40.wav", "file3-50-60.wav"},
pngFiles: []string{"file1-10-20.png", "file2-30-40.png"},
expectedPaired: []string{"file1-10-20", "file2-30-40"},
expectedMismatched: []string{"file3-50-60.wav"},
},
{
name: "Missing all PNGs",
wavFiles: []string{"file1-10-20.wav", "file2-30-40.wav"},
pngFiles: []string{},
expectedPaired: []string{},
expectedMismatched: []string{"file1-10-20.wav", "file2-30-40.wav"},
},
{
name: "Extra PNG files (no WAV)",
wavFiles: []string{"file1-10-20.wav"},
pngFiles: []string{"file1-10-20.png", "file2-30-40.png", "file3-50-60.png"},
expectedPaired: []string{"file1-10-20"},
expectedMismatched: []string{},
},
{
name: "No files",
wavFiles: []string{},
pngFiles: []string{},
expectedPaired: []string{},
expectedMismatched: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
paired, mismatched := ValidateWAVPNGPairs(tt.wavFiles, tt.pngFiles)
if !stringSlicesEqual(paired, tt.expectedPaired) {
t.Errorf("Expected paired %v, got %v", tt.expectedPaired, paired)
}
if !stringSlicesEqual(mismatched, tt.expectedMismatched) {
t.Errorf("Expected mismatched %v, got %v", tt.expectedMismatched, mismatched)
}
})
}
}
func TestExtractDateTimePattern(t *testing.T) {
tests := []struct {
name string
filename string
expectedPattern string
expectedFound bool
}{
{
name: "8-digit date (YYYYMMDD)",
filename: "20250517_214501",
expectedPattern: "20250517_214501",
expectedFound: true,
},
{
name: "8-digit date with prefix",
filename: "A05-20250517_214501",
expectedPattern: "20250517_214501",
expectedFound: true,
},
{
name: "8-digit date with suffix",
filename: "20250517_214501-extra",
expectedPattern: "20250517_214501",
expectedFound: true,
},
{
name: "6-digit date (YYMMDD or DDMMYY)",
filename: "250517_214501",
expectedPattern: "250517_214501",
expectedFound: true,
},
{
name: "6-digit date with prefix",
filename: "Site-170525_214501",
expectedPattern: "170525_214501",
expectedFound: true,
},
{
name: "No date pattern",
filename: "file_without_date",
expectedPattern: "",
expectedFound: false,
},
{
name: "Incomplete pattern (missing time)",
filename: "20250517",
expectedPattern: "",
expectedFound: false,
},
{
name: "Incomplete pattern (missing date)",
filename: "214501",
expectedPattern: "",
expectedFound: false,
},
{
name: "Wrong separator",
filename: "20250517-214501",
expectedPattern: "",
expectedFound: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pattern, found := ExtractDateTimePattern(tt.filename)
if found != tt.expectedFound {
t.Errorf("Expected found=%v, got found=%v", tt.expectedFound, found)
}
if pattern != tt.expectedPattern {
t.Errorf("Expected pattern '%s', got '%s'", tt.expectedPattern, pattern)
}
})
}
}
func containsString(s, substr string) bool {
return len(substr) == 0 || (len(s) > 0 && len(substr) > 0 &&
len(s) >= len(substr) && contains(s, substr))
}
func contains(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
func stringSlicesEqual(a, b []string) bool {
if len(a) != len(b) {
return false
}
if len(a) == 0 {
return true
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}