codex-mobile-bridge 0.2.9

Remote bridge and service manager for codex-mobile.
Documentation
use std::env;
use std::fs;

use rusqlite::Connection;
use tempfile::tempdir;

use super::Storage;
use crate::bridge_protocol::{
    BridgeManagementOperation, BridgeManagementPhase, BridgeManagementStatus, BridgeManagementTask,
};

#[test]
fn ensure_primary_runtime_refreshes_existing_binary() {
    let base_dir =
        env::temp_dir().join(format!("codex-mobile-storage-test-{}", std::process::id()));
    fs::create_dir_all(&base_dir).expect("创建测试目录失败");
    let db_path = base_dir.join("bridge.db");
    let storage = Storage::open(db_path).expect("打开存储失败");

    let initial = storage
        .ensure_primary_runtime(None, "codex".to_string())
        .expect("创建 primary runtime 失败");
    assert_eq!(initial.codex_binary, "codex");

    let refreshed = storage
        .ensure_primary_runtime(None, "/home/test/.npm-global/bin/codex".to_string())
        .expect("刷新 primary runtime 失败");
    assert_eq!(refreshed.codex_binary, "/home/test/.npm-global/bin/codex");
}

#[test]
fn migrate_legacy_workspaces_preserves_existing_bookmarks() {
    let temp_dir = tempdir().expect("创建临时目录失败");
    let db_path = temp_dir.path().join("bridge.db");

    {
        let conn = Connection::open(&db_path).expect("创建旧版数据库失败");
        conn.execute_batch(
            "CREATE TABLE workspaces (
                root_path TEXT PRIMARY KEY,
                display_name TEXT NOT NULL,
                created_at_ms INTEGER NOT NULL,
                updated_at_ms INTEGER NOT NULL
            );

            CREATE TABLE directory_bookmarks (
                path TEXT PRIMARY KEY,
                display_name TEXT NOT NULL,
                created_at_ms INTEGER NOT NULL,
                updated_at_ms INTEGER NOT NULL
            );

            INSERT INTO workspaces (root_path, display_name, created_at_ms, updated_at_ms)
            VALUES
                ('/tmp/new-project', '新项目', 11, 12),
                ('/tmp/existing-project', '旧工作区名称', 21, 22);

            INSERT INTO directory_bookmarks (path, display_name, created_at_ms, updated_at_ms)
            VALUES ('/tmp/existing-project', '已存在书签', 31, 32);",
        )
        .expect("写入旧版测试数据失败");
    }

    Storage::open(db_path.clone()).expect("执行迁移失败");

    let conn = Connection::open(&db_path).expect("打开迁移后的数据库失败");
    let mut stmt = conn
        .prepare(
            "SELECT path, display_name, created_at_ms, updated_at_ms
             FROM directory_bookmarks
             ORDER BY path ASC",
        )
        .expect("准备查询目录书签失败");
    let bookmarks = stmt
        .query_map([], |row| {
            Ok((
                row.get::<_, String>(0)?,
                row.get::<_, String>(1)?,
                row.get::<_, i64>(2)?,
                row.get::<_, i64>(3)?,
            ))
        })
        .expect("查询目录书签失败")
        .collect::<rusqlite::Result<Vec<_>>>()
        .expect("收集目录书签失败");

    assert_eq!(
        bookmarks,
        vec![
            (
                "/tmp/existing-project".to_string(),
                "已存在书签".to_string(),
                31,
                32,
            ),
            ("/tmp/new-project".to_string(), "新项目".to_string(), 11, 12),
        ]
    );

    let workspace_exists = conn
        .query_row(
            "SELECT EXISTS(
                SELECT 1
                FROM sqlite_master
                WHERE type = 'table' AND name = 'workspaces'
            )",
            [],
            |row| row.get::<_, i64>(0),
        )
        .expect("检查 workspaces 表失败");
    assert_eq!(workspace_exists, 0);
}

#[test]
fn bridge_management_tasks_can_round_trip() {
    let temp_dir = tempdir().expect("创建临时目录失败");
    let db_path = temp_dir.path().join("bridge.db");
    let storage = Storage::open(db_path).expect("打开存储失败");

    let task = BridgeManagementTask {
        task_id: "task-1".to_string(),
        operation: BridgeManagementOperation::Update,
        status: BridgeManagementStatus::Running,
        phase: BridgeManagementPhase::InstallRelease,
        summary: "正在安装 bridge 0.2.8".to_string(),
        detail: Some("通过 cargo install 拉取 release".to_string()),
        failure_code: None,
        target_version: Some("0.2.8".to_string()),
        current_version: Some("0.2.7".to_string()),
        started_at_ms: 1,
        updated_at_ms: 2,
        snapshot: None,
    };

    storage
        .upsert_bridge_management_task(&task)
        .expect("写入 bridge 管理任务失败");

    let loaded = storage
        .get_bridge_management_task("task-1")
        .expect("读取 bridge 管理任务失败")
        .expect("bridge 管理任务不存在");

    assert_eq!(loaded.task_id, "task-1");
    assert_eq!(loaded.target_version.as_deref(), Some("0.2.8"));
    assert_eq!(loaded.current_version.as_deref(), Some("0.2.7"));
    assert!(
        storage
            .active_bridge_management_task()
            .expect("查询活动任务失败")
            .is_some()
    );
}