package utils
import (
"os"
"testing"
)
func TestDataFileParse(t *testing.T) {
content := `[
{"Operator": "Auto", "Reviewer": null, "Duration": 60.0},
[10.0, 20.0, 0, 0, [{"species": "Kiwi", "certainty": 70, "filter": "test-filter"}]],
[30.0, 40.0, 1000, 5000, [{"species": "Morepork", "certainty": 80, "filter": "M"}]]
]`
tmpfile, err := os.CreateTemp("", "test*.data")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpfile.Name())
if _, err := tmpfile.Write([]byte(content)); err != nil {
t.Fatal(err)
}
tmpfile.Close()
df, err := ParseDataFile(tmpfile.Name())
if err != nil {
t.Fatal(err)
}
if df.Meta.Operator != "Auto" {
t.Errorf("expected Operator=Auto, got %s", df.Meta.Operator)
}
if df.Meta.Duration != 60.0 {
t.Errorf("expected Duration=60.0, got %f", df.Meta.Duration)
}
if len(df.Segments) != 2 {
t.Errorf("expected 2 segments, got %d", len(df.Segments))
}
if df.Segments[0].StartTime != 10.0 {
t.Errorf("expected StartTime=10.0, got %f", df.Segments[0].StartTime)
}
if df.Segments[0].EndTime != 20.0 {
t.Errorf("expected EndTime=20.0, got %f", df.Segments[0].EndTime)
}
if len(df.Segments[0].Labels) != 1 {
t.Errorf("expected 1 label, got %d", len(df.Segments[0].Labels))
}
if df.Segments[0].Labels[0].Species != "Kiwi" {
t.Errorf("expected Species=Kiwi, got %s", df.Segments[0].Labels[0].Species)
}
if df.Segments[0].Labels[0].Certainty != 70 {
t.Errorf("expected Certainty=70, got %d", df.Segments[0].Labels[0].Certainty)
}
}
func TestDataFileWrite(t *testing.T) {
df := &DataFile{
FilePath: "",
Meta: &DataMeta{
Operator: "Test",
Reviewer: "David",
Duration: 120.0,
},
Segments: []*Segment{
{
StartTime: 5.0,
EndTime: 15.0,
FreqLow: 0,
FreqHigh: 0,
Labels: []*Label{
{Species: "Kiwi", Certainty: 100, Filter: "test"},
},
},
},
}
tmpfile, err := os.CreateTemp("", "test*.data")
if err != nil {
t.Fatal(err)
}
tmpfile.Close()
defer os.Remove(tmpfile.Name())
if err := df.Write(tmpfile.Name()); err != nil {
t.Fatal(err)
}
df2, err := ParseDataFile(tmpfile.Name())
if err != nil {
t.Fatal(err)
}
if df2.Meta.Reviewer != "David" {
t.Errorf("expected Reviewer=David, got %s", df2.Meta.Reviewer)
}
if len(df2.Segments) != 1 {
t.Errorf("expected 1 segment, got %d", len(df2.Segments))
}
if df2.Segments[0].Labels[0].Species != "Kiwi" {
t.Errorf("expected Species=Kiwi, got %s", df2.Segments[0].Labels[0].Species)
}
}
func TestHasFilterLabel(t *testing.T) {
seg := &Segment{
Labels: []*Label{
{Species: "Kiwi", Filter: "test-filter"},
{Species: "Morepork", Filter: "M"},
},
}
if !seg.HasFilterLabel("test-filter") {
t.Error("expected HasFilterLabel(test-filter)=true")
}
if !seg.HasFilterLabel("M") {
t.Error("expected HasFilterLabel(M)=true")
}
if seg.HasFilterLabel("other") {
t.Error("expected HasFilterLabel(other)=false")
}
if !seg.HasFilterLabel("") {
t.Error("expected HasFilterLabel('')=true (no filter)")
}
}
func TestGetFilterLabels(t *testing.T) {
seg := &Segment{
Labels: []*Label{
{Species: "Kiwi", Filter: "test-filter", Certainty: 70},
{Species: "Morepork", Filter: "M", Certainty: 80},
{Species: "Don't Know", Filter: "test-filter", Certainty: 0},
},
}
labels := seg.GetFilterLabels("test-filter")
if len(labels) != 2 {
t.Errorf("expected 2 labels, got %d", len(labels))
}
labels = seg.GetFilterLabels("")
if len(labels) != 3 {
t.Errorf("expected 3 labels (no filter), got %d", len(labels))
}
}
func TestLabelComment(t *testing.T) {
content := `[
{"Operator": "Test", "Duration": 60.0},
[10.0, 20.0, 0, 0, [{"species": "Kiwi", "certainty": 100, "filter": "M", "comment": "Good call"}]]
]`
tmpfile, err := os.CreateTemp("", "test*.data")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpfile.Name())
if _, err := tmpfile.Write([]byte(content)); err != nil {
t.Fatal(err)
}
tmpfile.Close()
df, err := ParseDataFile(tmpfile.Name())
if err != nil {
t.Fatal(err)
}
if df.Segments[0].Labels[0].Comment != "Good call" {
t.Errorf("expected Comment='Good call', got '%s'", df.Segments[0].Labels[0].Comment)
}
df.Segments[0].Labels[0].Comment = "Updated comment"
tmpfile2, err := os.CreateTemp("", "test2*.data")
if err != nil {
t.Fatal(err)
}
tmpfile2.Close()
defer os.Remove(tmpfile2.Name())
if err := df.Write(tmpfile2.Name()); err != nil {
t.Fatal(err)
}
df2, err := ParseDataFile(tmpfile2.Name())
if err != nil {
t.Fatal(err)
}
if df2.Segments[0].Labels[0].Comment != "Updated comment" {
t.Errorf("expected Comment='Updated comment', got '%s'", df2.Segments[0].Labels[0].Comment)
}
}
func TestSkraakHashRoundTrip(t *testing.T) {
df := &DataFile{
Meta: &DataMeta{
Operator: "Test",
Duration: 60.0,
Extra: map[string]any{
"skraak_hash": "abc123def456",
},
},
Segments: []*Segment{
{
StartTime: 10.0,
EndTime: 20.0,
Labels: []*Label{
{Species: "Kiwi", Certainty: 100, Filter: "M"},
},
},
},
}
tmpfile, err := os.CreateTemp("", "test*.data")
if err != nil {
t.Fatal(err)
}
tmpfile.Close()
defer os.Remove(tmpfile.Name())
if err := df.Write(tmpfile.Name()); err != nil {
t.Fatal(err)
}
df2, err := ParseDataFile(tmpfile.Name())
if err != nil {
t.Fatal(err)
}
if df2.Meta.Extra == nil {
t.Fatal("expected Extra to be non-nil")
}
hash, ok := df2.Meta.Extra["skraak_hash"].(string)
if !ok {
t.Fatal("expected skraak_hash to be string")
}
if hash != "abc123def456" {
t.Errorf("expected skraak_hash=abc123def456, got %s", hash)
}
}
func TestSkraakLabelIDRoundTrip(t *testing.T) {
df := &DataFile{
Meta: &DataMeta{
Operator: "Test",
Duration: 60.0,
},
Segments: []*Segment{
{
StartTime: 10.0,
EndTime: 20.0,
Labels: []*Label{
{
Species: "Kiwi",
Certainty: 100,
Filter: "M",
Extra: map[string]any{
"skraak_label_id": "label_abc123",
},
},
},
},
},
}
tmpfile, err := os.CreateTemp("", "test*.data")
if err != nil {
t.Fatal(err)
}
tmpfile.Close()
defer os.Remove(tmpfile.Name())
if err := df.Write(tmpfile.Name()); err != nil {
t.Fatal(err)
}
df2, err := ParseDataFile(tmpfile.Name())
if err != nil {
t.Fatal(err)
}
if len(df2.Segments) != 1 {
t.Fatalf("expected 1 segment, got %d", len(df2.Segments))
}
if len(df2.Segments[0].Labels) != 1 {
t.Fatalf("expected 1 label, got %d", len(df2.Segments[0].Labels))
}
label := df2.Segments[0].Labels[0]
if label.Extra == nil {
t.Fatal("expected label Extra to be non-nil")
}
labelID, ok := label.Extra["skraak_label_id"].(string)
if !ok {
t.Fatal("expected skraak_label_id to be string")
}
if labelID != "label_abc123" {
t.Errorf("expected skraak_label_id=label_abc123, got %s", labelID)
}
}
func TestSkraakFieldsBothPresent(t *testing.T) {
df := &DataFile{
Meta: &DataMeta{
Operator: "Test",
Duration: 60.0,
Extra: map[string]any{
"skraak_hash": "file_hash_xyz",
},
},
Segments: []*Segment{
{
StartTime: 10.0,
EndTime: 20.0,
Labels: []*Label{
{
Species: "Kiwi",
Certainty: 100,
Filter: "M",
Extra: map[string]any{
"skraak_label_id": "label_id_1",
},
},
{
Species: "Roroa",
Certainty: 90,
Filter: "M",
Extra: map[string]any{
"skraak_label_id": "label_id_2",
},
},
},
},
},
}
tmpfile, err := os.CreateTemp("", "test*.data")
if err != nil {
t.Fatal(err)
}
tmpfile.Close()
defer os.Remove(tmpfile.Name())
if err := df.Write(tmpfile.Name()); err != nil {
t.Fatal(err)
}
df2, err := ParseDataFile(tmpfile.Name())
if err != nil {
t.Fatal(err)
}
if df2.Meta.Extra["skraak_hash"] != "file_hash_xyz" {
t.Errorf("expected skraak_hash=file_hash_xyz, got %v", df2.Meta.Extra["skraak_hash"])
}
if len(df2.Segments[0].Labels) != 2 {
t.Fatalf("expected 2 labels, got %d", len(df2.Segments[0].Labels))
}
labelIDs := []string{"label_id_1", "label_id_2"}
for i, label := range df2.Segments[0].Labels {
if label.Extra["skraak_label_id"] != labelIDs[i] {
t.Errorf("label %d: expected skraak_label_id=%s, got %v", i, labelIDs[i], label.Extra["skraak_label_id"])
}
}
}
func TestSegmentMatchesFilters(t *testing.T) {
seg := &Segment{
Labels: []*Label{
{Species: "Kiwi", Filter: "model-1.0", CallType: "Duet", Certainty: 70},
{Species: "Morepork", Filter: "model-2.0", CallType: "", Certainty: 100},
},
}
tests := []struct {
name string
filter string
species string
callType string
certainty int
want bool
}{
{"no filters", "", "", "", -1, true},
{"filter only match", "model-1.0", "", "", -1, true},
{"filter only no match", "model-3.0", "", "", -1, false},
{"species only match", "", "Kiwi", "", -1, true},
{"species only no match", "", "Tomtit", "", -1, false},
{"calltype only match", "", "", "Duet", -1, true},
{"calltype only no match", "", "", "Male", -1, false},
{"certainty match", "", "", "", 70, true},
{"certainty no match", "", "", "", 80, false},
{"certainty 100 match", "", "", "", 100, true},
{"filter+species match", "model-1.0", "Kiwi", "", -1, true},
{"filter+species+calltype match", "model-1.0", "Kiwi", "Duet", -1, true},
{"filter+species+calltype+certainty match", "model-1.0", "Kiwi", "Duet", 70, true},
{"filter+species+calltype certainty miss", "model-1.0", "Kiwi", "Duet", 100, false},
{"filter match species miss", "model-1.0", "Morepork", "", -1, false},
{"all miss", "model-3.0", "Tomtit", "Male", -1, false},
{"CallTypeNone matches empty calltype", "model-2.0", "Morepork", CallTypeNone, -1, true},
{"CallTypeNone skips non-empty calltype", "model-1.0", "Kiwi", CallTypeNone, -1, false},
{"CallTypeNone + certainty match", "model-2.0", "Morepork", CallTypeNone, 100, true},
{"CallTypeNone + certainty miss", "model-2.0", "Morepork", CallTypeNone, 70, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := seg.SegmentMatchesFilters(tt.filter, tt.species, tt.callType, tt.certainty)
if got != tt.want {
t.Errorf("SegmentMatchesFilters(%q, %q, %q, %d) = %v, want %v",
tt.filter, tt.species, tt.callType, tt.certainty, got, tt.want)
}
})
}
}
func TestParseSpeciesCallType(t *testing.T) {
tests := []struct {
input string
species string
callType string
}{
{"", "", ""},
{"Kiwi", "Kiwi", ""},
{"Kiwi+Duet", "Kiwi", "Duet"},
{"GSK+Female", "GSK", "Female"},
{"Species+With+Multiple+Plus", "Species", "With+Multiple+Plus"},
{"Kiwi+_", "Kiwi", "_"},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
species, callType := ParseSpeciesCallType(tt.input)
if species != tt.species || callType != tt.callType {
t.Errorf("ParseSpeciesCallType(%q) = (%q, %q), want (%q, %q)",
tt.input, species, callType, tt.species, tt.callType)
}
})
}
}