#![expect(clippy::unwrap_used)]
use std::collections::BTreeMap;
use std::path::Path;
use degenbot_cli_core::{
ensure_supported_registrations, run, Command, DatabaseCommand, DatabaseReport, ExchangeCommand,
};
use degenbot_config::MapEnv;
use tempfile::TempDir;
struct NoPrompt;
impl degenbot_cli_core::Prompter for NoPrompt {
fn confirm(&self, _message: &str, _default: bool) -> bool {
false
}
}
fn env() -> MapEnv {
MapEnv::new(BTreeMap::new())
}
fn run_cmd(command: &Command, path: &Path) -> degenbot_cli_core::CommandOutcome {
run(
command,
°enbot_cli_core::CliContext::new(&env()).with_database(path.display().to_string()),
&NoPrompt,
)
}
fn exchange_row(path: &Path, chain_id: i64, name: &str) -> Option<(bool,)> {
let conn = rusqlite::Connection::open(path).unwrap();
conn.query_row(
"SELECT active FROM exchanges WHERE chain_id = ?1 AND name = ?2",
rusqlite::params![chain_id, name],
|r| Ok((r.get(0)?,)),
)
.ok()
}
fn aave_row(path: &Path, chain_id: i64, name: &str) -> Option<(bool, Option<i64>)> {
let conn = rusqlite::Connection::open(path).unwrap();
conn.query_row(
"SELECT active, last_update_block FROM aave_v3_markets WHERE chain_id = ?1 AND name = ?2",
rusqlite::params![chain_id, name],
|r| Ok((r.get(0)?, r.get(1)?)),
)
.ok()
}
fn exchange_count(path: &Path) -> i64 {
let conn = rusqlite::Connection::open(path).unwrap();
conn.query_row("SELECT COUNT(*) FROM exchanges", [], |r| r.get(0))
.unwrap()
}
fn pool_manager_count(path: &Path) -> i64 {
let conn = rusqlite::Connection::open(path).unwrap();
conn.query_row("SELECT COUNT(*) FROM pool_managers", [], |r| r.get(0))
.unwrap()
}
const RETIRED_PAIRS: usize = 17;
#[test]
fn database_reset_registers_supported_exchanges_and_market_inactive() {
let dir = TempDir::new().unwrap();
let db = dir.path().join("degenbot.db");
let outcome = run_cmd(
&Command::Database(DatabaseCommand::Reset { force: true }),
&db,
);
assert_eq!(outcome.exit_code, degenbot_cli_core::ExitCode::Success);
assert!(matches!(
outcome.report(),
Some(degenbot_cli_core::CommandReport::Database(
DatabaseReport::Reset { .. }
))
));
assert_eq!(
exchange_count(&db),
i64::try_from(RETIRED_PAIRS).unwrap(),
"all 17 supported (chain, dex) pairs registered"
);
for (chain, name) in [
(8453, "aerodrome_v2"),
(1, "uniswap_v4"),
(1, "pancakeswap_v3"),
] {
let (active,) = exchange_row(&db, chain, name).unwrap();
assert!(!active, "{name} must register inactive");
}
assert_eq!(pool_manager_count(&db), 2, "both V4 singletons registered");
let (active, stamp) = aave_row(&db, 1, "Aave Ethereum Market").unwrap();
assert!(!active);
assert_eq!(stamp, None, "bare registration carries no bootstrap stamp");
let outcome = run_cmd(
&Command::Exchange(ExchangeCommand::List { chain: None }),
&db,
);
let lines = outcome.report().unwrap().render_lines();
assert!(lines
.iter()
.any(|l| l == "Aerodrome V2 on Base (chain ID 8453): inactive"));
assert!(!lines.iter().any(|l| l.contains("not in database")));
}
#[test]
fn ensure_is_idempotent_and_activation_flips_without_creating() {
let dir = TempDir::new().unwrap();
let db = dir.path().join("degenbot.db");
let first = ensure_supported_registrations(&db).unwrap();
assert_eq!(first.exchanges, RETIRED_PAIRS);
assert_eq!(first.aave_markets, 1);
let second = ensure_supported_registrations(&db).unwrap();
assert_eq!(second.exchanges, 0, "idempotent: nothing new registered");
assert_eq!(second.aave_markets, 0, "idempotent: nothing new registered");
let outcome = run_cmd(
&Command::Exchange(ExchangeCommand::Activate {
chain: "ethereum".to_string(),
name: "uniswap_v2".to_string(),
}),
&db,
);
assert_eq!(outcome.exit_code, degenbot_cli_core::ExitCode::Success);
let (active,) = exchange_row(&db, 1, "uniswap_v2").unwrap();
assert!(active);
assert_eq!(
exchange_count(&db),
i64::try_from(RETIRED_PAIRS).unwrap(),
"no duplicate rows"
);
}
#[test]
fn registered_bare_market_has_no_contract_substrate() {
let dir = TempDir::new().unwrap();
let db = dir.path().join("degenbot.db");
ensure_supported_registrations(&db).unwrap();
let conn = rusqlite::Connection::open(&db).unwrap();
let market_id: i64 = conn
.query_row(
"SELECT id FROM aave_v3_markets WHERE chain_id = 1 AND name = 'Aave Ethereum Market'",
[],
|r| r.get(0),
)
.unwrap();
let contracts: i64 = conn
.query_row(
"SELECT COUNT(*) FROM aave_v3_contracts WHERE market_id = ?1",
rusqlite::params![market_id],
|r| r.get(0),
)
.unwrap();
assert_eq!(
contracts, 0,
"the bare registration carries no contract substrate"
);
}