Restrict Quire::new and Default impls to test builds; inline config loading
GlobalConfig::load is inlined into Quire::load, removing the separate
method. Default impls for GlobalConfig and Quire, and Quire::new, are
now #[cfg(test)] since they exist only to support test construction.
dev.rs switches to Quire::load (returns built-in defaults when the
seeded tempdir has no config file).
change
commit be7b284d1fdb8a0192713cdf2518fd1b76d6b8cc
author Claude <noreply@anthropic.com>
date
parent 65c497bd
diff --git a/quire-server/src/bin/quire/commands/dev.rs b/quire-server/src/bin/quire/commands/dev.rs
index 8442719..ccc566b 100644
--- a/quire-server/src/bin/quire/commands/dev.rs
+++ b/quire-server/src/bin/quire/commands/dev.rs
@@ -67,7 +67,7 @@ impl Seeder {
         let base_dir = dir.keep();
         tracing::info!(path = %base_dir.display(), "seeded tempdir");
 
-        let quire = Quire::new(base_dir, quire::GlobalConfig::default());
+        let quire = Quire::load(base_dir)?;
 
         // Create the repos dir + a bare repo so the web view resolves the repo.
         let bare_repo = quire.repos_dir().join("example.git");
diff --git a/quire-server/src/quire/mod.rs b/quire-server/src/quire/mod.rs
index e5e6794..b107707 100644
--- a/quire-server/src/quire/mod.rs
+++ b/quire-server/src/quire/mod.rs
@@ -36,6 +36,7 @@ pub struct GlobalConfig {
     pub github: GlobalGithubConfig,
 }
 
+#[cfg(test)]
 impl Default for GlobalConfig {
     fn default() -> Self {
         Self {
@@ -48,17 +49,6 @@ impl Default for GlobalConfig {
     }
 }
 
-impl GlobalConfig {
-    /// Load config from `path`. Returns `Self::default()` if the file is absent;
-    /// propagates parse errors.
-    pub fn load(path: &Path) -> Result<Self> {
-        if !path.exists() {
-            return Ok(Self::default());
-        }
-        Ok(Fennel::load_config(path)?)
-    }
-}
-
 /// Global GitHub integration configuration.
 #[derive(serde::Deserialize, Debug, Default, Clone)]
 #[serde(rename_all = "kebab-case")]
@@ -236,24 +226,27 @@ pub struct Quire {
     db_pool: Arc<OnceLock<Mutex<rusqlite::Connection>>>,
 }
 
-impl Default for Quire {
-    fn default() -> Self {
-        Self::new(PathBuf::from("/var/quire"), GlobalConfig::default())
-    }
-}
-
 impl Quire {
     /// Load config from `base_dir/config.fnl` and create a `Quire` rooted there.
     ///
-    /// Returns default config if the file is absent; propagates parse errors.
+    /// Returns built-in defaults if the file is absent; propagates parse errors.
     pub fn load(base_dir: PathBuf) -> Result<Self> {
-        let config = GlobalConfig::load(&base_dir.join("config.fnl"))?;
-        Ok(Self::new(base_dir, config))
+        let config_path = base_dir.join("config.fnl");
+        let config = if config_path.exists() {
+            Fennel::load_config(&config_path)?
+        } else {
+            GlobalConfig {
+                sentry: None,
+                secrets: HashMap::new(),
+                port: default_port(),
+                ci: CiConfig::default(),
+                github: GlobalGithubConfig::default(),
+            }
+        };
+        Ok(Self::init(base_dir, config))
     }
 
-    /// Create a `Quire` with an explicit config. Prefer `Quire::load` in production;
-    /// use this in tests that need direct control over config.
-    pub fn new(base_dir: PathBuf, config: GlobalConfig) -> Self {
+    fn init(base_dir: PathBuf, config: GlobalConfig) -> Self {
         Self {
             base_dir,
             config,
@@ -261,6 +254,11 @@ impl Quire {
         }
     }
 
+    #[cfg(test)]
+    pub fn new(base_dir: PathBuf, config: GlobalConfig) -> Self {
+        Self::init(base_dir, config)
+    }
+
     pub fn base_dir(&self) -> &Path {
         &self.base_dir
     }
@@ -335,6 +333,13 @@ impl Quire {
     }
 }
 
+#[cfg(test)]
+impl Default for Quire {
+    fn default() -> Self {
+        Self::new(PathBuf::from("/var/quire"), GlobalConfig::default())
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -468,88 +473,80 @@ mod tests {
     #[test]
     fn global_config_ci_defaults() {
         let dir = tempfile::tempdir().expect("tempdir");
-        let config_path = dir.path().join("config.fnl");
-        fs_err::write(&config_path, "{}").expect("write");
+        fs_err::write(dir.path().join("config.fnl"), "{}").expect("write");
 
-        let config = GlobalConfig::load(&config_path).expect("should load");
-        assert_eq!(config.ci.executor, Executor::Process);
-        assert_eq!(config.port, 3000);
+        let q = Quire::load(dir.path().to_path_buf()).expect("should load");
+        assert_eq!(q.config.ci.executor, Executor::Process);
+        assert_eq!(q.config.port, 3000);
     }
 
     #[test]
     fn global_config_parses_custom_port() {
         let dir = tempfile::tempdir().expect("tempdir");
-        let config_path = dir.path().join("config.fnl");
-        fs_err::write(&config_path, r#"{:port 4000}"#).expect("write");
+        fs_err::write(dir.path().join("config.fnl"), r#"{:port 4000}"#).expect("write");
 
-        let config = GlobalConfig::load(&config_path).expect("should load");
-        assert_eq!(config.port, 4000);
+        let q = Quire::load(dir.path().to_path_buf()).expect("should load");
+        assert_eq!(q.config.port, 4000);
     }
 
     #[test]
     fn global_config_loads_from_fennel_file() {
         let dir = tempfile::tempdir().expect("tempdir");
-        let config_path = dir.path().join("config.fnl");
-        fs_err::write(&config_path, "{}").expect("write");
+        fs_err::write(dir.path().join("config.fnl"), "{}").expect("write");
 
-        let config = GlobalConfig::load(&config_path).expect("should load");
-        assert!(config.secrets.is_empty());
+        let q = Quire::load(dir.path().to_path_buf()).expect("should load");
+        assert!(q.config.secrets.is_empty());
     }
 
     #[test]
     fn global_config_missing_file_uses_defaults() {
         let dir = tempfile::tempdir().expect("tempdir");
-        let config_path = dir.path().join("config.fnl");
 
-        let config = GlobalConfig::load(&config_path).expect("missing file should use defaults");
-        assert_eq!(config.port, 3000);
-        assert!(config.sentry.is_none());
-        assert!(config.secrets.is_empty());
+        let q = Quire::load(dir.path().to_path_buf()).expect("missing file should use defaults");
+        assert_eq!(q.config.port, 3000);
+        assert!(q.config.sentry.is_none());
+        assert!(q.config.secrets.is_empty());
     }
 
     #[test]
     fn global_config_loads_with_sentry() {
         let dir = tempfile::tempdir().expect("tempdir");
-        let config_path = dir.path().join("config.fnl");
         fs_err::write(
-            &config_path,
+            dir.path().join("config.fnl"),
             r#"{:sentry {:dsn "https://key@sentry.io/123"}}"#,
         )
         .expect("write");
 
-        let config = GlobalConfig::load(&config_path).expect("should load");
-        let sentry = config.sentry.expect("sentry should be present");
+        let q = Quire::load(dir.path().to_path_buf()).expect("should load");
+        let sentry = q.config.sentry.expect("sentry should be present");
         assert_eq!(sentry.dsn.reveal().unwrap(), "https://key@sentry.io/123");
     }
 
     #[test]
     fn global_config_sentry_is_optional() {
         let dir = tempfile::tempdir().expect("tempdir");
-        let config_path = dir.path().join("config.fnl");
-        fs_err::write(&config_path, "{}").expect("write");
+        fs_err::write(dir.path().join("config.fnl"), "{}").expect("write");
 
-        let config = GlobalConfig::load(&config_path).expect("should load");
-        assert!(config.sentry.is_none());
+        let q = Quire::load(dir.path().to_path_buf()).expect("should load");
+        assert!(q.config.sentry.is_none());
     }
 
     #[test]
     fn global_config_secrets_default_empty() {
         let dir = tempfile::tempdir().expect("tempdir");
-        let config_path = dir.path().join("config.fnl");
-        fs_err::write(&config_path, "{}").expect("write");
+        fs_err::write(dir.path().join("config.fnl"), "{}").expect("write");
 
-        let config = GlobalConfig::load(&config_path).expect("should load");
-        assert!(config.secrets.is_empty());
+        let q = Quire::load(dir.path().to_path_buf()).expect("should load");
+        assert!(q.config.secrets.is_empty());
     }
 
     #[test]
     fn global_config_loads_secrets_map() {
         let dir = tempfile::tempdir().expect("tempdir");
-        let config_path = dir.path().join("config.fnl");
         let secret_file = dir.path().join("gh_token");
         fs_err::write(&secret_file, "ghp_from_file\n").expect("write secret");
         fs_err::write(
-            &config_path,
+            dir.path().join("config.fnl"),
             format!(
                 r#"{{:secrets {{:github_token {{:file "{}"}}
                    :slack_webhook "https://hooks.slack.com/abc"}}}}"#,
@@ -558,14 +555,14 @@ mod tests {
         )
         .expect("write");
 
-        let config = GlobalConfig::load(&config_path).expect("should load");
-        assert_eq!(config.secrets.len(), 2);
+        let q = Quire::load(dir.path().to_path_buf()).expect("should load");
+        assert_eq!(q.config.secrets.len(), 2);
         assert_eq!(
-            config.secrets["github_token"].reveal().unwrap(),
+            q.config.secrets["github_token"].reveal().unwrap(),
             "ghp_from_file"
         );
         assert_eq!(
-            config.secrets["slack_webhook"].reveal().unwrap(),
+            q.config.secrets["slack_webhook"].reveal().unwrap(),
             "https://hooks.slack.com/abc"
         );
     }
diff --git a/quire-server/src/quire/web/api.rs b/quire-server/src/quire/web/api.rs
index c01f96a..c73557e 100644
--- a/quire-server/src/quire/web/api.rs
+++ b/quire-server/src/quire/web/api.rs
@@ -371,10 +371,14 @@ mod tests {
     async fn secret_returns_plaintext_value() {
         let config = {
             let dir = tempfile::tempdir().expect("tempdir");
-            let config_path = dir.path().join("config.fnl");
-            fs_err::write(&config_path, r#"{:secrets {:my_token "hunter2"}}"#)
-                .expect("write config");
-            crate::quire::GlobalConfig::load(&config_path).expect("load config")
+            fs_err::write(
+                dir.path().join("config.fnl"),
+                r#"{:secrets {:my_token "hunter2"}}"#,
+            )
+            .expect("write config");
+            crate::Quire::load(dir.path().to_path_buf())
+                .expect("load config")
+                .config
         };
         let env = TestEnv::with_config(config);
         let session = ApiSession::new(3000);