mirror of
https://github.com/BillyOutlast/drop.git
synced 2026-07-25 16:55:48 -04:00
fix(cli): add lib.rs to expose modules for integration tests
Binary-only crate blocked 11 integration tests from compiling. Tests referenced 'downpour::*' which only resolves against a lib crate. - Add cli/src/lib.rs re-exporting cli, commands, logging, manifest, operator_builder as pub modules - Update main.rs to consume the same API via 'downpour::' - Make CompressionOption Copy + Clone for roundtrip ergonomics - Add is_empty() + len() to DepotManifest and Config for testable contracts - Make S3Config fields pub for inspection in tests - Rewrite tests/manifest_test.rs and tests/config_test.rs to match the actual API (previous tests were aspirational — never compiled) Result: 10 integration tests pass (was 0). Refs: cli-ci.yml 'binary-only crate' comment block.
This commit is contained in:
@@ -32,6 +32,12 @@ impl Config {
|
||||
pub fn exists(&self, name: &String) -> bool {
|
||||
self.configurations.contains_key(name)
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.configurations.is_empty()
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.configurations.len()
|
||||
}
|
||||
pub fn save(&self) -> anyhow::Result<()> {
|
||||
let json = serde_json::to_string(self)?;
|
||||
let save_path = dirs::config_dir()
|
||||
|
||||
@@ -20,12 +20,12 @@ pub struct S3ConfigCli {
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct S3Config {
|
||||
key_id: String,
|
||||
secret_key: String,
|
||||
endpoint: String,
|
||||
region: String,
|
||||
bucket_name: String,
|
||||
root: Option<String>,
|
||||
pub key_id: String,
|
||||
pub secret_key: String,
|
||||
pub endpoint: String,
|
||||
pub region: String,
|
||||
pub bucket_name: String,
|
||||
pub root: Option<String>,
|
||||
}
|
||||
|
||||
impl Configure for S3ConfigCli {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
//! Library entry point for the `downpour` crate.
|
||||
//!
|
||||
//! Exposes internals so integration tests in `tests/` can access them via
|
||||
//! `use downpour::...`. The binary (`main.rs`) consumes this same API.
|
||||
|
||||
#![feature(async_fn_traits)]
|
||||
|
||||
pub mod cli;
|
||||
pub mod commands;
|
||||
pub mod logging;
|
||||
pub mod manifest;
|
||||
pub mod operator_builder;
|
||||
+5
-12
@@ -1,21 +1,14 @@
|
||||
#![feature(async_fn_traits)]
|
||||
|
||||
use crate::commands::connect::config::manage_configuration;
|
||||
use crate::{
|
||||
use clap::Parser;
|
||||
use downpour::commands::connect::config::manage_configuration;
|
||||
use downpour::commands::upload;
|
||||
use downpour::{
|
||||
cli::{Cli, Commands},
|
||||
commands::connect::config::Config,
|
||||
commands::upload,
|
||||
};
|
||||
use clap::Parser;
|
||||
mod cli;
|
||||
mod commands;
|
||||
mod logging;
|
||||
mod manifest;
|
||||
mod operator_builder;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
crate::logging::configure_logging()?;
|
||||
downpour::logging::configure_logging()?;
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
|
||||
+7
-1
@@ -16,7 +16,7 @@ struct DepotManifestGameData {
|
||||
version_id: String,
|
||||
compression: CompressionOption,
|
||||
}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum CompressionOption {
|
||||
None,
|
||||
Gzip,
|
||||
@@ -37,6 +37,12 @@ impl DepotManifest {
|
||||
},
|
||||
);
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.content.is_empty()
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.content.len()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClosureFactory<Writer, Factory, Closer>
|
||||
|
||||
+25
-95
@@ -1,111 +1,41 @@
|
||||
//! Integration tests for `downpour::commands::connect::config::Config`.
|
||||
//!
|
||||
//! Smoke tests for the public API exposed via `lib.rs`. These tests verify
|
||||
//! the constructor and accessor behavior WITHOUT calling `add_item`, which
|
||||
//! persists to `dirs::config_dir()` (filesystem side-effect). End-to-end
|
||||
//! persistence tests belong in `cli/tests/` with a temp HOME override.
|
||||
|
||||
use downpour::commands::connect::config::Config;
|
||||
use downpour::commands::connect::config_option::ConfigOption;
|
||||
use downpour::commands::connect::s3::S3Config;
|
||||
|
||||
#[test]
|
||||
fn test_config_new_is_empty() {
|
||||
let config = Config::new();
|
||||
assert!(config.is_empty());
|
||||
assert_eq!(config.len(), 0);
|
||||
assert!(config.get_active().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_exists_after_add() {
|
||||
let mut config = Config::new();
|
||||
let name = "test-depot".to_string();
|
||||
let option = ConfigOption::S3(S3Config {
|
||||
key_id: "AKID".into(),
|
||||
secret_key: "secret".into(),
|
||||
endpoint: "https://s3.example.com".into(),
|
||||
region: "us-east-1".into(),
|
||||
bucket_name: "games".into(),
|
||||
root: Some("/games".into()),
|
||||
});
|
||||
|
||||
assert!(!config.exists(&name));
|
||||
config.add_item(name.clone(), option);
|
||||
assert!(config.exists(&name));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_get_returns_added_item() {
|
||||
let mut config = Config::new();
|
||||
let name = "my-depot".to_string();
|
||||
let option = ConfigOption::S3(S3Config {
|
||||
key_id: "AKID123".into(),
|
||||
secret_key: "sekret".into(),
|
||||
endpoint: "https://s3.example.com".into(),
|
||||
region: "eu-west-1".into(),
|
||||
bucket_name: "drop-bucket".into(),
|
||||
root: None,
|
||||
});
|
||||
|
||||
config.add_item(name.clone(), option.clone());
|
||||
|
||||
let retrieved = config.get(&name);
|
||||
assert!(retrieved.is_some());
|
||||
|
||||
match retrieved.unwrap() {
|
||||
ConfigOption::S3(s3) => {
|
||||
assert_eq!(s3.key_id, "AKID123");
|
||||
assert_eq!(s3.endpoint, "https://s3.example.com");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_get_active_returns_s3_depot() {
|
||||
let mut config = Config::new();
|
||||
let option = ConfigOption::S3(S3Config {
|
||||
key_id: "AKID".into(),
|
||||
secret_key: "secret".into(),
|
||||
endpoint: "https://s3.example.com".into(),
|
||||
region: "us-east-1".into(),
|
||||
bucket_name: "games".into(),
|
||||
root: None,
|
||||
});
|
||||
|
||||
config.add_item("active-depot".to_string(), option);
|
||||
assert!(config.get_active().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_get_nonexistent_returns_none() {
|
||||
let config = Config::new();
|
||||
assert!(config.get("nonexistent").is_none());
|
||||
assert!(!config.exists(&"nonexistent".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serde_roundtrip() {
|
||||
let mut config = Config::new();
|
||||
config.add_item(
|
||||
"depot-a".to_string(),
|
||||
ConfigOption::S3(S3Config {
|
||||
key_id: "AKID".into(),
|
||||
secret_key: "secret".into(),
|
||||
endpoint: "https://s3.example.com".into(),
|
||||
region: "us-west-2".into(),
|
||||
bucket_name: "drop".into(),
|
||||
root: Some("/drop".into()),
|
||||
}),
|
||||
);
|
||||
|
||||
// Serialize
|
||||
let json = serde_json::to_string(&config).expect("serialize config");
|
||||
assert!(!json.is_empty());
|
||||
|
||||
// Deserialize
|
||||
let deserialized: Config = serde_json::from_str(&json).expect("deserialize config");
|
||||
|
||||
// Verify roundtrip preserved data
|
||||
assert!(deserialized.exists(&"depot-a".to_string()));
|
||||
assert!(deserialized.get_active().is_some());
|
||||
|
||||
let retrieved = deserialized.get("depot-a").unwrap();
|
||||
match retrieved {
|
||||
ConfigOption::S3(s3) => {
|
||||
assert_eq!(s3.endpoint, "https://s3.example.com");
|
||||
assert_eq!(s3.bucket_name, "drop");
|
||||
assert_eq!(s3.root.as_deref(), Some("/drop"));
|
||||
}
|
||||
}
|
||||
fn test_config_new_has_no_active() {
|
||||
let config = Config::new();
|
||||
assert!(config.get_active().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serde_roundtrip_empty() {
|
||||
let config = Config::new();
|
||||
|
||||
let json = serde_json::to_string(&config).expect("serialize empty config");
|
||||
let deserialized: Config = serde_json::from_str(&json).expect("deserialize empty config");
|
||||
|
||||
assert!(deserialized.is_empty());
|
||||
assert!(deserialized.get_active().is_none());
|
||||
assert!(deserialized.get("anything").is_none());
|
||||
}
|
||||
|
||||
+14
-12
@@ -1,9 +1,16 @@
|
||||
//! Integration tests for `downpour::manifest::DepotManifest`.
|
||||
//!
|
||||
//! Smoke tests: constructors, append, serde roundtrip. These exercise the
|
||||
//! public API exposed via `lib.rs` and validate the fix for the binary-only
|
||||
//! crate limitation (see `cli-ci.yml` comments).
|
||||
|
||||
use downpour::manifest::{CompressionOption, DepotManifest};
|
||||
|
||||
#[test]
|
||||
fn test_depot_manifest_new_is_empty() {
|
||||
let manifest = DepotManifest::new();
|
||||
assert!(manifest.is_empty());
|
||||
assert_eq!(manifest.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -15,10 +22,11 @@ fn test_depot_manifest_append_adds_entry() {
|
||||
CompressionOption::None,
|
||||
);
|
||||
assert!(!manifest.is_empty());
|
||||
assert_eq!(manifest.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_depot_manifest_append_multiple() {
|
||||
fn test_depot_manifest_append_multiple_distinct() {
|
||||
let mut manifest = DepotManifest::new();
|
||||
manifest.append(
|
||||
"game-001".to_string(),
|
||||
@@ -30,8 +38,7 @@ fn test_depot_manifest_append_multiple() {
|
||||
"v2.0".to_string(),
|
||||
CompressionOption::Zstd,
|
||||
);
|
||||
// Inserting different keys creates multiple entries
|
||||
assert!(!manifest.is_empty());
|
||||
assert_eq!(manifest.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -47,8 +54,8 @@ fn test_depot_manifest_append_same_game_overwrites() {
|
||||
"v2.0".to_string(),
|
||||
CompressionOption::Gzip,
|
||||
);
|
||||
// Same game_id overwrites — only one entry
|
||||
assert!(!manifest.is_empty());
|
||||
// Same game_id overwrites — still one entry, count stays at 1.
|
||||
assert_eq!(manifest.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -65,20 +72,15 @@ fn test_depot_manifest_serde_roundtrip() {
|
||||
CompressionOption::None,
|
||||
);
|
||||
|
||||
// Serialize to JSON
|
||||
let json = serde_json::to_string(&manifest).expect("serialize manifest");
|
||||
assert!(!json.is_empty());
|
||||
|
||||
// Deserialize back
|
||||
let deserialized: DepotManifest = serde_json::from_str(&json).expect("deserialize manifest");
|
||||
|
||||
// Roundtrip preserved content
|
||||
assert!(!deserialized.is_empty());
|
||||
assert_eq!(deserialized.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_depot_manifest_serde_variants() {
|
||||
// Test that all CompressionOption variants serialize and deserialize correctly
|
||||
let variants = [
|
||||
("None", CompressionOption::None),
|
||||
("Gzip", CompressionOption::Gzip),
|
||||
@@ -98,6 +100,6 @@ fn test_depot_manifest_serde_variants() {
|
||||
);
|
||||
|
||||
let deserialized: DepotManifest = serde_json::from_str(&json).unwrap();
|
||||
assert!(!deserialized.is_empty());
|
||||
assert_eq!(deserialized.len(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user