added tests

quietlight
May 13, 2026, 2:53 AM
DHIPFBFPF4F7SLMMBVHO4TUFPLFYWY4KGOYZEHOQGUQNZELRSW3AC

Dependencies

Change contents

  • replacement in tools/import/bulk_file_import.go at line 361
    [3.62630][3.62630:62758]()
    if len(record) < 6 {
    return nil, fmt.Errorf("CSV row %d has insufficient columns (expected 6, got %d)", i+1, len(record))
    [3.62630]
    [3.62758]
    loc, err := parseBulkCSVRow(record)
    if err != nil {
    return nil, fmt.Errorf("row %d: %w", i+1, err)
  • edit in tools/import/bulk_file_import.go at line 365
    [3.62762]
    [3.62762]
    locations = append(locations, loc)
    }
  • replacement in tools/import/bulk_file_import.go at line 368
    [3.62763][3.62763:63231]()
    // Validate required string fields are non-empty
    locationName := strings.TrimSpace(record[0])
    if locationName == "" {
    return nil, fmt.Errorf("empty location_name in row %d", i+1)
    }
    directoryPath := strings.TrimSpace(record[2])
    if directoryPath == "" {
    return nil, fmt.Errorf("empty directory_path in row %d", i+1)
    }
    dateRange := strings.TrimSpace(record[3])
    if dateRange == "" {
    return nil, fmt.Errorf("empty date_range in row %d", i+1)
    }
    [3.62763]
    [3.63231]
    return locations, nil
    }
  • replacement in tools/import/bulk_file_import.go at line 371
    [3.63232][3.63232:63443]()
    // Validate location_id format
    locationID := record[1]
    if err := utils.ValidateShortID(locationID, "location_id"); err != nil {
    return nil, fmt.Errorf("invalid location_id in row %d: %v", i+1, err)
    }
    [3.63232]
    [3.63443]
    // parseBulkCSVRow parses a single CSV record into a bulkLocationData.
    // Columns: location_name, location_id, directory_path, date_range, sample_rate, file_count.
    // Returns an error if columns are missing, fields are empty, or numeric fields are invalid.
    func parseBulkCSVRow(record []string) (bulkLocationData, error) {
    if len(record) < 6 {
    return bulkLocationData{}, fmt.Errorf("insufficient columns (expected 6, got %d)", len(record))
    }
  • replacement in tools/import/bulk_file_import.go at line 379
    [3.63444][3.63444:63584]()
    sampleRate, err := strconv.Atoi(record[4])
    if err != nil {
    return nil, fmt.Errorf("invalid sample_rate in row %d: %v", i+1, err)
    }
    [3.63444]
    [3.63584]
    locationName := strings.TrimSpace(record[0])
    if locationName == "" {
    return bulkLocationData{}, fmt.Errorf("empty location_name")
    }
    directoryPath := strings.TrimSpace(record[2])
    if directoryPath == "" {
    return bulkLocationData{}, fmt.Errorf("empty directory_path")
    }
    dateRange := strings.TrimSpace(record[3])
    if dateRange == "" {
    return bulkLocationData{}, fmt.Errorf("empty date_range")
    }
  • replacement in tools/import/bulk_file_import.go at line 392
    [3.63585][3.63585:63774]()
    // Validate sample rate is in reasonable range
    if err := utils.ValidateSampleRate(sampleRate); err != nil {
    return nil, fmt.Errorf("invalid sample_rate in row %d: %v", i+1, err)
    }
    [3.63585]
    [3.63774]
    locationID := record[1]
    if err := utils.ValidateShortID(locationID, "location_id"); err != nil {
    return bulkLocationData{}, fmt.Errorf("invalid location_id: %v", err)
    }
  • replacement in tools/import/bulk_file_import.go at line 397
    [3.63775][3.63775:63913]()
    fileCount, err := strconv.Atoi(record[5])
    if err != nil {
    return nil, fmt.Errorf("invalid file_count in row %d: %v", i+1, err)
    }
    [3.63775]
    [3.63913]
    sampleRate, err := strconv.Atoi(record[4])
    if err != nil {
    return bulkLocationData{}, fmt.Errorf("invalid sample_rate: %v", err)
    }
    if err := utils.ValidateSampleRate(sampleRate); err != nil {
    return bulkLocationData{}, fmt.Errorf("invalid sample_rate: %v", err)
    }
  • replacement in tools/import/bulk_file_import.go at line 405
    [3.63914][3.63914:64152]()
    locations = append(locations, bulkLocationData{
    LocationName: locationName,
    LocationID: locationID,
    DirectoryPath: directoryPath,
    DateRange: dateRange,
    SampleRate: sampleRate,
    FileCount: fileCount,
    })
    [3.63914]
    [3.64152]
    fileCount, err := strconv.Atoi(record[5])
    if err != nil {
    return bulkLocationData{}, fmt.Errorf("invalid file_count: %v", err)
  • replacement in tools/import/bulk_file_import.go at line 410
    [3.64156][3.64156:64179]()
    return locations, nil
    [3.64156]
    [3.64179]
    return bulkLocationData{
    LocationName: locationName,
    LocationID: locationID,
    DirectoryPath: directoryPath,
    DateRange: dateRange,
    SampleRate: sampleRate,
    FileCount: fileCount,
    }, nil
  • file addition: bulk_csv_test.go (----------)
    [3.1]
    package imp
    import (
    "strings"
    "testing"
    )
    const validLocID = "IBv_KxDGsNQs"
    func validRow() []string {
    return []string{"Site A", validLocID, "/data/site_a", "2024-01-01_2024-01-31", "48000", "1200"}
    }
    func TestParseBulkCSVRow_Valid(t *testing.T) {
    got, err := parseBulkCSVRow(validRow())
    if err != nil {
    t.Fatalf("unexpected error: %v", err)
    }
    want := bulkLocationData{
    LocationName: "Site A",
    LocationID: validLocID,
    DirectoryPath: "/data/site_a",
    DateRange: "2024-01-01_2024-01-31",
    SampleRate: 48000,
    FileCount: 1200,
    }
    if got != want {
    t.Errorf("got %+v want %+v", got, want)
    }
    }
    func TestParseBulkCSVRow_TrimsWhitespace(t *testing.T) {
    row := []string{" Site A ", validLocID, " /data/site_a ", " 2024-01-01_2024-01-31 ", "48000", "1200"}
    got, err := parseBulkCSVRow(row)
    if err != nil {
    t.Fatalf("unexpected error: %v", err)
    }
    if got.LocationName != "Site A" || got.DirectoryPath != "/data/site_a" || got.DateRange != "2024-01-01_2024-01-31" {
    t.Errorf("whitespace not trimmed: %+v", got)
    }
    }
    func TestParseBulkCSVRow_Errors(t *testing.T) {
    mutate := func(idx int, val string) []string {
    r := validRow()
    r[idx] = val
    return r
    }
    tests := []struct {
    name string
    row []string
    errSubstr string
    }{
    {"too few columns", []string{"a", "b", "c"}, "insufficient columns"},
    {"empty location_name", mutate(0, ""), "empty location_name"},
    {"empty location_name whitespace", mutate(0, " "), "empty location_name"},
    {"empty directory_path", mutate(2, ""), "empty directory_path"},
    {"empty date_range", mutate(3, ""), "empty date_range"},
    {"bad location_id (short)", mutate(1, "abc"), "invalid location_id"},
    {"bad location_id (bad chars)", mutate(1, "IBv KxDGsNQs"), "invalid location_id"},
    {"non-numeric sample_rate", mutate(4, "fast"), "invalid sample_rate"},
    {"out-of-range sample_rate", mutate(4, "10"), "invalid sample_rate"},
    {"non-numeric file_count", mutate(5, "many"), "invalid file_count"},
    }
    for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
    _, err := parseBulkCSVRow(tt.row)
    if err == nil {
    t.Fatalf("expected error containing %q, got nil", tt.errSubstr)
    }
    if !strings.Contains(err.Error(), tt.errSubstr) {
    t.Errorf("error %q does not contain %q", err.Error(), tt.errSubstr)
    }
    })
    }
    }
  • file addition: parallel_aggregate_test.go (----------)
    [3.67281]
    package calls
    import (
    "errors"
    "sync/atomic"
    "testing"
    )
    type fakeResult struct {
    path string
    calls []ClusteredCall
    written bool
    skipped bool
    err error
    }
    func (r fakeResult) filePath() string { return r.path }
    func (r fakeResult) getCalls() []ClusteredCall { return r.calls }
    func (r fakeResult) wasWritten() bool { return r.written }
    func (r fakeResult) wasSkipped() bool { return r.skipped }
    func (r fakeResult) getError() error { return r.err }
    func sendAll(results []parallelResult) <-chan parallelResult {
    ch := make(chan parallelResult, len(results))
    for _, r := range results {
    ch <- r
    }
    close(ch)
    return ch
    }
    func TestAggregateResults_CountsAndSpecies(t *testing.T) {
    results := []parallelResult{
    fakeResult{path: "a.wav", written: true, calls: []ClusteredCall{
    {File: "a.wav", EbirdCode: "tomtit1", StartTime: 1},
    {File: "a.wav", EbirdCode: "tomtit1", StartTime: 2},
    }},
    fakeResult{path: "b.wav", skipped: true},
    fakeResult{path: "c.wav", written: true, calls: []ClusteredCall{
    {File: "c.wav", EbirdCode: "bellbird1", StartTime: 0.5},
    }},
    }
    var processed atomic.Int32
    var progressCalls int
    stats := aggregateResults(sendAll(results), len(results), &processed, false,
    func(cur, total int, msg string) { progressCalls++ })
    if stats.filesProcessed != 3 {
    t.Errorf("filesProcessed: got %d want 3", stats.filesProcessed)
    }
    if stats.dataFilesWritten != 2 {
    t.Errorf("written: got %d want 2", stats.dataFilesWritten)
    }
    if stats.dataFilesSkipped != 1 {
    t.Errorf("skipped: got %d want 1", stats.dataFilesSkipped)
    }
    if len(stats.calls) != 3 {
    t.Errorf("calls: got %d want 3", len(stats.calls))
    }
    if stats.speciesCount["tomtit1"] != 2 || stats.speciesCount["bellbird1"] != 1 {
    t.Errorf("speciesCount: got %v", stats.speciesCount)
    }
    if stats.firstErr != nil {
    t.Errorf("firstErr: got %v want nil", stats.firstErr)
    }
    if progressCalls != 3 {
    t.Errorf("progressCalls: got %d want 3", progressCalls)
    }
    if stats.filesDeleted != 0 {
    t.Errorf("filesDeleted: got %d want 0 (deleteFiles=false)", stats.filesDeleted)
    }
    }
    func TestAggregateResults_KeepsFirstError(t *testing.T) {
    err1 := errors.New("first")
    err2 := errors.New("second")
    results := []parallelResult{
    fakeResult{path: "a", err: err1},
    fakeResult{path: "b", err: err2},
    fakeResult{path: "c"},
    }
    var processed atomic.Int32
    stats := aggregateResults(sendAll(results), 3, &processed, false, nil)
    if stats.firstErr != err1 {
    t.Errorf("firstErr: got %v want %v", stats.firstErr, err1)
    }
    }
    func TestAggregateResults_NilProgressHandler(t *testing.T) {
    var processed atomic.Int32
    stats := aggregateResults(sendAll([]parallelResult{fakeResult{path: "a"}}), 1, &processed, false, nil)
    if stats.filesProcessed != 1 {
    t.Errorf("filesProcessed: got %d want 1", stats.filesProcessed)
    }
    }
    func TestSortCallsByFileAndTime(t *testing.T) {
    calls := []ClusteredCall{
    {File: "b.wav", StartTime: 1},
    {File: "a.wav", StartTime: 2},
    {File: "a.wav", StartTime: 1},
    {File: "b.wav", StartTime: 0.5},
    }
    sortCallsByFileAndTime(calls)
    want := []struct {
    file string
    start float64
    }{
    {"a.wav", 1},
    {"a.wav", 2},
    {"b.wav", 0.5},
    {"b.wav", 1},
    }
    for i, w := range want {
    if calls[i].File != w.file || calls[i].StartTime != w.start {
    t.Errorf("calls[%d]: got %+v want %+v", i, calls[i], w)
    }
    }
    }
  • file addition: calls_summarise_test.go (----------)
    [3.67281]
    package calls
    import (
    "reflect"
    "testing"
    "skraak/utils"
    )
    func TestFilterLabels(t *testing.T) {
    a := &utils.Label{Filter: "kiwi.txt", Species: "Kiwi"}
    b := &utils.Label{Filter: "tomtit.txt", Species: "Tomtit"}
    c := &utils.Label{Filter: "kiwi.txt", Species: "Kiwi2"}
    labels := []*utils.Label{a, b, c}
    tests := []struct {
    name string
    filter string
    want []*utils.Label
    }{
    {"no filter returns all", "", labels},
    {"matching filter returns subset", "kiwi.txt", []*utils.Label{a, c}},
    {"non-matching filter returns nil", "missing.txt", nil},
    }
    for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
    got := filterLabels(labels, tt.filter)
    if !reflect.DeepEqual(got, tt.want) {
    t.Errorf("got %v want %v", got, tt.want)
    }
    })
    }
    }
    func TestBuildLabelSummaries_OmitsEmptyFields(t *testing.T) {
    labels := []*utils.Label{
    {Filter: "f", Certainty: 80, Species: "Sp"},
    {Filter: "f", Certainty: 100, Species: "Sp", CallType: "song", Comment: "hi", Bookmark: true},
    }
    got := buildLabelSummaries(labels)
    if len(got) != 2 {
    t.Fatalf("len=%d want 2", len(got))
    }
    if got[0].CallType != "" || got[0].Comment != "" || got[0].Bookmark {
    t.Errorf("empty fields leaked: %+v", got[0])
    }
    if got[1].CallType != "song" || got[1].Comment != "hi" || !got[1].Bookmark {
    t.Errorf("populated fields wrong: %+v", got[1])
    }
    }
    func TestUpdateReviewStatus(t *testing.T) {
    out := &CallsSummariseOutput{}
    labels := []*utils.Label{
    {Certainty: 100},
    {Certainty: 100, CallType: "song"},
    {Certainty: 0},
    {Certainty: 50, Comment: "hmm", Bookmark: true},
    }
    for _, l := range labels {
    updateReviewStatus(l, out)
    }
    want := ReviewStatus{
    Confirmed: 2,
    DontKnow: 1,
    Unreviewed: 1,
    WithCallType: 1,
    WithComments: 1,
    Bookmarked: 1,
    }
    if out.ReviewStatus != want {
    t.Errorf("got %+v want %+v", out.ReviewStatus, want)
    }
    }
    func TestUpdateFilterStats_AccumulatesAcrossLabels(t *testing.T) {
    out := &CallsSummariseOutput{Filters: map[string]FilterStats{}}
    labels := []*utils.Label{
    {Filter: "kiwi.txt", Species: "Kiwi"},
    {Filter: "kiwi.txt", Species: "Kiwi", CallType: "brrr"},
    {Filter: "kiwi.txt", Species: "Kiwi", CallType: "brrr"},
    {Filter: "kiwi.txt", Species: "Kiwi2"},
    {Filter: "tomtit.txt", Species: "Tomtit", CallType: "song"},
    }
    for _, l := range labels {
    updateFilterStats(l, out)
    }
    kiwi := out.Filters["kiwi.txt"]
    if kiwi.Segments != 4 {
    t.Errorf("kiwi.Segments=%d want 4", kiwi.Segments)
    }
    if kiwi.Species["Kiwi"] != 3 || kiwi.Species["Kiwi2"] != 1 {
    t.Errorf("kiwi.Species=%v", kiwi.Species)
    }
    if kiwi.Calltypes["Kiwi"]["brrr"] != 2 {
    t.Errorf("kiwi calltype brrr=%d want 2", kiwi.Calltypes["Kiwi"]["brrr"])
    }
    tomtit := out.Filters["tomtit.txt"]
    if tomtit.Segments != 1 || tomtit.Calltypes["Tomtit"]["song"] != 1 {
    t.Errorf("tomtit=%+v", tomtit)
    }
    }
    func TestTrackMeta(t *testing.T) {
    ops, revs := map[string]bool{}, map[string]bool{}
    trackMeta(nil, ops, revs)
    if len(ops) != 0 || len(revs) != 0 {
    t.Errorf("nil meta should be no-op")
    }
    trackMeta(&utils.DataMeta{Operator: "alice", Reviewer: ""}, ops, revs)
    trackMeta(&utils.DataMeta{Operator: "", Reviewer: "bob"}, ops, revs)
    trackMeta(&utils.DataMeta{Operator: "alice", Reviewer: "bob"}, ops, revs)
    if !ops["alice"] || len(ops) != 1 {
    t.Errorf("operators=%v want only alice", ops)
    }
    if !revs["bob"] || len(revs) != 1 {
    t.Errorf("reviewers=%v want only bob", revs)
    }
    }
    func TestExtractRelativePath(t *testing.T) {
    tests := []struct {
    dataPath string
    want string
    }{
    {"/folder/tx51_LISTENING_20260221_203004.WAV.data", "tx51_LISTENING_20260221_203004.WAV"},
    {"foo.wav.data", "foo.wav"},
    {"/a/b/c/file.WAV.data", "file.WAV"},
    {"noslash.data", "noslash"},
    }
    for _, tt := range tests {
    t.Run(tt.dataPath, func(t *testing.T) {
    got := extractRelativePath("", tt.dataPath)
    if got != tt.want {
    t.Errorf("got %q want %q", got, tt.want)
    }
    })
    }
    }
    func TestFinaliseSummary_SortsAndClearsEmptyCalltypes(t *testing.T) {
    out := &CallsSummariseOutput{
    Filters: map[string]FilterStats{
    "f1": {Segments: 1, Species: map[string]int{"S": 1}, Calltypes: map[string]map[string]int{}},
    "f2": {Segments: 1, Species: map[string]int{"S": 1}, Calltypes: map[string]map[string]int{"S": {"c": 1}}},
    },
    Segments: []SegmentSummary{
    {File: "b.wav", StartTime: 1},
    {File: "a.wav", StartTime: 5},
    {File: "a.wav", StartTime: 2},
    },
    }
    finaliseSummary(out, map[string]bool{"zoe": true, "alice": true}, map[string]bool{"bob": true}, false)
    if out.Filters["f1"].Calltypes != nil {
    t.Errorf("empty calltypes not cleared: %v", out.Filters["f1"].Calltypes)
    }
    if out.Filters["f2"].Calltypes == nil {
    t.Errorf("populated calltypes cleared in error")
    }
    wantOps := []string{"alice", "zoe"}
    if !reflect.DeepEqual(out.Operators, wantOps) {
    t.Errorf("operators=%v want %v", out.Operators, wantOps)
    }
    wantOrder := []struct {
    file string
    start float64
    }{{"a.wav", 2}, {"a.wav", 5}, {"b.wav", 1}}
    for i, w := range wantOrder {
    if out.Segments[i].File != w.file || out.Segments[i].StartTime != w.start {
    t.Errorf("segment[%d]=%+v want file=%s start=%v", i, out.Segments[i], w.file, w.start)
    }
    }
    }
  • file addition: avianz_types_test.go (----------)
    [3.67281]
    package calls
    import (
    "encoding/json"
    "testing"
    )
    func TestAviaNZMetaJSONRoundTrip(t *testing.T) {
    reviewer := "alice"
    tests := []struct {
    name string
    in AviaNZMeta
    }{
    {"no reviewer", AviaNZMeta{Operator: "bob", Duration: 60.5}},
    {"with reviewer", AviaNZMeta{Operator: "bob", Reviewer: &reviewer, Duration: 0}},
    }
    for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
    b, err := json.Marshal(tt.in)
    if err != nil {
    t.Fatalf("marshal: %v", err)
    }
    var out AviaNZMeta
    if err := json.Unmarshal(b, &out); err != nil {
    t.Fatalf("unmarshal: %v", err)
    }
    if out.Operator != tt.in.Operator || out.Duration != tt.in.Duration {
    t.Errorf("operator/duration mismatch: got %+v want %+v", out, tt.in)
    }
    if (out.Reviewer == nil) != (tt.in.Reviewer == nil) {
    t.Errorf("reviewer nil mismatch: got %v want %v", out.Reviewer, tt.in.Reviewer)
    }
    if out.Reviewer != nil && tt.in.Reviewer != nil && *out.Reviewer != *tt.in.Reviewer {
    t.Errorf("reviewer mismatch: got %q want %q", *out.Reviewer, *tt.in.Reviewer)
    }
    })
    }
    }
    func TestAviaNZMetaOmitsNilReviewer(t *testing.T) {
    b, err := json.Marshal(AviaNZMeta{Operator: "bob", Duration: 1})
    if err != nil {
    t.Fatalf("marshal: %v", err)
    }
    if got := string(b); got != `{"Operator":"bob","Duration":1}` {
    t.Errorf("unexpected JSON: %s", got)
    }
    }
    func TestAviaNZLabelJSONRoundTrip(t *testing.T) {
    in := AviaNZLabel{Species: "Tomtit", Certainty: 100, Filter: "kiwi.txt"}
    b, err := json.Marshal(in)
    if err != nil {
    t.Fatalf("marshal: %v", err)
    }
    var out AviaNZLabel
    if err := json.Unmarshal(b, &out); err != nil {
    t.Fatalf("unmarshal: %v", err)
    }
    if out != in {
    t.Errorf("got %+v want %+v", out, in)
    }
    }
    func TestAviaNZSegmentJSONRoundTrip(t *testing.T) {
    raw := `[1.5,2.5,500,8000,[{"species":"Bellbird","certainty":80,"filter":"f.txt"}]]`
    var seg AviaNZSegment
    if err := json.Unmarshal([]byte(raw), &seg); err != nil {
    t.Fatalf("unmarshal: %v", err)
    }
    start, ok := seg[0].(float64)
    if !ok || start != 1.5 {
    t.Errorf("start: got %v (%T), want 1.5", seg[0], seg[0])
    }
    end, ok := seg[1].(float64)
    if !ok || end != 2.5 {
    t.Errorf("end: got %v (%T), want 2.5", seg[1], seg[1])
    }
    labels, ok := seg[4].([]any)
    if !ok || len(labels) != 1 {
    t.Fatalf("labels: got %v (%T)", seg[4], seg[4])
    }
    b, err := json.Marshal(seg)
    if err != nil {
    t.Fatalf("marshal: %v", err)
    }
    var seg2 AviaNZSegment
    if err := json.Unmarshal(b, &seg2); err != nil {
    t.Fatalf("re-unmarshal: %v", err)
    }
    }
  • file addition: common_test.go (----------)
    [5.1037540]
    package cmd
    import (
    "flag"
    "io"
    "strings"
    "testing"
    )
    func silentFlagSet() *flag.FlagSet {
    fs := flag.NewFlagSet("test", flag.ContinueOnError)
    fs.SetOutput(io.Discard)
    fs.Usage = func() {}
    return fs
    }
    func TestCheckFlags(t *testing.T) {
    tests := []struct {
    name string
    pairs []string
    wantErr bool
    wantMissing []string
    }{
    {"all present", []string{"--db", "x.duckdb", "--id", "abc"}, false, nil},
    {"empty value", []string{"--db", "", "--id", "abc"}, true, []string{"--db"}},
    {"multiple missing", []string{"--db", "", "--id", ""}, true, []string{"--db", "--id"}},
    {"no pairs", []string{}, false, nil},
    {"single missing", []string{"--name", ""}, true, []string{"--name"}},
    }
    for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
    err := checkFlags(silentFlagSet(), tt.pairs...)
    if (err != nil) != tt.wantErr {
    t.Fatalf("err=%v, wantErr=%v", err, tt.wantErr)
    }
    if tt.wantErr {
    for _, name := range tt.wantMissing {
    if !strings.Contains(err.Error(), name) {
    t.Errorf("err %q missing flag name %q", err.Error(), name)
    }
    }
    }
    })
    }
    }
    func TestCheckNonZeroFlags(t *testing.T) {
    type pair = struct {
    Name string
    Value int
    }
    tests := []struct {
    name string
    pairs []pair
    wantErr bool
    wantMissing []string
    }{
    {"all non-zero", []pair{{"--n", 5}, {"--m", 1}}, false, nil},
    {"zero value", []pair{{"--n", 0}, {"--m", 1}}, true, []string{"--n"}},
    {"multiple zero", []pair{{"--n", 0}, {"--m", 0}}, true, []string{"--n", "--m"}},
    {"empty pairs", nil, false, nil},
    {"negative is allowed", []pair{{"--n", -1}}, false, nil},
    }
    for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
    err := checkNonZeroFlags(silentFlagSet(), tt.pairs...)
    if (err != nil) != tt.wantErr {
    t.Fatalf("err=%v, wantErr=%v", err, tt.wantErr)
    }
    if tt.wantErr {
    for _, name := range tt.wantMissing {
    if !strings.Contains(err.Error(), name) {
    t.Errorf("err %q missing flag name %q", err.Error(), name)
    }
    }
    }
    })
    }
    }
  • edit in CHANGELOG.md at line 4
    [5.1198010]
    [2.783]
    ## [2026-05-13] Targeted unit tests on pure-logic seams
    Filled zero/low-coverage gaps without duplicating what `shell_scripts/` already integration-tests. Philosophy: extract pure logic, table-test it, leave DB/filesystem paths to shell scripts.
    - `cmd/common_test.go`: `checkFlags` and `checkNonZeroFlags` (cmd 0% → 0.9%).
    - `tools/calls/avianz_types_test.go`: JSON round-trip for `AviaNZMeta`/`AviaNZLabel`/`AviaNZSegment` to lock the wire format.
    - `tools/calls/parallel_aggregate_test.go`: fan-in aggregator tested with a `parallelResult` fake; covers species counting, written/skipped counting, first-error retention, nil progress handler, sort.
    - `tools/calls/calls_summarise_test.go`: pure helpers (`filterLabels`, `buildLabelSummaries`, `updateFilterStats`, `updateReviewStatus`, `trackMeta`, `extractRelativePath`, `finaliseSummary`). tools/calls 54% → 58%.
    - `tools/import/bulk_file_import.go`: extracted `parseBulkCSVRow` from the inline `bulkReadCSV` loop. `tools/import/bulk_csv_test.go` table-tests valid rows, whitespace handling, and every error branch. tools/import 0.8% → 3.7%.
  • edit in CHANGELOG.md at line 15
    [2.784]
    [2.784]
    Intentionally not touched: `db/validation.go` (DB-bound — covered by `test_db_state.sh` / `test_write_tools.sh`), most `cmd/*.go` (CLI dispatchers — covered by shell scripts), `tui/classify.go` (Bubble Tea event loop), `tools/import/import_segments.go` DB paths (covered by `test_import.sh`).