package eval import ( "context" "database/sql" "testing" "time" _ "modernc.org/sqlite" ) func TestBatch_EmptyInputClosesChannel(t *testing.T) { b := &Batch{Concurrency: 3} ch := b.Run(context.Background(), nil) select { case _, ok := <-ch: if ok { t.Fatal("expected closed channel for empty input") } case <-time.After(100 * time.Millisecond): t.Fatal("channel did not close") } } func TestBatch_NilClientErrorsEveryJob(t *testing.T) { b := &Batch{Concurrency: 2} jobs := []JobContext{ {URL: "https://a", Company: "A", Title: "T"}, {URL: "https://b", Company: "B", Title: "T"}, } ch := b.Run(context.Background(), jobs) var starts, errors int for ev := range ch { switch ev.Kind { case EventStart: starts++ case EventError: errors++ case EventDone: t.Fatalf("unexpected EventDone with nil client") } } if starts != 2 || errors != 2 { t.Errorf("starts=%d errors=%d, want 2/2", starts, errors) } } func TestBatch_DemoteBelowMinScore(t *testing.T) { db := setupReportDB(t) defer db.Close() // Seed a job + application row (Evaluated, score=3.0). if _, err := db.Exec(`INSERT INTO jobs (url, company, title, source) VALUES (?, ?, ?, ?)`, "https://x/1", "Acme", "Engineer", "test"); err != nil { t.Fatal(err) } var jobID int64 if err := db.QueryRow(`SELECT id FROM jobs WHERE url = ?`, "https://x/1").Scan(&jobID); err != nil { t.Fatal(err) } res, err := db.Exec(`INSERT INTO applications (job_id, status, score) VALUES (?, 'Evaluated', 3.0)`, jobID) if err != nil { t.Fatal(err) } appID, _ := res.LastInsertId() b := &Batch{DB: db, MinScore: 4.0} if err := b.demoteBelowScore(context.Background(), appID); err != nil { t.Fatalf("demote: %v", err) } var status string if err := db.QueryRow(`SELECT status FROM applications WHERE id = ?`, appID).Scan(&status); err != nil { t.Fatal(err) } if status != "Discarded" { t.Errorf("status = %q, want Discarded", status) } // Now set the row to Applied and re-run demote — it must NOT regress. if _, err := db.Exec(`UPDATE applications SET status = 'Applied' WHERE id = ?`, appID); err != nil { t.Fatal(err) } if err := b.demoteBelowScore(context.Background(), appID); err != nil { t.Fatalf("demote#2: %v", err) } _ = db.QueryRow(`SELECT status FROM applications WHERE id = ?`, appID).Scan(&status) if status != "Applied" { t.Errorf("user-advanced status must be preserved, got %q", status) } } // silence unused-import warning when the only test using sql is compiled out. var _ = (*sql.DB)(nil) func TestBatch_ContextCanceledBeforeStart(t *testing.T) { b := &Batch{Concurrency: 1} jobs := []JobContext{{URL: "https://x", Company: "X", Title: "T"}} ctx, cancel := context.WithCancel(context.Background()) cancel() ch := b.Run(ctx, jobs) var errs int for ev := range ch { if ev.Kind == EventError { errs++ } } if errs != 1 { t.Errorf("expected 1 error from canceled batch, got %d", errs) } }