use crate::db::TempDb;
use omry_archiving::{Document, Marshal, Record, RecordParams, ToRecord};
use std::env;
use std::fs::{self, File};
use std::io::prelude::*;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, TcpListener, ToSocketAddrs};
use std::path::{Path, PathBuf};
use tempfile::tempdir;
use tracing::Level;
use tracing_subscriber::FmtSubscriber;
const WORKSPACE_DIR_KEY: &str = "CARGO_WORKSPACE_DIR";
const TEST_ARCHIVE_SUBPATH: &str = "test/data/example.bincode";
const FIND_PORT_MAX_RETRIES: u8 = 5;
fn free_port_from_socket_addr(
socket: impl ToSocketAddrs,
max_attempts: u8,
) -> anyhow::Result<(TcpListener, u16)> {
let mut attempts_left = max_attempts;
let socket_addrs = socket.to_socket_addrs()?.collect::<Vec<_>>();
while attempts_left > 0 {
if let Ok(listener) = TcpListener::bind(socket_addrs.as_slice()) {
let port = listener.local_addr()?.port();
return Ok((listener, port));
}
attempts_left -= 1;
}
anyhow::bail!("Couldn't bind a listener to a port in {max_attempts} attempts.");
}
#[must_use]
pub fn free_port_ipv4() -> (TcpListener, u16) {
let socket = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0);
#[allow(clippy::expect_used)]
free_port_from_socket_addr(socket, FIND_PORT_MAX_RETRIES).expect("Couldn't find a free port.")
}
#[must_use]
pub fn free_port_ipv6() -> (TcpListener, u16) {
let socket = SocketAddrV6::new(Ipv6Addr::LOCALHOST, 0, 0, 0);
#[allow(clippy::expect_used)]
free_port_from_socket_addr(socket, FIND_PORT_MAX_RETRIES).expect("Couldn't find a free port.")
}
fn make_test_archive_holder() -> anyhow::Result<Document> {
let (_, workspace_dir_path) = env::vars()
.find(|(k, _)| k == WORKSPACE_DIR_KEY)
.ok_or_else(|| anyhow::anyhow!("{WORKSPACE_DIR_KEY} is not set"))?;
let workspace_dir_path = fs::canonicalize(workspace_dir_path)?;
let test_archive_path = workspace_dir_path.join(TEST_ARCHIVE_SUBPATH);
let mut file = File::open(test_archive_path)?;
let mut buf = vec![];
file.read_to_end(&mut buf)?;
let archive_holder = Document::from_bytes(&buf)?;
Ok(archive_holder)
}
pub fn make_test_record_params() -> anyhow::Result<RecordParams> {
let document = make_test_archive_holder()?;
let url = "https://example.org/".to_string();
let title = "Example".to_string();
let client_datetime = "2024-10-11T13:49:46-05:00".to_string();
let language = Some("en".to_string());
Ok(RecordParams {
id: None,
url,
title,
language,
client_datetime,
document,
timestamp_flora: None,
})
}
pub fn make_test_record() -> anyhow::Result<Record> {
let incoming_record = Record::new(make_test_record_params()?);
Ok(incoming_record)
}
pub async fn make_test_db_records(how_many: usize) -> anyhow::Result<Vec<impl ToRecord>> {
let db = TempDb::new().await?;
let mut db_records = Vec::with_capacity(how_many);
for _ in 0..how_many {
let record = make_test_record()?;
let row_id = db.insert(record.try_into()?).await?;
let db_record = db.get_record(row_id).await?;
db_records.push(db_record);
}
Ok(db_records)
}
#[derive(Debug)]
pub struct TempPathWithString {
path: PathBuf,
path_string: String,
}
impl TempPathWithString {
pub fn new() -> anyhow::Result<Self> {
let temp_path = tempdir()?.keep();
#[allow(clippy::unnecessary_debug_formatting)]
let temp_path_string = temp_path
.to_str()
.ok_or_else(|| anyhow::anyhow!("Couldn't get a path string from {temp_path:?}"))?
.to_string();
Ok(Self {
path: temp_path,
path_string: temp_path_string,
})
}
#[must_use]
pub fn as_path(&self) -> &Path {
&self.path
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.path_string
}
}
pub fn init_tracing_subscriber() -> anyhow::Result<()> {
let subscriber = FmtSubscriber::builder()
.with_max_level(Level::INFO)
.finish();
tracing::subscriber::set_global_default(subscriber)?;
Ok(())
}