LEM/pkg/lem/db_test.go
Snider 3606ff994b fix: memory, error handling, and signal improvements across pkg/lem
- Stream parquet export rows instead of unbounded memory allocation
- Replace QueryGoldenSet/QueryExpansionPrompts with iter.Seq2 iterators
- Remove legacy runtime.GC() calls from distill (go-mlx handles cleanup)
- Replace log.Fatalf with error return in tier_score.go
- Add SIGINT/SIGTERM signal handling to agent and worker daemon loops
- Add error checks for unchecked db.conn.Exec in import.go and tier_score.go
- Update tests for iterator-based database methods

Co-Authored-By: Gemini <noreply@google.com>
Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-23 04:46:51 +00:00

292 lines
6.7 KiB
Go

package lem
import (
"os"
"path/filepath"
"testing"
)
func createTestDB(t *testing.T) *DB {
t.Helper()
dir := t.TempDir()
path := filepath.Join(dir, "test.duckdb")
db, err := OpenDBReadWrite(path)
if err != nil {
t.Fatalf("open test db: %v", err)
}
// Create golden_set table.
_, err = db.conn.Exec(`CREATE TABLE golden_set (
idx INTEGER, seed_id VARCHAR, domain VARCHAR, voice VARCHAR,
prompt VARCHAR, response VARCHAR, gen_time DOUBLE, char_count INTEGER
)`)
if err != nil {
t.Fatalf("create golden_set: %v", err)
}
// Create expansion_prompts table.
_, err = db.conn.Exec(`CREATE TABLE expansion_prompts (
idx BIGINT, seed_id VARCHAR, region VARCHAR, domain VARCHAR,
language VARCHAR, prompt VARCHAR, prompt_en VARCHAR, priority INTEGER, status VARCHAR
)`)
if err != nil {
t.Fatalf("create expansion_prompts: %v", err)
}
return db
}
func TestOpenDBReadOnly(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.duckdb")
// Create a DB first so the file exists.
db, err := OpenDBReadWrite(path)
if err != nil {
t.Fatalf("create db: %v", err)
}
db.Close()
// Now open read-only.
roDB, err := OpenDB(path)
if err != nil {
t.Fatalf("open read-only: %v", err)
}
defer roDB.Close()
if roDB.path != path {
t.Errorf("path = %q, want %q", roDB.path, path)
}
}
func TestOpenDBNotFound(t *testing.T) {
_, err := OpenDB("/nonexistent/path/to.duckdb")
if err == nil {
t.Fatal("expected error for nonexistent path")
}
}
func TestQueryGoldenSet(t *testing.T) {
db := createTestDB(t)
defer db.Close()
// Insert test data.
_, err := db.conn.Exec(`INSERT INTO golden_set VALUES
(0, 'seed1', 'Identity', 'junior', 'prompt one', 'response one with enough chars to pass', 10.5, 200),
(1, 'seed2', 'Ethics', 'senior', 'prompt two', 'short', 5.0, 5),
(2, 'seed3', 'Privacy', 'peer', 'prompt three', 'another good response with sufficient length', 8.2, 300)
`)
if err != nil {
t.Fatalf("insert: %v", err)
}
// Query with minChars=50 should return 2 (skip the short one).
var rows []GoldenSetRow
for r, err := range db.GoldenSet(50) {
if err != nil {
t.Fatalf("iter error: %v", err)
}
rows = append(rows, r)
}
if len(rows) != 2 {
t.Fatalf("got %d rows, want 2", len(rows))
}
if rows[0].SeedID != "seed1" {
t.Errorf("first row seed_id = %q, want seed1", rows[0].SeedID)
}
if rows[1].Domain != "Privacy" {
t.Errorf("second row domain = %q, want Privacy", rows[1].Domain)
}
}
func TestGoldenSetEmpty(t *testing.T) {
db := createTestDB(t)
defer db.Close()
count := 0
for _, err := range db.GoldenSet(0) {
if err != nil {
t.Fatalf("iter error: %v", err)
}
count++
}
if count != 0 {
t.Fatalf("got %d rows, want 0", count)
}
}
func TestCountGoldenSet(t *testing.T) {
db := createTestDB(t)
defer db.Close()
_, err := db.conn.Exec(`INSERT INTO golden_set VALUES
(0, 'seed1', 'Identity', 'junior', 'p1', 'r1', 10.5, 200),
(1, 'seed2', 'Ethics', 'senior', 'p2', 'r2', 5.0, 150)
`)
if err != nil {
t.Fatalf("insert: %v", err)
}
count, err := db.CountGoldenSet()
if err != nil {
t.Fatalf("count: %v", err)
}
if count != 2 {
t.Errorf("count = %d, want 2", count)
}
}
func TestExpansionPrompts(t *testing.T) {
db := createTestDB(t)
defer db.Close()
_, err := db.conn.Exec(`INSERT INTO expansion_prompts VALUES
(0, 'ep1', 'chinese', 'Identity', 'zh', 'prompt zh', 'prompt en', 1, 'pending'),
(1, 'ep2', 'russian', 'Ethics', 'ru', 'prompt ru', 'prompt en2', 2, 'pending'),
(2, 'ep3', 'english', 'Privacy', 'en', 'prompt en3', '', 1, 'completed')
`)
if err != nil {
t.Fatalf("insert: %v", err)
}
// Query pending only.
var rows []ExpansionPromptRow
for r, err := range db.ExpansionPrompts("pending", 0) {
if err != nil {
t.Fatalf("iter error: %v", err)
}
rows = append(rows, r)
}
if len(rows) != 2 {
t.Fatalf("got %d rows, want 2", len(rows))
}
// Should be ordered by priority, idx.
if rows[0].SeedID != "ep1" {
t.Errorf("first row = %q, want ep1", rows[0].SeedID)
}
// Query all.
var all []ExpansionPromptRow
for r, err := range db.ExpansionPrompts("", 0) {
if err != nil {
t.Fatalf("iter error: %v", err)
}
all = append(all, r)
}
if len(all) != 3 {
t.Fatalf("got %d rows, want 3", len(all))
}
// Query with limit.
var limited []ExpansionPromptRow
for r, err := range db.ExpansionPrompts("pending", 1) {
if err != nil {
t.Fatalf("iter error: %v", err)
}
limited = append(limited, r)
}
if len(limited) != 1 {
t.Fatalf("got %d rows, want 1", len(limited))
}
}
func TestCountExpansionPrompts(t *testing.T) {
db := createTestDB(t)
defer db.Close()
_, err := db.conn.Exec(`INSERT INTO expansion_prompts VALUES
(0, 'ep1', 'chinese', 'Identity', 'zh', 'p1', 'p1en', 1, 'pending'),
(1, 'ep2', 'russian', 'Ethics', 'ru', 'p2', 'p2en', 2, 'completed'),
(2, 'ep3', 'english', 'Privacy', 'en', 'p3', '', 1, 'pending')
`)
if err != nil {
t.Fatalf("insert: %v", err)
}
total, pending, err := db.CountExpansionPrompts()
if err != nil {
t.Fatalf("count: %v", err)
}
if total != 3 {
t.Errorf("total = %d, want 3", total)
}
if pending != 2 {
t.Errorf("pending = %d, want 2", pending)
}
}
func TestUpdateExpansionStatus(t *testing.T) {
db := createTestDB(t)
defer db.Close()
_, err := db.conn.Exec(`INSERT INTO expansion_prompts VALUES
(0, 'ep1', 'chinese', 'Identity', 'zh', 'p1', 'p1en', 1, 'pending')
`)
if err != nil {
t.Fatalf("insert: %v", err)
}
err = db.UpdateExpansionStatus(0, "completed")
if err != nil {
t.Fatalf("update: %v", err)
}
count := 0
for r, err := range db.ExpansionPrompts("completed", 0) {
if err != nil {
t.Fatalf("iter error: %v", err)
}
if r.Status != "completed" {
t.Errorf("status = %q, want completed", r.Status)
}
count++
}
if count != 1 {
t.Fatalf("got %d rows, want 1", count)
}
}
func TestTableCounts(t *testing.T) {
db := createTestDB(t)
defer db.Close()
_, err := db.conn.Exec(`INSERT INTO golden_set VALUES
(0, 's1', 'd1', 'v1', 'p1', 'r1', 1.0, 100)
`)
if err != nil {
t.Fatalf("insert golden: %v", err)
}
counts, err := db.TableCounts()
if err != nil {
t.Fatalf("table counts: %v", err)
}
if counts["golden_set"] != 1 {
t.Errorf("golden_set count = %d, want 1", counts["golden_set"])
}
if counts["expansion_prompts"] != 0 {
t.Errorf("expansion_prompts count = %d, want 0", counts["expansion_prompts"])
}
}
func TestOpenDBWithEnvDefault(t *testing.T) {
// Test that OpenDB uses the default path from LEM_DB env if available.
dir := t.TempDir()
path := filepath.Join(dir, "env-test.duckdb")
db, err := OpenDBReadWrite(path)
if err != nil {
t.Fatalf("create: %v", err)
}
db.Close()
os.Setenv("LEM_DB", path)
defer os.Unsetenv("LEM_DB")
db2, err := OpenDB(path)
if err != nil {
t.Fatalf("open via env: %v", err)
}
defer db2.Close()
}