package tools
import (
"os"
"path/filepath"
"testing"
"skraak/utils"
)
func TestCallsFromBirda_NewDataFile(t *testing.T) {
tmpDir := t.TempDir()
wavPath := filepath.Join(tmpDir, "test.WAV")
createMinimalWAV(t, wavPath, 16000, 60.0)
birdaPath := filepath.Join(tmpDir, "test.BirdNET.results.csv")
birdaContent := "\ufeffStart (s),End (s),Scientific name,Common name,Confidence,File\n0.0,3.0,Turdus migratorius,American Robin,0.85,/some/path/test.WAV\n"
if err := os.WriteFile(birdaPath, []byte(birdaContent), 0644); err != nil {
t.Fatal(err)
}
input := CallsFromBirdaInput{
File: birdaPath,
}
output, err := CallsFromBirda(input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if output.DataFilesWritten != 1 {
t.Errorf("expected 1 data file written, got %d", output.DataFilesWritten)
}
if output.Filter != "BirdNET" {
t.Errorf("expected filter 'BirdNET', got '%s'", output.Filter)
}
if output.TotalCalls != 1 {
t.Errorf("expected 1 call, got %d", output.TotalCalls)
}
dataPath := wavPath + ".data"
df, err := utils.ParseDataFile(dataPath)
if err != nil {
t.Fatalf("failed to parse .data file: %v", err)
}
if len(df.Segments) != 1 {
t.Errorf("expected 1 segment, got %d", len(df.Segments))
}
if df.Segments[0].Labels[0].Filter != "BirdNET" {
t.Errorf("expected filter 'BirdNET', got '%s'", df.Segments[0].Labels[0].Filter)
}
if df.Segments[0].Labels[0].Certainty != 85 {
t.Errorf("expected certainty 85, got %d", df.Segments[0].Labels[0].Certainty)
}
}
func TestCallsFromBirda_ExistingSameFilter(t *testing.T) {
tmpDir := t.TempDir()
wavPath := filepath.Join(tmpDir, "test.WAV")
createMinimalWAV(t, wavPath, 16000, 60.0)
dataPath := wavPath + ".data"
existingData := `[{"Operator": "Test", "Duration": 60.0}, [5.0, 10.0, 0, 16000, [{"species": "Existing Bird", "certainty": 90, "filter": "BirdNET"}]]]`
if err := os.WriteFile(dataPath, []byte(existingData), 0644); err != nil {
t.Fatal(err)
}
birdaPath := filepath.Join(tmpDir, "test.BirdNET.results.csv")
birdaContent := "\ufeffStart (s),End (s),Scientific name,Common name,Confidence,File\n0.0,3.0,New Bird,New Bird,0.85,test.WAV\n"
if err := os.WriteFile(birdaPath, []byte(birdaContent), 0644); err != nil {
t.Fatal(err)
}
input := CallsFromBirdaInput{File: birdaPath}
output, err := CallsFromBirda(input)
if err == nil {
t.Error("expected error for same filter, got nil")
}
if output.Error == nil {
t.Error("expected error message in output")
}
}
func TestCallsFromBirda_ExistingDifferentFilter(t *testing.T) {
tmpDir := t.TempDir()
wavPath := filepath.Join(tmpDir, "test.WAV")
createMinimalWAV(t, wavPath, 16000, 60.0)
dataPath := wavPath + ".data"
existingData := `[{"Operator": "Test", "Duration": 60.0}, [5.0, 10.0, 0, 16000, [{"species": "Kiwi", "certainty": 90, "filter": "Manual"}]]]`
if err := os.WriteFile(dataPath, []byte(existingData), 0644); err != nil {
t.Fatal(err)
}
birdaPath := filepath.Join(tmpDir, "test.BirdNET.results.csv")
birdaContent := "\ufeffStart (s),End (s),Scientific name,Common name,Confidence,File\n0.0,3.0,Robin,Robin,0.85,test.WAV\n"
if err := os.WriteFile(birdaPath, []byte(birdaContent), 0644); err != nil {
t.Fatal(err)
}
input := CallsFromBirdaInput{File: birdaPath}
output, err := CallsFromBirda(input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if output.DataFilesWritten != 1 {
t.Errorf("expected 1 data file written, got %d", output.DataFilesWritten)
}
df, err := utils.ParseDataFile(dataPath)
if err != nil {
t.Fatalf("failed to parse .data file: %v", err)
}
if len(df.Segments) != 2 {
t.Errorf("expected 2 segments after merge, got %d", len(df.Segments))
}
}
func TestCallsFromBirda_DeleteOption(t *testing.T) {
tmpDir := t.TempDir()
wavPath := filepath.Join(tmpDir, "test.WAV")
createMinimalWAV(t, wavPath, 16000, 60.0)
birdaPath := filepath.Join(tmpDir, "test.BirdNET.results.csv")
birdaContent := "\ufeffStart (s),End (s),Scientific name,Common name,Confidence,File\n0.0,3.0,Robin,Robin,0.85,test.WAV\n"
if err := os.WriteFile(birdaPath, []byte(birdaContent), 0644); err != nil {
t.Fatal(err)
}
input := CallsFromBirdaInput{File: birdaPath, Delete: true}
output, err := CallsFromBirda(input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if output.FilesDeleted != 1 {
t.Errorf("expected 1 file deleted, got %d", output.FilesDeleted)
}
if _, err := os.Stat(birdaPath); !os.IsNotExist(err) {
t.Error("expected BirdNET file to be deleted")
}
}
func TestCallsFromBirda_FolderMode(t *testing.T) {
tmpDir := t.TempDir()
for i := range 2 {
wavPath := filepath.Join(tmpDir, "test"+string(rune('0'+i))+".WAV")
createMinimalWAV(t, wavPath, 16000, 60.0)
birdaPath := filepath.Join(tmpDir, "test"+string(rune('0'+i))+".BirdNET.results.csv")
birdaContent := "\ufeffStart (s),End (s),Scientific name,Common name,Confidence,File\n0.0,3.0,Bird,Bird,0.85,test.WAV\n"
if err := os.WriteFile(birdaPath, []byte(birdaContent), 0644); err != nil {
t.Fatal(err)
}
}
input := CallsFromBirdaInput{Folder: tmpDir}
output, err := CallsFromBirda(input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if output.FilesProcessed != 2 {
t.Errorf("expected 2 files processed, got %d", output.FilesProcessed)
}
if output.DataFilesWritten != 2 {
t.Errorf("expected 2 data files written, got %d", output.DataFilesWritten)
}
}
func TestCallsFromRaven_NewDataFile(t *testing.T) {
tmpDir := t.TempDir()
wavPath := filepath.Join(tmpDir, "test.WAV")
createMinimalWAV(t, wavPath, 16000, 60.0)
ravenPath := filepath.Join(tmpDir, "test.Table.1.selections.txt")
ravenContent := "Selection\tView\tChannel\tBegin Time (s)\tEnd Time (s)\tLow Freq (Hz)\tHigh Freq (Hz)\tSpecies\n1\tSpectrogram 1\t1\t0.0\t5.0\t1000\t5000\tKiwi\n"
if err := os.WriteFile(ravenPath, []byte(ravenContent), 0644); err != nil {
t.Fatal(err)
}
input := CallsFromRavenInput{File: ravenPath}
output, err := CallsFromRaven(input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if output.DataFilesWritten != 1 {
t.Errorf("expected 1 data file written, got %d", output.DataFilesWritten)
}
if output.Filter != "Raven" {
t.Errorf("expected filter 'Raven', got '%s'", output.Filter)
}
dataPath := wavPath + ".data"
df, err := utils.ParseDataFile(dataPath)
if err != nil {
t.Fatalf("failed to parse .data file: %v", err)
}
if df.Segments[0].FreqLow != 1000 {
t.Errorf("expected freq_low 1000, got %f", df.Segments[0].FreqLow)
}
if df.Segments[0].FreqHigh != 5000 {
t.Errorf("expected freq_high 5000, got %f", df.Segments[0].FreqHigh)
}
}
func TestCallsFromRaven_ExistingSameFilter(t *testing.T) {
tmpDir := t.TempDir()
wavPath := filepath.Join(tmpDir, "test.WAV")
createMinimalWAV(t, wavPath, 16000, 60.0)
dataPath := wavPath + ".data"
existingData := `[{"Operator": "Test", "Duration": 60.0}, [5.0, 10.0, 0, 16000, [{"species": "Existing", "certainty": 90, "filter": "Raven"}]]]`
if err := os.WriteFile(dataPath, []byte(existingData), 0644); err != nil {
t.Fatal(err)
}
ravenPath := filepath.Join(tmpDir, "test.Table.1.selections.txt")
ravenContent := "Selection\tView\tChannel\tBegin Time (s)\tEnd Time (s)\tLow Freq (Hz)\tHigh Freq (Hz)\tSpecies\n1\tSpectrogram 1\t1\t0.0\t5.0\t1000\t5000\tNew\n"
if err := os.WriteFile(ravenPath, []byte(ravenContent), 0644); err != nil {
t.Fatal(err)
}
input := CallsFromRavenInput{File: ravenPath}
output, err := CallsFromRaven(input)
if err == nil {
t.Error("expected error for same filter, got nil")
}
if output.Error == nil {
t.Error("expected error message in output")
}
}
func TestCallsFromRaven_ExistingDifferentFilter(t *testing.T) {
tmpDir := t.TempDir()
wavPath := filepath.Join(tmpDir, "test.WAV")
createMinimalWAV(t, wavPath, 16000, 60.0)
dataPath := wavPath + ".data"
existingData := `[{"Operator": "Test", "Duration": 60.0}, [5.0, 10.0, 0, 16000, [{"species": "Kiwi", "certainty": 90, "filter": "BirdNET"}]]]`
if err := os.WriteFile(dataPath, []byte(existingData), 0644); err != nil {
t.Fatal(err)
}
ravenPath := filepath.Join(tmpDir, "test.Table.1.selections.txt")
ravenContent := "Selection\tView\tChannel\tBegin Time (s)\tEnd Time (s)\tLow Freq (Hz)\tHigh Freq (Hz)\tSpecies\n1\tSpectrogram 1\t1\t0.0\t5.0\t1000\t5000\tMorepork\n"
if err := os.WriteFile(ravenPath, []byte(ravenContent), 0644); err != nil {
t.Fatal(err)
}
input := CallsFromRavenInput{File: ravenPath}
output, err := CallsFromRaven(input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if output.DataFilesWritten != 1 {
t.Errorf("expected 1 data file written, got %d", output.DataFilesWritten)
}
df, err := utils.ParseDataFile(dataPath)
if err != nil {
t.Fatalf("failed to parse .data file: %v", err)
}
if len(df.Segments) != 2 {
t.Errorf("expected 2 segments after merge, got %d", len(df.Segments))
}
}
func TestCallsFromRaven_DeleteOption(t *testing.T) {
tmpDir := t.TempDir()
wavPath := filepath.Join(tmpDir, "test.WAV")
createMinimalWAV(t, wavPath, 16000, 60.0)
ravenPath := filepath.Join(tmpDir, "test.Table.1.selections.txt")
ravenContent := "Selection\tView\tChannel\tBegin Time (s)\tEnd Time (s)\tLow Freq (Hz)\tHigh Freq (Hz)\tSpecies\n1\tSpectrogram 1\t1\t0.0\t5.0\t1000\t5000\tKiwi\n"
if err := os.WriteFile(ravenPath, []byte(ravenContent), 0644); err != nil {
t.Fatal(err)
}
input := CallsFromRavenInput{File: ravenPath, Delete: true}
output, err := CallsFromRaven(input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if output.FilesDeleted != 1 {
t.Errorf("expected 1 file deleted, got %d", output.FilesDeleted)
}
if _, err := os.Stat(ravenPath); !os.IsNotExist(err) {
t.Error("expected Raven file to be deleted")
}
}
func TestCallsFromRaven_MultipleSelections(t *testing.T) {
tmpDir := t.TempDir()
wavPath := filepath.Join(tmpDir, "test.WAV")
createMinimalWAV(t, wavPath, 16000, 60.0)
ravenPath := filepath.Join(tmpDir, "test.Table.1.selections.txt")
ravenContent := "Selection\tView\tChannel\tBegin Time (s)\tEnd Time (s)\tLow Freq (Hz)\tHigh Freq (Hz)\tSpecies\n1\tSpectrogram 1\t1\t0.0\t5.0\t1000\t5000\tKiwi\n2\tSpectrogram 1\t1\t10.0\t15.0\t2000\t6000\tMorepork\n3\tSpectrogram 1\t1\t20.0\t25.0\t1500\t4500\tTui\n"
if err := os.WriteFile(ravenPath, []byte(ravenContent), 0644); err != nil {
t.Fatal(err)
}
input := CallsFromRavenInput{File: ravenPath}
output, err := CallsFromRaven(input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if output.TotalCalls != 3 {
t.Errorf("expected 3 calls, got %d", output.TotalCalls)
}
if output.SpeciesCount["Kiwi"] != 1 || output.SpeciesCount["Morepork"] != 1 || output.SpeciesCount["Tui"] != 1 {
t.Errorf("unexpected species count: %v", output.SpeciesCount)
}
}