pub use sn_interface::test_utils::TestSectionTree;
use crate::{Safe, SafeUrl};
use sn_client::utils::test_utils::read_genesis_dbc_from_first_node;
use sn_dbc::{rng, Dbc, Owner, OwnerOnce, Token};
use sn_interface::types::Keypair;
use anyhow::{anyhow, bail, Context, Result};
use async_once::AsyncOnce;
use bls::SecretKey;
use lazy_static::lazy_static;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use std::{collections::HashMap, env::var, ops::Index, sync::Once};
use tokio::sync::Mutex;
use tracing_subscriber::{fmt, EnvFilter};
const TEST_AUTH_CREDENTIALS: &str = "TEST_AUTH_CREDENTIALS";
const NUM_OF_DBCS_TO_REISSUE: usize = 40;
const REISSUED_DBC_MIN_BALANCE: u64 = 50_000_000_000;
const REISSUED_DBC_MAX_BALANCE: u64 = 100_000_000_000;
lazy_static! {
pub static ref GENESIS_DBC: Dbc = match read_genesis_dbc_from_first_node() {
Ok(dbc) => dbc,
Err(err) => panic!("Failed to read genesis DBC for tests: {err:?}"),
};
}
fn init_logger() {
static INIT_LOGGER: Once = Once::new();
INIT_LOGGER.call_once(|| {
fmt()
.with_ansi(false)
.with_env_filter(EnvFilter::from_default_env())
.with_target(false)
.init()
});
}
pub async fn get_next_bearer_dbc() -> Result<(Dbc, Token)> {
lazy_static! {
static ref NEXT_DBC_INDEX: Mutex<usize> = Mutex::new(0);
static ref REISSUED_DBCS: AsyncOnce<Vec<(Dbc, Token)>> = AsyncOnce::new(async {
match reissue_bearer_dbcs().await {
Ok(dbcs) => dbcs,
Err(err) => panic!("Failed to reissue DBCs from genesis DBC: {err:?}"),
}
});
}
let mut index = NEXT_DBC_INDEX.lock().await;
let next_dbc = REISSUED_DBCS
.get()
.await
.get(*index)
.ok_or_else(|| anyhow!("No more unused DBCs available, already used: {}", index))?
.clone();
*index += 1;
Ok(next_dbc)
}
async fn reissue_bearer_dbcs() -> Result<Vec<(Dbc, Token)>> {
let total_balance = match GENESIS_DBC.amount_secrets_bearer() {
Ok(amount_secrets) => amount_secrets.amount().as_nano(),
Err(err) => bail!("Failed to obtain genesis DBC balance: {:?}", err),
};
let mut rng = rand::thread_rng();
let amounts: Vec<u64> = (0..NUM_OF_DBCS_TO_REISSUE)
.map(|_| rng.gen_range(REISSUED_DBC_MIN_BALANCE..REISSUED_DBC_MAX_BALANCE))
.collect();
let total_output_amount: u64 = amounts.iter().sum();
let change_amount = total_balance - total_output_amount;
let output_amounts: Vec<(Token, OwnerOnce)> = amounts
.into_iter()
.map(|amount| {
let owner = Owner::from_random_secret_key(&mut rng::thread_rng());
let output_owner = OwnerOnce::from_owner_base(owner, &mut rng::thread_rng());
(Token::from_nano(amount), output_owner)
})
.collect();
let safe = new_safe_instance().await?;
let (output_dbcs, _) = safe
.reissue_dbcs(
vec![GENESIS_DBC.clone()],
output_amounts,
Token::from_nano(change_amount),
)
.await?;
Ok(output_dbcs
.into_iter()
.map(|(dbc, _, amount_secrets)| {
let amount = amount_secrets.amount();
(dbc, amount)
})
.collect())
}
pub async fn new_safe_instance_with_dbc() -> Result<(Safe, Dbc, Token)> {
let (dbc, balance) = get_next_bearer_dbc().await?;
let safe = new_safe_instance().await?;
Ok((safe, dbc, balance))
}
pub struct TestDataFilesContainer {
pub url: SafeUrl,
pub files_map: HashMap<String, SafeUrl>,
}
impl TestDataFilesContainer {
pub async fn get_container<'a>(
files: impl IntoIterator<Item = &'a str>,
) -> Result<TestDataFilesContainer> {
let mut map: HashMap<String, SafeUrl> = HashMap::new();
let safe = new_safe_instance().await?;
let (container_xorurl, _, files_map) = safe
.files_container_create_from("./testdata", None, false, false)
.await?;
let container_url = SafeUrl::from_url(&container_xorurl)?;
for file in files {
let file_info = files_map
.get(file)
.ok_or_else(|| anyhow!(format!("could not retrieve {file} from files map")))?;
let file_link = file_info
.get("link")
.ok_or_else(|| anyhow!("could not retrieve file link"))?;
let file_url = SafeUrl::from_url(file_link)?;
map.insert(file.to_string(), file_url);
}
Ok(TestDataFilesContainer {
url: container_url,
files_map: map,
})
}
}
impl Index<&str> for TestDataFilesContainer {
type Output = SafeUrl;
fn index(&self, file_path: &str) -> &Self::Output {
match self.files_map.get(file_path) {
Some(url) => url,
None => panic!("cannot find file in files map"),
}
}
}
pub async fn new_safe_instance() -> Result<Safe> {
init_logger();
let credentials = match var(TEST_AUTH_CREDENTIALS) {
Ok(val) => serde_json::from_str(&val).with_context(|| {
format!("Failed to parse credentials read from {TEST_AUTH_CREDENTIALS} env var",)
})?,
Err(_) => Keypair::new_ed25519(),
};
let safe = Safe::connected(Some(credentials), None, None, None).await?;
Ok(safe)
}
pub async fn new_safe_instance_with_dbc_owner(secret_key: &str) -> Result<(Safe, Owner)> {
init_logger();
let credentials = match var(TEST_AUTH_CREDENTIALS) {
Ok(val) => serde_json::from_str(&val).with_context(|| {
format!("Failed to parse credentials read from {TEST_AUTH_CREDENTIALS} env var",)
})?,
Err(_) => Keypair::new_ed25519(),
};
let sk: SecretKey = bincode::deserialize(secret_key.as_bytes())
.with_context(|| "Failed to deserialize secret key for DBC owner")?;
let dbc_owner = Owner::from(sk);
let safe = Safe::connected(Some(credentials), None, None, Some(dbc_owner.clone())).await?;
Ok((safe, dbc_owner))
}
pub async fn new_read_only_safe_instance() -> Result<Safe> {
init_logger();
let safe = Safe::connected(None, None, None, None).await?;
Ok(safe)
}
pub fn random_nrs_name() -> String {
thread_rng()
.sample_iter(&Alphanumeric)
.take(15)
.map(char::from)
.collect()
}