Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ jobs:
- name: Prettier
run: pnpm prettier:check

- name: Lint
run: pnpm lint

- name: Frontend Tests
run: pnpm test

- name: Install GTK
run: sudo apt-get update && sudo apt-get install libgtk-3-dev libjavascriptcoregtk-4.1-dev libwebkit2gtk-4.1-dev

Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

68 changes: 68 additions & 0 deletions crates/sage-config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,71 @@ impl Default for RpcConfig {
}
}
}

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

#[test]
fn config_defaults() {
let config = Config::default();
assert_eq!(config.version, 2);
assert_eq!(config.global.log_level, "INFO");
assert!(config.global.fingerprint.is_none());
assert_eq!(config.network.default_network, "mainnet");
assert_eq!(config.network.target_peers, 5);
assert!(config.network.discover_peers);
assert!(!config.rpc.enabled);
assert_eq!(config.rpc.port, 9257);
}

#[test]
fn config_toml_round_trip() {
let config = Config::default();
let toml_str = toml::to_string(&config).unwrap();
let parsed: Config = toml::from_str(&toml_str).unwrap();
assert_eq!(config, parsed);
}

#[test]
fn config_partial_deserialize() {
// Only specify a few fields, rest should use defaults
let toml_str = r#"
version = 2

[global]
log_level = "DEBUG"
"#;
let config: Config = toml::from_str(toml_str).unwrap();
assert_eq!(config.global.log_level, "DEBUG");
assert_eq!(config.network.default_network, "mainnet"); // default
assert!(!config.rpc.enabled); // default
}

#[test]
fn config_with_fingerprint() {
let toml_str = r#"
version = 2

[global]
log_level = "INFO"
fingerprint = 12345

[network]
default_network = "testnet11"
target_peers = 3
discover_peers = false

[rpc]
enabled = true
port = 8080
"#;
let config: Config = toml::from_str(toml_str).unwrap();
assert_eq!(config.global.fingerprint, Some(12345));
assert_eq!(config.network.default_network, "testnet11");
assert_eq!(config.network.target_peers, 3);
assert!(!config.network.discover_peers);
assert!(config.rpc.enabled);
assert_eq!(config.rpc.port, 8080);
}
}
119 changes: 119 additions & 0 deletions crates/sage-config/src/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,122 @@ pub static TESTNET11: LazyLock<Network> = LazyLock::new(|| Network {
additional_peer_introducers: vec!["introducer-testnet11.chia.net".to_string()],
inherit: Some(InheritedNetwork::Testnet11),
});

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

#[test]
fn mainnet_defaults() {
assert_eq!(MAINNET.name, "mainnet");
assert_eq!(MAINNET.ticker, "XCH");
assert_eq!(MAINNET.default_port, 8444);
assert_eq!(MAINNET.precision, 12);
assert!(MAINNET.prefix.is_none());
assert!(MAINNET.network_id.is_none());
assert!(MAINNET.agg_sig_me.is_none());
}

#[test]
fn testnet11_defaults() {
assert_eq!(TESTNET11.name, "testnet11");
assert_eq!(TESTNET11.ticker, "TXCH");
assert_eq!(TESTNET11.default_port, 58444);
assert_eq!(TESTNET11.precision, 12);
}

#[test]
fn prefix_fallback_to_lowercase_ticker() {
assert_eq!(MAINNET.prefix(), "xch");
assert_eq!(TESTNET11.prefix(), "txch");
}

#[test]
fn prefix_custom_override() {
let mut network = MAINNET.clone();
network.prefix = Some("custom".to_string());
assert_eq!(network.prefix(), "custom");
}

#[test]
fn network_id_fallback_to_name() {
assert_eq!(MAINNET.network_id(), "mainnet");
assert_eq!(TESTNET11.network_id(), "testnet11");
}

#[test]
fn network_id_custom_override() {
let mut network = MAINNET.clone();
network.network_id = Some("custom-id".to_string());
assert_eq!(network.network_id(), "custom-id");
}

#[test]
fn agg_sig_me_fallback_to_genesis_challenge() {
assert_eq!(MAINNET.agg_sig_me(), MAINNET.genesis_challenge);
}

#[test]
fn agg_sig_me_custom_override() {
let custom = Bytes32::new([42; 32]);
let mut network = MAINNET.clone();
network.agg_sig_me = Some(custom);
assert_eq!(network.agg_sig_me(), custom);
}

#[test]
fn by_name_finds_mainnet() {
let list = NetworkList::default();
let found = list.by_name("mainnet");
assert!(found.is_some());
assert_eq!(found.unwrap().ticker, "XCH");
}

#[test]
fn by_name_finds_testnet11() {
let list = NetworkList::default();
let found = list.by_name("testnet11");
assert!(found.is_some());
assert_eq!(found.unwrap().ticker, "TXCH");
}

#[test]
fn by_name_returns_none_for_unknown() {
let list = NetworkList::default();
assert!(list.by_name("unknown_network").is_none());
}

#[test]
fn dns_introducers_inherited_from_mainnet() {
let network = Network {
additional_dns_introducers: Vec::new(),
..MAINNET.clone()
};
let introducers = network.dns_introducers();
assert!(!introducers.is_empty());
assert!(introducers.contains(&"dns-introducer.chia.net".to_string()));
}

#[test]
fn dns_introducers_no_inheritance() {
let mut network = MAINNET.clone();
network.inherit = None;
network.additional_dns_introducers = vec!["custom.dns".to_string()];
let introducers = network.dns_introducers();
assert_eq!(introducers, vec!["custom.dns".to_string()]);
}

#[test]
fn dns_introducers_merged_without_duplicates() {
let network = Network {
additional_dns_introducers: vec!["dns-introducer.chia.net".to_string()],
..MAINNET.clone()
};
let introducers = network.dns_introducers();
let count = introducers
.iter()
.filter(|i| *i == "dns-introducer.chia.net")
.count();
assert_eq!(count, 1, "Should not have duplicate introducers");
}
}
117 changes: 117 additions & 0 deletions crates/sage-config/src/old.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,120 @@ pub fn migrate_networks(old: IndexMap<String, OldNetwork>) -> NetworkList {
.collect(),
}
}

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

#[test]
fn old_config_default_is_v1() {
let old = OldConfig::default();
assert!(old.is_old());
}

#[test]
fn migrate_config_basic() {
let old = OldConfig::default();
let (config, wallet_config) = migrate_config(old).unwrap();

assert_eq!(config.version, 2);
assert_eq!(config.global.log_level, "INFO");
assert!(config.global.fingerprint.is_none());
assert_eq!(config.network.default_network, "mainnet");
assert_eq!(config.network.target_peers, 5);
assert!(config.network.discover_peers);
assert!(!config.rpc.enabled);
assert_eq!(config.rpc.port, 9257);
assert!(wallet_config.wallets.is_empty());
}

#[test]
fn migrate_config_with_fingerprint_and_wallets() {
let mut old = OldConfig::default();
old.app.active_fingerprint = Some(12345);
old.app.log_level = "DEBUG".to_string();
old.rpc.run_on_startup = true;
old.rpc.server_port = 8080;
old.network.network_id = "testnet11".to_string();
old.wallets.insert(
"67890".to_string(),
OldWalletConfig {
name: "My Wallet".to_string(),
..OldWalletConfig::default()
},
);

let (config, wallet_config) = migrate_config(old).unwrap();

assert_eq!(config.global.fingerprint, Some(12345));
assert_eq!(config.global.log_level, "DEBUG");
assert!(config.rpc.enabled);
assert_eq!(config.rpc.port, 8080);
assert_eq!(config.network.default_network, "testnet11");
assert_eq!(wallet_config.wallets.len(), 1);
assert_eq!(wallet_config.wallets[0].fingerprint, 67890);
assert_eq!(wallet_config.wallets[0].name, "My Wallet");
}

#[test]
fn migrate_config_invalid_fingerprint_key() {
let mut old = OldConfig::default();
old.wallets.insert(
"not_a_number".to_string(),
OldWalletConfig::default(),
);
let result = migrate_config(old);
assert!(result.is_err());
}

#[test]
fn migrate_networks_mainnet_inherits() {
let genesis = Bytes32::new([1; 32]);
let mut networks = IndexMap::new();
networks.insert(
"mainnet".to_string(),
OldNetwork {
default_port: 8444,
ticker: "XCH".to_string(),
address_prefix: "xch".to_string(),
precision: 12,
genesis_challenge: genesis,
agg_sig_me: genesis, // same as genesis → should become None
dns_introducers: vec!["dns.example.com".to_string()],
},
);

let result = migrate_networks(networks);
assert_eq!(result.networks.len(), 1);
let net = &result.networks[0];
assert_eq!(net.name, "mainnet");
assert!(net.prefix.is_none()); // matches lowercase ticker
assert!(net.agg_sig_me.is_none()); // matches genesis
assert!(matches!(net.inherit, Some(InheritedNetwork::Mainnet)));
}

#[test]
fn migrate_networks_custom_prefix_preserved() {
let genesis = Bytes32::new([2; 32]);
let agg = Bytes32::new([3; 32]);
let mut networks = IndexMap::new();
networks.insert(
"custom".to_string(),
OldNetwork {
default_port: 9999,
ticker: "CUST".to_string(),
address_prefix: "mycustom".to_string(), // doesn't match "cust"
precision: 6,
genesis_challenge: genesis,
agg_sig_me: agg, // different from genesis
dns_introducers: vec![],
},
);

let result = migrate_networks(networks);
let net = &result.networks[0];
assert_eq!(net.prefix, Some("mycustom".to_string()));
assert_eq!(net.agg_sig_me, Some(agg));
assert!(net.inherit.is_none()); // not mainnet or testnet11
}
}
5 changes: 5 additions & 0 deletions crates/sage-database/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,8 @@ sqlx = { workspace = true, features = ["sqlite"] }
thiserror = { workspace = true }
tracing = { workspace = true }
hex = { workspace = true }

[dev-dependencies]
anyhow = { workspace = true }
sqlx = { workspace = true, features = ["runtime-tokio", "sqlite"] }
tokio = { workspace = true }
2 changes: 2 additions & 0 deletions crates/sage-database/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
mod maintenance;
mod serialized_primitives;
mod tables;
#[cfg(test)]
pub(crate) mod test_utils;
mod utils;

pub use maintenance::*;
Expand Down
Loading