dev-manager-tauri/src-tauri/src/db.rs
lanrtop 6162533c77 feat: 飞轮阶段一 + 项目个人备忘功能
飞轮智能成长阶段一:
- 梦核 prompt 注入跨项目复盘笔记,新增【高频陷阱】分析节
- 修复 collect_flywheel_notes 查错表 bug(projects → project_workspaces)
- MCP append_project_note 描述内嵌【复盘】结构化格式,引导 Claude 写标准笔记
- FlywheelStats 新增 project_notes 字段,支持 win_path/wsl_path 两种路径匹配

项目个人备忘(user_project_todos):
- 新表 + 4 个 Rust command,依赖注入写法,6 个单元测试全绿
- UserTodoPanel 组件,📝 按钮展开,支持新增/勾选/删除

CONVENTIONS v1.4.4:新增 Rust 可测试性规范(依赖注入强制要求)
2026-04-11 17:21:47 +09:00

643 lines
25 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use once_cell::sync::OnceCell;
use r2d2::Pool;
use r2d2_sqlite::SqliteConnectionManager;
use std::path::PathBuf;
pub type DbPool = Pool<SqliteConnectionManager>;
static POOL: OnceCell<DbPool> = OnceCell::new();
pub fn init(data_dir: PathBuf) {
std::fs::create_dir_all(&data_dir).expect("create data dir");
let db_path = data_dir.join("dev-manager.db");
let manager = SqliteConnectionManager::file(db_path);
let pool = Pool::new(manager).expect("create sqlite pool");
{
let conn = pool.get().expect("get conn for migration");
conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")
.expect("pragma");
migrate(&conn);
}
POOL.set(pool).expect("pool already initialized");
}
pub fn pool() -> &'static DbPool {
POOL.get().expect("db not initialized")
}
fn migrate(conn: &rusqlite::Connection) {
conn.execute_batch(
"
CREATE TABLE IF NOT EXISTS projects (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
type TEXT,
status TEXT DEFAULT 'active',
priority TEXT DEFAULT 'mid',
wsl_path TEXT,
win_path TEXT,
tech_stack TEXT,
recent_focus TEXT,
startup_commands TEXT,
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS product_groups (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT
);
CREATE TABLE IF NOT EXISTS project_group_map (
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
group_id TEXT NOT NULL REFERENCES product_groups(id) ON DELETE CASCADE,
role TEXT,
PRIMARY KEY (project_id, group_id)
);
CREATE TABLE IF NOT EXISTS relations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
from_project TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
to_project TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
relation_type TEXT,
note TEXT
);
CREATE TABLE IF NOT EXISTS activity_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
summary TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS project_tools (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
name TEXT NOT NULL,
icon TEXT NOT NULL DEFAULT '🔧',
exe_path TEXT NOT NULL,
args TEXT,
sort_order INTEGER DEFAULT 0
);
CREATE TABLE IF NOT EXISTS dev_tools (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL,
cmd TEXT,
path TEXT,
version TEXT,
note TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS project_env_tools (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
layer INTEGER NOT NULL,
name TEXT NOT NULL,
source TEXT,
version TEXT,
note TEXT,
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS network_monitored_tools (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
exe_path TEXT NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1
);
CREATE TABLE IF NOT EXISTS network_mode_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
mode TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
);
INSERT OR IGNORE INTO settings (key, value) VALUES ('editor', 'cursor');
INSERT OR IGNORE INTO settings (key, value) VALUES ('wsl_distro', 'Ubuntu');
INSERT OR IGNORE INTO settings (key, value) VALUES ('dev_mode', '0');
INSERT OR IGNORE INTO settings (key, value) VALUES ('github_client_id', '');
INSERT OR IGNORE INTO settings (key, value) VALUES ('github_client_secret', '');
INSERT OR IGNORE INTO settings (key, value) VALUES ('mcp_port', '27190');
",
)
.expect("migrate");
// 增量迁移:为 dev_tools 添加 env 字段(已存在则忽略)
let _ = conn.execute(
"ALTER TABLE dev_tools ADD COLUMN env TEXT NOT NULL DEFAULT 'windows'",
[],
);
// 增量迁移Git 多空间功能所需新表
conn.execute_batch(
"
CREATE TABLE IF NOT EXISTS git_accounts (
id TEXT PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
avatar_url TEXT,
provider TEXT NOT NULL DEFAULT 'github',
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS spaces (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
local_root TEXT NOT NULL,
is_current INTEGER NOT NULL DEFAULT 0,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS deploy_commands (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
name TEXT NOT NULL,
command TEXT NOT NULL,
run_in TEXT NOT NULL DEFAULT 'windows'
);
",
)
.expect("migrate git spaces");
// 增量迁移git_accounts 补 UNIQUE 索引(已存在则忽略)
let _ = conn.execute(
"CREATE UNIQUE INDEX IF NOT EXISTS idx_git_accounts_username ON git_accounts(username)",
[],
);
// 增量迁移projects 表新增 Git 相关字段(已存在则忽略)
let _ = conn.execute("ALTER TABLE projects ADD COLUMN space_id TEXT", []);
let _ = conn.execute("ALTER TABLE projects ADD COLUMN repo_url TEXT", []);
let _ = conn.execute("ALTER TABLE projects ADD COLUMN upstream_url TEXT", []);
let _ = conn.execute("ALTER TABLE projects ADD COLUMN default_branch TEXT", []);
let _ = conn.execute("ALTER TABLE projects ADD COLUMN last_push_at TEXT", []);
// 增量迁移platform 字段("windows" | "wsl",已存在则忽略)
let _ = conn.execute("ALTER TABLE projects ADD COLUMN platform TEXT", []);
// 增量迁移product_groups 表新增 space_id已存在则忽略
let _ = conn.execute("ALTER TABLE product_groups ADD COLUMN space_id TEXT", []);
// 增量迁移dev_tools 表新增 space_id已存在则忽略
let _ = conn.execute("ALTER TABLE dev_tools ADD COLUMN space_id TEXT", []);
// 增量迁移network_monitored_tools 新增 space_id已存在则忽略
let _ = conn.execute("ALTER TABLE network_monitored_tools ADD COLUMN space_id TEXT", []);
// 增量迁移:空间级别的 settings 覆盖表
let _ = conn.execute_batch(
"CREATE TABLE IF NOT EXISTS space_settings (
space_id TEXT NOT NULL,
key TEXT NOT NULL,
value TEXT NOT NULL,
PRIMARY KEY (space_id, key)
);"
);
// 增量迁移:项目生命周期字段
let _ = conn.execute("ALTER TABLE projects ADD COLUMN staging_url TEXT", []);
let _ = conn.execute("ALTER TABLE projects ADD COLUMN prod_url TEXT", []);
let _ = conn.execute("ALTER TABLE projects ADD COLUMN server_note TEXT", []);
// ── 方案A重构project_profiles + project_workspaces ──────────────────────
conn.execute_batch("
CREATE TABLE IF NOT EXISTS project_profiles (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
type TEXT,
status TEXT DEFAULT 'active',
priority TEXT DEFAULT 'mid',
tech_stack TEXT,
repo_url TEXT,
upstream_url TEXT,
default_branch TEXT,
staging_url TEXT,
prod_url TEXT,
server_note TEXT,
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS project_workspaces (
id TEXT PRIMARY KEY,
profile_id TEXT NOT NULL REFERENCES project_profiles(id) ON DELETE CASCADE,
space_id TEXT,
wsl_path TEXT,
win_path TEXT,
platform TEXT DEFAULT 'windows',
recent_focus TEXT,
startup_commands TEXT,
last_push_at TEXT,
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_pw_profile_space
ON project_workspaces(profile_id, ifnull(space_id, ''));
").expect("migrate project profiles/workspaces");
// ── git_reposGitHub 仓库注册表(全局锚点)────────────────────────────────
conn.execute_batch("
CREATE TABLE IF NOT EXISTS git_repos (
id TEXT PRIMARY KEY,
repo_url TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT,
default_branch TEXT DEFAULT 'main',
upstream_url TEXT,
staging_url TEXT,
prod_url TEXT,
server_note TEXT,
status TEXT DEFAULT 'active',
priority TEXT DEFAULT 'mid',
tech_stack TEXT,
last_synced_at TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_git_repos_url ON git_repos(repo_url);
").expect("migrate git_repos");
// project_workspaces 添加 repo_id 外键(已存在则忽略)
let _ = conn.execute(
"ALTER TABLE project_workspaces ADD COLUMN repo_id TEXT REFERENCES git_repos(id) ON DELETE SET NULL",
[],
);
// 增量迁移:项目模板系统
conn.execute_batch("
CREATE TABLE IF NOT EXISTS project_templates (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
platform TEXT NOT NULL DEFAULT 'both',
tech_stack TEXT,
init_commands TEXT,
is_builtin INTEGER NOT NULL DEFAULT 0,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS project_template_files (
id TEXT PRIMARY KEY,
template_id TEXT NOT NULL REFERENCES project_templates(id) ON DELETE CASCADE,
path TEXT NOT NULL,
content TEXT NOT NULL DEFAULT '',
UNIQUE(template_id, path)
);
").expect("migrate project templates");
// 迁移 project_group_map将外键从 projects(id) 改为 project_workspaces(id)
let pgm_sql: Option<String> = conn.query_row(
"SELECT sql FROM sqlite_master WHERE type='table' AND name='project_group_map'",
[],
|row| row.get(0),
).ok();
if pgm_sql.map(|s| s.contains("REFERENCES projects")).unwrap_or(false) {
conn.execute_batch("
PRAGMA foreign_keys=OFF;
CREATE TABLE project_group_map_new (
project_id TEXT NOT NULL REFERENCES project_workspaces(id) ON DELETE CASCADE,
group_id TEXT NOT NULL REFERENCES product_groups(id) ON DELETE CASCADE,
role TEXT,
PRIMARY KEY (project_id, group_id)
);
INSERT OR IGNORE INTO project_group_map_new (project_id, group_id, role)
SELECT m.project_id, m.group_id, m.role
FROM project_group_map m
INNER JOIN project_workspaces pw ON pw.id = m.project_id;
DROP TABLE project_group_map;
ALTER TABLE project_group_map_new RENAME TO project_group_map;
PRAGMA foreign_keys=ON;
").expect("migrate project_group_map FK to project_workspaces");
}
// 迁移 project_tools / activity_log / project_env_tools
// 将外键从 projects(id) 改为 project_workspaces(id)
let pt_sql: Option<String> = conn.query_row(
"SELECT sql FROM sqlite_master WHERE type='table' AND name='project_tools'",
[], |row| row.get(0),
).ok();
if pt_sql.as_deref().map(|s| s.contains("REFERENCES projects")).unwrap_or(false) {
conn.execute_batch("
PRAGMA foreign_keys=OFF;
CREATE TABLE project_tools_new (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES project_workspaces(id) ON DELETE CASCADE,
name TEXT NOT NULL,
icon TEXT NOT NULL DEFAULT '🔧',
exe_path TEXT NOT NULL,
args TEXT,
sort_order INTEGER DEFAULT 0
);
INSERT OR IGNORE INTO project_tools_new
SELECT t.id, t.project_id, t.name, t.icon, t.exe_path, t.args, t.sort_order
FROM project_tools t
INNER JOIN project_workspaces pw ON pw.id = t.project_id;
DROP TABLE project_tools;
ALTER TABLE project_tools_new RENAME TO project_tools;
PRAGMA foreign_keys=ON;
").expect("migrate project_tools FK to project_workspaces");
}
let al_sql: Option<String> = conn.query_row(
"SELECT sql FROM sqlite_master WHERE type='table' AND name='activity_log'",
[], |row| row.get(0),
).ok();
if al_sql.as_deref().map(|s| s.contains("REFERENCES projects")).unwrap_or(false) {
conn.execute_batch("
PRAGMA foreign_keys=OFF;
CREATE TABLE activity_log_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id TEXT NOT NULL REFERENCES project_workspaces(id) ON DELETE CASCADE,
summary TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
);
INSERT OR IGNORE INTO activity_log_new (id, project_id, summary, created_at)
SELECT a.id, a.project_id, a.summary, a.created_at
FROM activity_log a
INNER JOIN project_workspaces pw ON pw.id = a.project_id;
DROP TABLE activity_log;
ALTER TABLE activity_log_new RENAME TO activity_log;
PRAGMA foreign_keys=ON;
").expect("migrate activity_log FK to project_workspaces");
}
let pet_sql: Option<String> = conn.query_row(
"SELECT sql FROM sqlite_master WHERE type='table' AND name='project_env_tools'",
[], |row| row.get(0),
).ok();
if pet_sql.as_deref().map(|s| s.contains("REFERENCES projects")).unwrap_or(false) {
conn.execute_batch("
PRAGMA foreign_keys=OFF;
CREATE TABLE project_env_tools_new (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES project_workspaces(id) ON DELETE CASCADE,
layer INTEGER NOT NULL,
name TEXT NOT NULL,
source TEXT,
version TEXT,
note TEXT,
updated_at TEXT DEFAULT (datetime('now'))
);
INSERT OR IGNORE INTO project_env_tools_new
SELECT t.id, t.project_id, t.layer, t.name, t.source, t.version, t.note, t.updated_at
FROM project_env_tools t
INNER JOIN project_workspaces pw ON pw.id = t.project_id;
DROP TABLE project_env_tools;
ALTER TABLE project_env_tools_new RENAME TO project_env_tools;
PRAGMA foreign_keys=ON;
").expect("migrate project_env_tools FK to project_workspaces");
}
// 从旧 projects 表迁移数据INSERT OR IGNORE 保证幂等)
let _ = conn.execute_batch("
INSERT OR IGNORE INTO project_profiles
(id, name, description, type, status, priority, tech_stack,
repo_url, upstream_url, default_branch,
staging_url, prod_url, server_note, updated_at)
SELECT
'prof-' || id,
name, description, type, status, priority, tech_stack,
repo_url, upstream_url, default_branch,
staging_url, prod_url, server_note, updated_at
FROM projects;
INSERT OR IGNORE INTO project_workspaces
(id, profile_id, space_id, wsl_path, win_path, platform,
recent_focus, startup_commands, last_push_at, updated_at)
SELECT
id,
'prof-' || id,
space_id, wsl_path, win_path, COALESCE(platform, 'windows'),
recent_focus, startup_commands, last_push_at, updated_at
FROM projects;
");
// 增量迁移project_profiles 加 tags 字段
let _ = conn.execute("ALTER TABLE project_profiles ADD COLUMN tags TEXT", []);
// 标签管理表
conn.execute_batch("
CREATE TABLE IF NOT EXISTS project_tags (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
color TEXT NOT NULL DEFAULT '#8b5cf6'
);
CREATE TABLE IF NOT EXISTS project_tag_map (
profile_id TEXT NOT NULL,
tag_id TEXT NOT NULL,
PRIMARY KEY (profile_id, tag_id)
);
").expect("create tag tables");
// 增量迁移product_groups 加 sort_order 字段
let _ = conn.execute(
"ALTER TABLE product_groups ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0",
[],
);
// CI/CD 配置表(替代 deploy_commands
conn.execute_batch("
CREATE TABLE IF NOT EXISTS cicd_configs (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL UNIQUE,
config_type TEXT NOT NULL DEFAULT 'web_ssh',
trigger_branch TEXT NOT NULL DEFAULT 'main',
build_command TEXT DEFAULT 'pnpm build',
node_version TEXT DEFAULT '20',
server_host TEXT,
server_user TEXT DEFAULT 'ubuntu',
deploy_path TEXT,
start_command TEXT DEFAULT 'pm2 restart app',
oss_endpoint TEXT DEFAULT 'oss-cn-hangzhou.aliyuncs.com',
created_at TEXT DEFAULT (datetime('now'))
);
").expect("create cicd_configs table");
// 资源视角画布状态(每个产品组独立保存)
conn.execute_batch("
CREATE TABLE IF NOT EXISTS canvas_states (
group_id TEXT PRIMARY KEY,
data TEXT NOT NULL,
updated_at TEXT DEFAULT (datetime('now'))
);
").expect("create canvas_states table");
// 运行时可观测性日志(最多保留 500 条)
conn.execute_batch("
CREATE TABLE IF NOT EXISTS runtime_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
category TEXT NOT NULL,
level TEXT NOT NULL,
message TEXT NOT NULL,
context TEXT
);
").expect("create runtime_logs table");
// AI 笔记Claude 通过 MCP 或用户手动写入的分析结论)
conn.execute_batch("
CREATE TABLE IF NOT EXISTS project_notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id TEXT NOT NULL,
source TEXT NOT NULL DEFAULT 'mcp',
content TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now', 'localtime'))
);
").expect("create project_notes table");
// 用户项目个人备忘自由文本待办Claude 不感知)
conn.execute_batch("
CREATE TABLE IF NOT EXISTS user_project_todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id TEXT NOT NULL,
content TEXT NOT NULL,
done INTEGER NOT NULL DEFAULT 0,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TEXT DEFAULT (datetime('now', 'localtime'))
);
").expect("create user_project_todos table");
// 蓝图 Buff记录哪些项目施加了 buff
conn.execute_batch("
CREATE TABLE IF NOT EXISTS blueprint_buffs (
project_path TEXT PRIMARY KEY,
project_id TEXT NOT NULL,
applied_at TEXT NOT NULL DEFAULT (datetime('now')),
last_sync_at TEXT,
last_commit TEXT,
status TEXT NOT NULL DEFAULT 'active'
);
").expect("create blueprint_buffs table");
// 仪表盘全局快启应用(无项目 FK 约束)
conn.execute_batch("
CREATE TABLE IF NOT EXISTS global_tools (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
icon TEXT NOT NULL DEFAULT '🔧',
exe_path TEXT NOT NULL,
args TEXT,
sort_order INTEGER DEFAULT 0
);
").expect("create global_tools table");
// 增量迁移:项目历史快照(每日一条,供 Claude 做趋势分析)
conn.execute_batch("
CREATE TABLE IF NOT EXISTS project_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id TEXT NOT NULL,
snapshot_date TEXT NOT NULL,
days_since_commit INTEGER,
blueprint_done_rate REAL,
blueprint_in_progress INTEGER DEFAULT 0,
blueprint_blocked INTEGER DEFAULT 0,
has_errors INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now')),
UNIQUE(project_id, snapshot_date)
);
").expect("create project_snapshots table");
}
#[cfg(test)]
pub fn conn_with_schema() -> rusqlite::Connection {
let conn = rusqlite::Connection::open_in_memory().unwrap();
conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")
.unwrap();
migrate(&conn);
conn
}
/// 写入运行时日志。失败时静默(不能让日志写入反过来崩溃业务逻辑)
pub fn log_event(category: &str, level: &str, message: &str, context: Option<&str>) {
let pool = match POOL.get() {
Some(p) => p,
None => return,
};
let conn = match pool.get() {
Ok(c) => c,
Err(_) => return,
};
let _ = conn.execute(
"INSERT INTO runtime_logs (category, level, message, context) VALUES (?1, ?2, ?3, ?4)",
rusqlite::params![category, level, message, context],
);
// 超出 550 条时才触发清理,保留最新 500 条(避免每次写入都全表扫描)
let count: i64 = conn
.query_row("SELECT COUNT(*) FROM runtime_logs", [], |r| r.get(0))
.unwrap_or(0);
if count > 550 {
let _ = conn.execute(
"DELETE FROM runtime_logs WHERE id NOT IN (SELECT id FROM runtime_logs ORDER BY id DESC LIMIT 500)",
[],
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn migrate_idempotent() {
// 连续两次 migrate 不应 panic
let conn = conn_with_schema();
migrate(&conn);
}
#[test]
fn core_tables_exist() {
let conn = conn_with_schema();
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' \
AND name IN ('projects','settings','spaces','git_repos','runtime_logs',\
'project_profiles','project_workspaces')",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(count, 7, "缺少核心表");
}
#[test]
fn default_settings_seeded() {
let conn = conn_with_schema();
let editor: String = conn
.query_row("SELECT value FROM settings WHERE key='editor'", [], |r| r.get(0))
.unwrap();
assert_eq!(editor, "cursor");
let port: String = conn
.query_row("SELECT value FROM settings WHERE key='mcp_port'", [], |r| r.get(0))
.unwrap();
assert_eq!(port, "27190");
}
#[test]
fn runtime_logs_insert_and_count() {
let conn = conn_with_schema();
conn.execute(
"INSERT INTO runtime_logs (category, level, message) VALUES ('test','info','hello')",
[],
)
.unwrap();
let count: i64 = conn
.query_row("SELECT COUNT(*) FROM runtime_logs", [], |r| r.get(0))
.unwrap();
assert_eq!(count, 1);
}
#[test]
fn log_event_silent_when_no_pool() {
// pool 未初始化时应静默返回,不 panic
log_event("test", "info", "no-pool test", None);
}
}