Fork channel

Create a new channel as a copy of main.

Rename channel

Rename main to:

Delete channel

Delete main? This cannot be undone.

import_segments_validation_test.go
package imp

import (
	"strings"
	"testing"

	"skraak/datafile"
	"skraak/db"
)

// --- validateSegmentHierarchy ---

func TestValidateSegmentHierarchy(t *testing.T) {
	database := setupImportTestDB(t)
	defer database.Close()

	t.Run("valid hierarchy", func(t *testing.T) {
		err := validateSegmentHierarchy(database, "dstest000001", "loctest00001", "cltest000001")
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
	})

	t.Run("unstructured dataset rejected", func(t *testing.T) {
		err := validateSegmentHierarchy(database, "dstest000002", "loctest00001", "cltest000001")
		if err == nil || !strings.Contains(err.Error(), "structured") {
			t.Errorf("expected structured-only error, got: %v", err)
		}
	})

	t.Run("inactive location rejected", func(t *testing.T) {
		err := validateSegmentHierarchy(database, "dstest000001", "loctest00002", "cltest000001")
		if err == nil {
			t.Error("expected error for inactive location")
		}
	})

	t.Run("inactive cluster rejected", func(t *testing.T) {
		err := validateSegmentHierarchy(database, "dstest000001", "loctest00001", "cltest000002")
		if err == nil {
			t.Error("expected error for inactive cluster")
		}
	})
}

// --- validateFiltersExist ---

func TestValidateFiltersExist(t *testing.T) {
	database := setupImportTestDB(t)
	defer database.Close()

	t.Run("all filters exist", func(t *testing.T) {
		filterMap, err := validateFiltersExist(database, map[string]bool{"kiwi.txt": true, "test.txt": true})
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(filterMap) != 2 {
			t.Errorf("expected 2 filters, got %d", len(filterMap))
		}
		if filterMap["kiwi.txt"] != "fitest000001" {
			t.Errorf("kiwi.txt ID = %q, want fitest000001", filterMap["kiwi.txt"])
		}
	})

	t.Run("missing filter reported", func(t *testing.T) {
		_, err := validateFiltersExist(database, map[string]bool{"phantom.txt": true})
		if err == nil || !strings.Contains(err.Error(), "not found") {
			t.Errorf("expected not-found error, got: %v", err)
		}
	})

	t.Run("empty filter set returns empty map", func(t *testing.T) {
		filterMap, err := validateFiltersExist(database, map[string]bool{})
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(filterMap) != 0 {
			t.Errorf("expected empty map, got %d entries", len(filterMap))
		}
	})
}

// --- loadSpeciesCalltypeIDs ---

func TestLoadSpeciesCalltypeIDs(t *testing.T) {
	database := setupImportTestDB(t)
	defer database.Close()

	mapping := MappingFile{
		"K-M": {Species: "Kiwi"},
		"GSK": {Species: "Roroa", Calltypes: map[string]string{"brrr": "brrr"}},
	}

	t.Run("loads species IDs", func(t *testing.T) {
		speciesMap, _, err := loadSpeciesCalltypeIDs(
			database, mapping,
			map[string]bool{"K-M": true, "GSK": true},
			map[string]map[string]bool{"K-M": {"song": true}, "GSK": {"brrr": true}},
		)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if speciesMap["Kiwi"] != "sptest000001" {
			t.Errorf("Kiwi ID = %q, want sptest000001", speciesMap["Kiwi"])
		}
		if speciesMap["Roroa"] != "sptest000002" {
			t.Errorf("Roroa ID = %q, want sptest000002", speciesMap["Roroa"])
		}
	})

	t.Run("loads calltype IDs", func(t *testing.T) {
		_, calltypeMap, err := loadSpeciesCalltypeIDs(
			database, mapping,
			map[string]bool{"K-M": true},
			map[string]map[string]bool{"K-M": {"song": true, "duet": true}},
		)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if calltypeMap["Kiwi"]["song"] != "cttest000001" {
			t.Errorf("Kiwi/song ID = %q, want cttest000001", calltypeMap["Kiwi"]["song"])
		}
		if calltypeMap["Kiwi"]["duet"] != "cttest000002" {
			t.Errorf("Kiwi/duet ID = %q, want cttest000002", calltypeMap["Kiwi"]["duet"])
		}
	})

	t.Run("missing calltype returns empty ID", func(t *testing.T) {
		_, calltypeMap, err := loadSpeciesCalltypeIDs(
			database, mapping,
			map[string]bool{"K-M": true},
			map[string]map[string]bool{"K-M": {"phantom": true}},
		)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if calltypeMap["Kiwi"]["phantom"] != "" {
			t.Errorf("missing calltype should have empty ID, got %q", calltypeMap["Kiwi"]["phantom"])
		}
	})
}

// --- validateAndMapFiles ---

func TestValidateAndMapFiles_NoScannedFiles(t *testing.T) {
	database := setupImportTestDB(t)
	defer database.Close()

	fileMap, errors := validateAndMapFiles(database, nil, "cltest000001", "dstest000001")
	if len(fileMap) != 0 {
		t.Errorf("expected empty map, got %d entries", len(fileMap))
	}
	if len(errors) != 0 {
		t.Errorf("expected no errors, got %d", len(errors))
	}
}

func TestValidateAndMapFiles_FileFoundByHash(t *testing.T) {
	database := setupImportTestDB(t)
	defer database.Close()

	// Create a temp WAV file so hash can be computed
	tmpDir := t.TempDir()
	wavPath := tmpDir + "/test.wav"
	hash := createTestWAV(t, wavPath)

	// Insert a file with the actual hash
	db.InsertTestFileForCluster(t, database, "fitest000004", "cltest000001", "loctest00001", "test.wav", hash, 60.0)

	// Link file to dataset
	mustExec(t, database, "INSERT INTO file_dataset (file_id, dataset_id, created_at, last_modified) VALUES (?, ?, now(), now())", "fitest000004", "dstest000001")

	scanned := []scannedDataFile{{
		DataPath: tmpDir + "/test.data",
		WavPath:  wavPath,
		Duration: 60.0,
		Segments: []*datafile.Segment{{StartTime: 1.0, EndTime: 2.0, Labels: []*datafile.Label{}}},
	}}

	fileMap, errors := validateAndMapFiles(database, scanned, "cltest000001", "dstest000001")

	// Should find the file by hash
	if len(fileMap) != 1 {
		t.Errorf("expected 1 file mapped, got %d; errors: %v", len(fileMap), errors)
	}
}

func TestValidateAndMapFiles_HashNotFound(t *testing.T) {
	database := setupImportTestDB(t)
	defer database.Close()

	// No file in DB — any hash lookup fails
	tmpDir := t.TempDir()
	wavPath := tmpDir + "/test.wav"
	createTestWAV(t, wavPath)

	scanned := []scannedDataFile{{
		DataPath: tmpDir + "/test.data",
		WavPath:  wavPath,
		Duration: 60.0,
		Segments: []*datafile.Segment{{StartTime: 1.0, EndTime: 2.0, Labels: []*datafile.Label{}}},
	}}

	fileMap, errors := validateAndMapFiles(database, scanned, "cltest000001", "dstest000001")

	if len(fileMap) != 0 {
		t.Errorf("expected 0 files mapped, got %d", len(fileMap))
	}
	if len(errors) == 0 {
		t.Error("expected hash-not-found error")
	}
}