frigg 0.4.2

Local-first MCP server for code understanding.
Documentation
use super::support::*;

#[test]
fn initialize_applies_base_schema_and_version() -> FriggResult<()> {
    let db_path = temp_db_path("init-base-schema");
    let storage = Storage::new(&db_path);
    let expected_schema_version = MIGRATIONS
        .last()
        .expect("storage migrations should not be empty")
        .version;

    storage.initialize()?;

    assert_eq!(storage.schema_version()?, expected_schema_version);

    let conn = open_test_connection(&db_path)?;
    for table in [
        "schema_version",
        "repository",
        "snapshot",
        "file_manifest",
        "provenance_event",
        "semantic_chunk",
        "semantic_chunk_embedding",
        "path_witness_projection",
        "test_subject_projection",
        "entrypoint_surface_projection",
        "retrieval_projection_head",
        "path_relation_projection",
        "subtree_coverage_projection",
        "path_surface_term_projection",
        "path_anchor_sketch_projection",
    ] {
        assert!(
            table_exists(&conn, table)?,
            "expected table '{table}' to exist"
        );
    }

    cleanup_db(&db_path);
    Ok(())
}

#[test]
fn initialize_is_idempotent() -> FriggResult<()> {
    let db_path = temp_db_path("init-idempotent");
    let storage = Storage::new(&db_path);
    let expected_schema_version = MIGRATIONS
        .last()
        .expect("storage migrations should not be empty")
        .version;

    storage.initialize()?;
    {
        let conn = open_test_connection(&db_path)?;
        conn.execute(
            r#"
                INSERT INTO repository (repository_id, root_path, display_name, created_at)
                VALUES ('repo-1', '/tmp/repo-1', 'Repo 1', '2026-03-04T00:00:00Z')
                "#,
            [],
        )
        .map_err(|err| {
            FriggError::Internal(format!("failed to seed repository row for test: {err}"))
        })?;
    }

    storage.initialize()?;

    assert_eq!(storage.schema_version()?, expected_schema_version);

    let conn = open_test_connection(&db_path)?;
    let schema_version_rows: i64 = conn
        .query_row("SELECT COUNT(*) FROM schema_version", [], |row| row.get(0))
        .map_err(|err| {
            FriggError::Internal(format!("failed to count schema version rows: {err}"))
        })?;
    assert_eq!(schema_version_rows, 1);

    let repository_rows: i64 = conn
        .query_row("SELECT COUNT(*) FROM repository", [], |row| row.get(0))
        .map_err(|err| FriggError::Internal(format!("failed to count repository rows: {err}")))?;
    assert_eq!(repository_rows, 1);

    cleanup_db(&db_path);
    Ok(())
}

#[test]
fn verify_succeeds_after_initialize() -> FriggResult<()> {
    let db_path = temp_db_path("verify-success");
    let storage = Storage::new(&db_path);

    storage.initialize()?;
    storage.verify()?;

    cleanup_db(&db_path);
    Ok(())
}

#[test]
fn verify_fails_when_required_table_missing() -> FriggResult<()> {
    let db_path = temp_db_path("verify-missing-table");
    let storage = Storage::new(&db_path);

    storage.initialize()?;
    {
        let conn = open_test_connection(&db_path)?;
        conn.execute("DROP TABLE snapshot", []).map_err(|err| {
            FriggError::Internal(format!(
                "failed to drop snapshot table for verify test: {err}"
            ))
        })?;
    }

    let err = storage
        .verify()
        .expect_err("verify should fail when schema table is missing");
    let err_message = err.to_string();
    assert!(
        err_message.contains("missing required table 'snapshot'"),
        "unexpected verify error: {err_message}"
    );

    cleanup_db(&db_path);
    Ok(())
}

#[test]
fn verify_fails_when_manifest_rows_reference_non_manifest_snapshots() -> FriggResult<()> {
    let db_path = temp_db_path("verify-non-manifest-manifest-row");
    let storage = Storage::new(&db_path);

    storage.initialize()?;
    {
        let conn = open_test_connection(&db_path)?;
        conn.execute(
            "INSERT INTO repository (repository_id, root_path, display_name, created_at) VALUES ('repo-1', '/tmp/repo-1', 'Repo 1', '2026-03-11T00:00:00Z')",
            [],
        )
        .map_err(|err| FriggError::Internal(format!("failed to seed repository row for manifest invariant test: {err}")))?;
        conn.execute(
            r#"
            INSERT INTO snapshot (snapshot_id, repository_id, kind, revision, created_at)
            VALUES ('snapshot-semantic', 'repo-1', 'semantic', NULL, '2026-03-11T00:00:00Z')
            "#,
            [],
        )
        .map_err(|err| {
            FriggError::Internal(format!(
                "failed to seed non-manifest snapshot for manifest invariant test: {err}"
            ))
        })?;
        conn.execute(
            r#"
            INSERT INTO file_manifest (snapshot_id, path, sha256, size_bytes, mtime_ns)
            VALUES ('snapshot-semantic', 'src/drift.rs', 'hash-drift', 64, 12345)
            "#,
            [],
        )
        .map_err(|err| {
            FriggError::Internal(format!(
                "failed to seed drifted manifest row for manifest invariant test: {err}"
            ))
        })?;
    }

    let err = storage
        .verify()
        .expect_err("verify should fail when file_manifest rows reference non-manifest snapshots");
    let err_message = err.to_string();
    assert!(
        err_message.contains("invariant=manifest_rows_require_manifest_snapshots"),
        "unexpected invariant error: {err_message}"
    );
    assert!(
        err_message.contains("count=1"),
        "unexpected manifest invariant count: {err_message}"
    );

    cleanup_db(&db_path);
    Ok(())
}

#[test]
fn provenance_trace_ids_are_uuid_v7_and_unique() {
    let first = Storage::new_provenance_trace_id("search_symbol");
    let second = Storage::new_provenance_trace_id("search_symbol");

    assert_ne!(first, second, "trace ids must be unique");
    assert_eq!(first.len(), 36, "uuid trace ids should use canonical form");
    assert_eq!(second.len(), 36, "uuid trace ids should use canonical form");
    assert_eq!(
        first.as_bytes().get(14),
        Some(&b'7'),
        "expected UUIDv7 version nibble in first trace id"
    );
    assert_eq!(
        second.as_bytes().get(14),
        Some(&b'7'),
        "expected UUIDv7 version nibble in second trace id"
    );
}

#[test]
fn initialize_creates_hotpath_indexes_for_snapshot_and_provenance_queries() -> FriggResult<()> {
    let db_path = temp_db_path("hotpath-indexes");
    let storage = Storage::new(&db_path);
    storage.initialize()?;

    let conn = open_test_connection(&db_path)?;
    for index_name in [
        "idx_snapshot_repository_created_snapshot",
        "idx_provenance_tool_created_trace",
        "idx_test_subject_projection_repo_snapshot_test",
        "idx_test_subject_projection_repo_snapshot_subject",
        "idx_entrypoint_surface_projection_repo_snapshot_path",
        "idx_retrieval_projection_head_repo_snapshot_family",
        "idx_path_relation_projection_repo_snapshot_src",
        "idx_path_relation_projection_repo_snapshot_dst",
        "idx_subtree_coverage_projection_repo_snapshot_subtree",
        "idx_path_surface_term_projection_repo_snapshot_path",
        "idx_path_anchor_sketch_projection_repo_snapshot_path",
    ] {
        assert!(
            index_exists(&conn, index_name)?,
            "expected index '{index_name}' to exist"
        );
    }

    let snapshot_plan = explain_query_plan(
        &conn,
        r#"
            SELECT snapshot_id
            FROM snapshot
            WHERE repository_id = 'repo-1'
            ORDER BY created_at DESC, rowid DESC
            LIMIT 1
            "#,
    )?;
    assert!(
        snapshot_plan
            .iter()
            .any(|detail| detail.contains("idx_snapshot_repository_created_snapshot")),
        "expected snapshot latest lookup plan to use hotpath index, got {snapshot_plan:?}"
    );

    let provenance_plan = explain_query_plan(
        &conn,
        r#"
            SELECT trace_id, tool_name, payload_json, created_at
            FROM provenance_event
            WHERE tool_name = 'read_file'
            ORDER BY created_at DESC, rowid DESC
            LIMIT 10
            "#,
    )?;
    assert!(
        provenance_plan
            .iter()
            .any(|detail| detail.contains("idx_provenance_tool_created_trace")),
        "expected provenance tool lookup plan to use hotpath index, got {provenance_plan:?}"
    );

    cleanup_db(&db_path);
    Ok(())
}

#[test]
fn migration_8_enforces_snapshot_repository_and_manifest_row_references() -> FriggResult<()> {
    let db_path = temp_db_path("fk-manifest-references");
    let storage = Storage::new(&db_path);
    storage.initialize()?;

    let conn = open_test_connection(&db_path)?;
    conn.execute_batch("PRAGMA foreign_keys = ON;")
        .map_err(|err| {
            FriggError::Internal(format!("failed to enable foreign key checks: {err}"))
        })?;

    let snapshot_repo_err = conn
        .execute(
            r#"
            INSERT INTO snapshot (snapshot_id, repository_id, kind, revision, created_at)
            VALUES ('snapshot-orphan', 'repo-missing', 'manifest', NULL, '2026-03-11T00:00:00Z')
            "#,
            [],
        )
        .expect_err("snapshot with missing repository should fail under FK constraint");
    assert!(snapshot_repo_err.to_string().contains("FOREIGN KEY"));

    conn.execute(
        "INSERT INTO repository (repository_id, root_path, display_name, created_at) VALUES ('repo-1', '/tmp/repo', 'repo-1', '2026-03-11T00:00:00Z')",
        [],
    )
    .map_err(|err| FriggError::Internal(format!("failed to seed manifest repository for test: {err}")))?;
    conn.execute(
        r#"
            INSERT INTO snapshot (snapshot_id, repository_id, kind, revision, created_at)
            VALUES ('snapshot-manifest', 'repo-1', 'manifest', NULL, '2026-03-11T00:00:00Z')
            "#,
        [],
    )
    .map_err(|err| {
        FriggError::Internal(format!("failed to seed manifest snapshot for test: {err}"))
    })?;
    conn.execute(
        r#"
            INSERT INTO file_manifest (snapshot_id, path, sha256, size_bytes, mtime_ns)
            VALUES ('snapshot-manifest', 'src/main.rs', 'hash-main', 128, 12345)
            "#,
        [],
    )
    .map_err(|err| FriggError::Internal(format!("failed to seed manifest row for test: {err}")))?;

    let manifest_ref_err = conn
        .execute(
            r#"
            INSERT INTO file_manifest (snapshot_id, path, sha256, size_bytes, mtime_ns)
            VALUES ('snapshot-missing', 'src/bad.rs', 'hash-bad', 10, 10)
            "#,
            [],
        )
        .expect_err("manifest rows for unknown snapshot should fail under FK constraint");
    assert!(manifest_ref_err.to_string().contains("FOREIGN KEY"));

    let manifest_row_count: i64 = conn
        .query_row(
            "SELECT COUNT(*) FROM file_manifest WHERE snapshot_id = 'snapshot-manifest'",
            [],
            |row| row.get(0),
        )
        .map_err(|err| {
            FriggError::Internal(format!(
                "failed to count manifest rows before cascade assertion: {err}"
            ))
        })?;
    assert_eq!(manifest_row_count, 1);

    conn.execute(
        "DELETE FROM snapshot WHERE snapshot_id = 'snapshot-manifest'",
        [],
    )
    .map_err(|err| {
        FriggError::Internal(format!(
            "failed to delete snapshot for cascade assertion: {err}"
        ))
    })?;

    let manifest_row_count_after: i64 = conn
        .query_row(
            "SELECT COUNT(*) FROM file_manifest WHERE snapshot_id = 'snapshot-manifest'",
            [],
            |row| row.get(0),
        )
        .map_err(|err| {
            FriggError::Internal(format!(
                "failed to count manifest rows after cascade assertion: {err}"
            ))
        })?;
    assert_eq!(manifest_row_count_after, 0);

    cleanup_db(&db_path);
    Ok(())
}