package main import ( "database/sql" "path/filepath" "testing" ) // TestRecordServiceSQLite 验证记录 SQLite 存储:建表、插入、查询、去重、全量替换。 // 直接测试 openRecordDB 返回的 *sql.DB(绕过 SettingsService 的 loadSettings 依赖)。 func TestRecordServiceSQLite(t *testing.T) { dir := t.TempDir() dbPath := filepath.Join(dir, "records.db") // 未配置路径时应报错(openRecordDB 空路径) if _, err := openRecordDB(""); err == nil { t.Fatal("空数据库路径应返回错误") } // 打开(自动建表 + WAL) db, err := openRecordDB(dbPath) if err != nil { t.Fatalf("openRecordDB 失败: %v", err) } defer db.Close() // 插入 3 条 for _, r := range []struct{ n, i string; v float64 }{ {"张三", "一月", 12.5}, {"张三", "二月", 30}, {"李四", "一月", 8.2}, } { if _, err := db.Exec(`INSERT INTO records (name, item, value) VALUES (?,?,?)`, r.n, r.i, r.v); err != nil { t.Fatalf("插入失败: %v", err) } } // 按 id 升序读取 rows, err := db.Query(`SELECT name, item, value FROM records ORDER BY id ASC`) if err != nil { t.Fatalf("查询失败: %v", err) } var got []RecordItem for rows.Next() { var it RecordItem if err := rows.Scan(&it.Name, &it.Item, &it.Value); err != nil { t.Fatalf("扫描失败: %v", err) } got = append(got, it) } rows.Close() if len(got) != 3 { t.Fatalf("期望 3 条记录,实际 %d", len(got)) } if got[0].Name != "张三" || got[0].Item != "一月" || got[0].Value != 12.5 { t.Fatalf("首条记录不符: %+v", got[0]) } // 去重姓名(UTF-8 字节序:张三 < 李四) names := distinctTest(t, db, "name") if len(names) != 2 || names[0] != "张三" || names[1] != "李四" { t.Fatalf("去重姓名不符: %v", names) } // 去重名称(排序:一月、二月) items := distinctTest(t, db, "item") if len(items) != 2 || items[0] != "一月" || items[1] != "二月" { t.Fatalf("去重名称不符: %v", items) } // 非法列名应报错 if _, err := recordDistinctValues("evil"); err == nil { t.Fatal("非法列名应返回错误") } // 全量替换:清空后写 1 条 tx, err := db.Begin() if err != nil { t.Fatalf("开启事务失败: %v", err) } if _, err := tx.Exec(`DELETE FROM records`); err != nil { t.Fatalf("清空失败: %v", err) } stmt, err := tx.Prepare(`INSERT INTO records (name, item, value) VALUES (?,?,?)`) if err != nil { t.Fatalf("准备语句失败: %v", err) } if _, err := stmt.Exec("王五", "三月", 99); err != nil { t.Fatalf("替换插入失败: %v", err) } stmt.Close() if err := tx.Commit(); err != nil { t.Fatalf("提交失败: %v", err) } var cnt int if err := db.QueryRow(`SELECT COUNT(*) FROM records`).Scan(&cnt); err != nil || cnt != 1 { t.Fatalf("全量替换后应有 1 条,实际 %d (err=%v)", cnt, err) } } // distinctTest 对已打开 db 执行与 recordDistinctValues 相同的去重 SQL。 func distinctTest(t *testing.T, db *sql.DB, col string) []string { t.Helper() rows, err := db.Query(`SELECT DISTINCT ` + col + ` FROM records WHERE TRIM(` + col + `) <> '' ORDER BY ` + col + ` COLLATE NOCASE ASC`) if err != nil { t.Fatalf("去重查询失败: %v", err) } defer rows.Close() var vals []string for rows.Next() { var v string if err := rows.Scan(&v); err != nil { t.Fatalf("扫描去重值失败: %v", err) } vals = append(vals, v) } return vals }