gobby-code 1.3.3

Fast Rust CLI for Gobby's code index — AST-aware search, symbol navigation, and dependency graph
Documentation
const GCODE_POSTGRES_TEST_DATABASE_URL_ENV: &str = "GCODE_POSTGRES_TEST_DATABASE_URL";
const GOBBY_POSTGRES_TEST_DATABASE_URL_ENV: &str = "GOBBY_POSTGRES_TEST_DATABASE_URL";
const DATABASE_URL_ENV: &str = "DATABASE_URL";
const GOBBY_HOME_ENV: &str = "GOBBY_HOME";

pub fn postgres_test_database_url(purpose: &str) -> String {
    match postgres_test_database_url_from_sources() {
        Ok(Some(database_url)) => database_url,
        Ok(None) => {
            panic!(
                "{purpose} requires a PostgreSQL test database URL; set \
                 {GCODE_POSTGRES_TEST_DATABASE_URL_ENV}, \
                 {GOBBY_POSTGRES_TEST_DATABASE_URL_ENV}, {DATABASE_URL_ENV}, \
                 GOBBY_POSTGRES_TEST_* components, or {GOBBY_HOME_ENV}/bootstrap.yaml"
            )
        }
        Err(error) => {
            panic!("{purpose} failed to read PostgreSQL test database URL sources: {error:#}")
        }
    }
}

fn postgres_test_database_url_from_sources() -> anyhow::Result<Option<String>> {
    if let Some(database_url) = [
        GCODE_POSTGRES_TEST_DATABASE_URL_ENV,
        GOBBY_POSTGRES_TEST_DATABASE_URL_ENV,
        DATABASE_URL_ENV,
    ]
    .iter()
    .find_map(|name| non_empty_env(name))
    {
        return Ok(Some(database_url));
    }

    if let Some(database_url) = postgres_test_database_url_from_parts() {
        return Ok(Some(database_url));
    }

    postgres_test_database_url_from_bootstrap()
}

fn postgres_test_database_url_from_parts() -> Option<String> {
    let database = non_empty_env("GOBBY_POSTGRES_TEST_DB")?;
    let user = non_empty_env("GOBBY_POSTGRES_TEST_USER")?;
    let password = non_empty_env("GOBBY_POSTGRES_TEST_PASSWORD").unwrap_or_default();
    let host = non_empty_env("GOBBY_POSTGRES_TEST_HOST").unwrap_or_else(|| "localhost".to_string());
    let port = non_empty_env("GOBBY_POSTGRES_TEST_PORT").unwrap_or_else(|| "5432".to_string());

    Some(format!(
        "postgresql://{user}:{password}@{host}:{port}/{database}"
    ))
}

fn postgres_test_database_url_from_bootstrap() -> anyhow::Result<Option<String>> {
    let Some(path) = gobby_core::bootstrap::bootstrap_path() else {
        return Ok(None);
    };
    gobby_core::bootstrap::postgres_database_url_from_bootstrap_file(&path)
}

fn non_empty_env(name: &str) -> Option<String> {
    std::env::var(name)
        .ok()
        .map(|value| value.trim().to_string())
        .filter(|value| !value.is_empty())
}

#[cfg(test)]
mod tests {
    use super::*;

    const POSTGRES_TEST_ENV_KEYS: &[&str] = &[
        GCODE_POSTGRES_TEST_DATABASE_URL_ENV,
        GOBBY_POSTGRES_TEST_DATABASE_URL_ENV,
        DATABASE_URL_ENV,
        "GOBBY_POSTGRES_TEST_DB",
        "GOBBY_POSTGRES_TEST_USER",
        "GOBBY_POSTGRES_TEST_PASSWORD",
        "GOBBY_POSTGRES_TEST_HOST",
        "GOBBY_POSTGRES_TEST_PORT",
        GOBBY_HOME_ENV,
    ];

    fn with_postgres_test_env<R>(
        overrides: &[(&str, Option<&str>)],
        closure: impl FnOnce() -> R,
    ) -> R {
        let vars = POSTGRES_TEST_ENV_KEYS
            .iter()
            .map(|key| {
                let value = overrides
                    .iter()
                    .find_map(|(name, value)| (*name == *key).then_some(*value))
                    .unwrap_or(None);
                (*key, value)
            })
            .collect::<Vec<_>>();
        temp_env::with_vars(vars, closure)
    }

    #[test]
    #[serial_test::serial(serial_db)]
    fn test_env_prefers_gcode_specific_database_url() {
        with_postgres_test_env(
            &[
                (
                    GCODE_POSTGRES_TEST_DATABASE_URL_ENV,
                    Some("postgresql://gcode/db"),
                ),
                (
                    GOBBY_POSTGRES_TEST_DATABASE_URL_ENV,
                    Some("postgresql://gobby/db"),
                ),
                (DATABASE_URL_ENV, Some("postgresql://database/db")),
            ],
            || {
                assert_eq!(
                    postgres_test_database_url_from_sources()
                        .unwrap()
                        .as_deref(),
                    Some("postgresql://gcode/db")
                );
            },
        );
    }

    #[test]
    #[serial_test::serial(serial_db)]
    fn test_env_uses_component_var_fallback() {
        with_postgres_test_env(
            &[
                ("GOBBY_POSTGRES_TEST_DB", Some("gcode_test")),
                ("GOBBY_POSTGRES_TEST_USER", Some("tester")),
                ("GOBBY_POSTGRES_TEST_PASSWORD", Some("secret")),
                ("GOBBY_POSTGRES_TEST_HOST", Some("db.local")),
                ("GOBBY_POSTGRES_TEST_PORT", Some("15432")),
            ],
            || {
                assert_eq!(
                    postgres_test_database_url_from_sources()
                        .unwrap()
                        .as_deref(),
                    Some("postgresql://tester:secret@db.local:15432/gcode_test")
                );
            },
        );
    }

    #[test]
    #[serial_test::serial(serial_db)]
    fn test_env_uses_bootstrap_fallback() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(
            dir.path().join("bootstrap.yaml"),
            "hub_backend: postgres\ndatabase_url: postgresql://bootstrap/gobby\n",
        )
        .unwrap();
        let home = dir.path().to_str().unwrap();

        with_postgres_test_env(&[(GOBBY_HOME_ENV, Some(home))], || {
            assert_eq!(
                postgres_test_database_url_from_sources()
                    .unwrap()
                    .as_deref(),
                Some("postgresql://bootstrap/gobby")
            );
        });
    }
}