mirror of
https://github.com/LowderPlay/cheburcheck.git
synced 2026-09-22 22:37:59 +03:00
Повышение надёжности проверок, улучшение тестов, микрофиксы (#87)
* test: cover probe verdict combinations - fix the `sni_block` fixture to use a whitelist host - cover `whitelist` for responsive blacklist hosts * fix: handle empty reporter results - return `0.00%` for empty scans - add tests for empty and populated summaries * fix: validate reporter scan options - reject zero values for `--probes` and `--retry-count` - increase read timeout on each retry - cover validation and timeout calculation with tests * refactor: clean up querying code - simplify parsing and collection access - use standard `std::io::Error` helpers - add `Default` for reusable checker types * refactor: simplify reporter code - remove redundant conversions and copies - simplify CSV parsing and HTTP status output * refactor: clean up website code - simplify database access and error conversion - clean up MQTT config parsing
This commit is contained in:
@@ -129,6 +129,12 @@ pub struct AsnCache {
|
||||
cache: Arc<RwLock<HashMap<u32, CachedAsnData>>>,
|
||||
}
|
||||
|
||||
impl Default for AsnCache {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsnCache {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
||||
@@ -142,8 +142,7 @@ impl DatabaseCache {
|
||||
},
|
||||
],
|
||||
};
|
||||
let metadata = serde_json::to_vec_pretty(&metadata)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
let metadata = serde_json::to_vec_pretty(&metadata).map_err(io::Error::other)?;
|
||||
self.write_file(METADATA_FILE, &metadata)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ use async_trait::async_trait;
|
||||
use maxminddb::geoip2::{City, Country};
|
||||
use maxminddb::{MaxMindDbError, geoip2};
|
||||
use serde::Serialize;
|
||||
use std::io;
|
||||
use std::io::Error;
|
||||
use std::net::IpAddr;
|
||||
|
||||
@@ -34,6 +33,12 @@ impl Default for IpInfo {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GeoIp {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl GeoIp {
|
||||
pub fn new() -> Self {
|
||||
GeoIp {
|
||||
@@ -80,11 +85,10 @@ impl GeoIp {
|
||||
|
||||
let country_code = country
|
||||
.as_ref()
|
||||
.map(|c| c.country.iso_code)
|
||||
.flatten()
|
||||
.and_then(|c| c.country.iso_code)
|
||||
.map(|c| c.to_string());
|
||||
|
||||
let city_geo_name_id = city.as_ref().map(|c| c.city.geoname_id).flatten();
|
||||
let city_geo_name_id = city.as_ref().and_then(|c| c.city.geoname_id);
|
||||
|
||||
let mut location = (None, None);
|
||||
if let Some(city) = city {
|
||||
@@ -141,7 +145,6 @@ impl Updatable for GeoIp {
|
||||
}
|
||||
|
||||
async fn install(&mut self, (asn, country, city): Self::Base) -> Result<(), Error> {
|
||||
self.update(asn, country, city)
|
||||
.map_err(|e| Error::new(io::ErrorKind::Other, e))
|
||||
self.update(asn, country, city).map_err(Error::other)
|
||||
}
|
||||
}
|
||||
|
||||
+8
-9
@@ -100,7 +100,7 @@ impl Checker {
|
||||
}
|
||||
};
|
||||
|
||||
let reverse_lookup = if let Some(ip) = ips.get(0).cloned() {
|
||||
let reverse_lookup = if let Some(ip) = ips.first().copied() {
|
||||
match self.resolver.lookup_ptr(ip).await {
|
||||
Ok(ptr) => ptr,
|
||||
Err(e) => {
|
||||
@@ -113,7 +113,7 @@ impl Checker {
|
||||
};
|
||||
|
||||
let geo_ip = self.geo_ip.load();
|
||||
let geo = match ips.get(0).map(|ip| geo_ip.lookup(ip.clone())) {
|
||||
let geo = match ips.first().map(|ip| geo_ip.lookup(*ip)) {
|
||||
None => IpInfo::default(),
|
||||
Some(Ok(ip)) => ip,
|
||||
Some(Err(e)) => {
|
||||
@@ -172,12 +172,11 @@ impl Checker {
|
||||
.collect();
|
||||
|
||||
for prefix in &prefixes {
|
||||
if let Ok(ipnet) = prefix.parse::<IpNet>() {
|
||||
if cdn_list.contains(&ipnet.network()).is_some() {
|
||||
if !blocked_prefixes.contains(prefix) {
|
||||
blocked_prefixes.push(prefix.clone());
|
||||
}
|
||||
}
|
||||
if let Ok(ipnet) = prefix.parse::<IpNet>()
|
||||
&& cdn_list.contains(&ipnet.network()).is_some()
|
||||
&& !blocked_prefixes.contains(prefix)
|
||||
{
|
||||
blocked_prefixes.push(prefix.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +214,7 @@ impl Checker {
|
||||
}
|
||||
|
||||
pub fn last_update(&self) -> Option<DateTime<Utc>> {
|
||||
self.rx.borrow().clone()
|
||||
*self.rx.borrow()
|
||||
}
|
||||
|
||||
pub async fn download_all() -> Result<Bases, io::Error> {
|
||||
|
||||
+41
-29
@@ -14,6 +14,12 @@ pub struct CdnList {
|
||||
trie: IpnetTrie<NetworkRecord>,
|
||||
}
|
||||
|
||||
impl Default for CdnList {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, Eq, PartialEq, Hash)]
|
||||
pub struct NetworkRecord {
|
||||
pub provider: String,
|
||||
@@ -97,6 +103,12 @@ pub struct RuBlacklist {
|
||||
pub domain_count: usize,
|
||||
}
|
||||
|
||||
impl Default for RuBlacklist {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl RuBlacklist {
|
||||
pub fn new() -> RuBlacklist {
|
||||
RuBlacklist {
|
||||
@@ -176,6 +188,35 @@ impl RuBlacklist {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Updatable for RuBlacklist {
|
||||
type Base = (VecDeque<u8>, VecDeque<u8>, VecDeque<u8>);
|
||||
|
||||
async fn download() -> Result<Self::Base, Error> {
|
||||
Ok((
|
||||
VecDeque::from(
|
||||
fetch_db(Self::get_url(
|
||||
"RKN_NETS",
|
||||
"https://antifilter.network/download/ipsum.lst",
|
||||
))
|
||||
.await?,
|
||||
),
|
||||
VecDeque::from(
|
||||
fetch_db(Self::get_url(
|
||||
"RKN_DOMAINS",
|
||||
"https://antifilter.download/list/domains.lst",
|
||||
))
|
||||
.await?,
|
||||
),
|
||||
VecDeque::from(include_bytes!("../dist-domains.txt").to_vec()),
|
||||
))
|
||||
}
|
||||
|
||||
async fn install(&mut self, (nets, domains, custom_domains): Self::Base) -> Result<(), Error> {
|
||||
self.update(nets, domains, custom_domains)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::RuBlacklist;
|
||||
@@ -205,32 +246,3 @@ mod tests {
|
||||
assert_eq!(list.contains_domain("notblocked.example"), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Updatable for RuBlacklist {
|
||||
type Base = (VecDeque<u8>, VecDeque<u8>, VecDeque<u8>);
|
||||
|
||||
async fn download() -> Result<Self::Base, Error> {
|
||||
Ok((
|
||||
VecDeque::from(
|
||||
fetch_db(Self::get_url(
|
||||
"RKN_NETS",
|
||||
"https://antifilter.network/download/ipsum.lst",
|
||||
))
|
||||
.await?,
|
||||
),
|
||||
VecDeque::from(
|
||||
fetch_db(Self::get_url(
|
||||
"RKN_DOMAINS",
|
||||
"https://antifilter.download/list/domains.lst",
|
||||
))
|
||||
.await?,
|
||||
),
|
||||
VecDeque::from(include_bytes!("../dist-domains.txt").to_vec()),
|
||||
))
|
||||
}
|
||||
|
||||
async fn install(&mut self, (nets, domains, custom_domains): Self::Base) -> Result<(), Error> {
|
||||
self.update(nets, domains, custom_domains)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use hickory_resolver::net::runtime::TokioRuntimeProvider;
|
||||
use hickory_resolver::net::{DnsError, NetError};
|
||||
use hickory_resolver::proto::ProtoError;
|
||||
use hickory_resolver::proto::rr::RData;
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::io::Error;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
@@ -93,6 +93,6 @@ fn map_resolve_error(error: NetError) -> ResolveError {
|
||||
{
|
||||
ResolveError::NxDomain
|
||||
}
|
||||
_ => ResolveError::Other(Error::new(ErrorKind::Other, error)),
|
||||
_ => ResolveError::Other(Error::other(error)),
|
||||
}
|
||||
}
|
||||
|
||||
+22
-21
@@ -17,26 +17,27 @@ pub enum Target {
|
||||
|
||||
impl From<&str> for Target {
|
||||
fn from(input: &str) -> Self {
|
||||
if input.to_lowercase().starts_with("as") {
|
||||
if let Ok(asn) = input[2..].parse::<u32>() {
|
||||
if asn >= 1 {
|
||||
return Target::Asn(asn);
|
||||
}
|
||||
}
|
||||
if input
|
||||
.get(..2)
|
||||
.is_some_and(|prefix| prefix.eq_ignore_ascii_case("as"))
|
||||
&& let Ok(asn) = input[2..].parse::<u32>()
|
||||
&& asn >= 1
|
||||
{
|
||||
return Target::Asn(asn);
|
||||
}
|
||||
|
||||
if input.contains('/') {
|
||||
if let Ok(ipv4_net) = input.parse::<Ipv4Net>() {
|
||||
if ipv4_net.prefix_len() >= 8 {
|
||||
return Target::Ipv4Subnet(ipv4_net);
|
||||
}
|
||||
}
|
||||
if input.contains('/')
|
||||
&& let Ok(ipv4_net) = input.parse::<Ipv4Net>()
|
||||
&& ipv4_net.prefix_len() >= 8
|
||||
{
|
||||
return Target::Ipv4Subnet(ipv4_net);
|
||||
}
|
||||
|
||||
if let Ok(ipv6_net) = input.parse::<Ipv6Net>() {
|
||||
if ipv6_net.prefix_len() >= 32 {
|
||||
return Target::Ipv6Subnet(ipv6_net);
|
||||
}
|
||||
}
|
||||
if input.contains('/')
|
||||
&& let Ok(ipv6_net) = input.parse::<Ipv6Net>()
|
||||
&& ipv6_net.prefix_len() >= 32
|
||||
{
|
||||
return Target::Ipv6Subnet(ipv6_net);
|
||||
}
|
||||
|
||||
if let Ok(ipv4) = input.parse::<Ipv4Addr>() {
|
||||
@@ -47,10 +48,10 @@ impl From<&str> for Target {
|
||||
return Target::Ipv6(ipv6);
|
||||
}
|
||||
|
||||
if let Ok(url) = input.parse::<Url>() {
|
||||
if let Some(host) = url.host_str() {
|
||||
return Target::Domain(host.trim_end_matches('.').to_string());
|
||||
}
|
||||
if let Ok(url) = input.parse::<Url>()
|
||||
&& let Some(host) = url.host_str()
|
||||
{
|
||||
return Target::Domain(host.trim_end_matches('.').to_string());
|
||||
}
|
||||
Target::Domain(input.trim_end_matches('.').to_string())
|
||||
}
|
||||
|
||||
@@ -4,30 +4,28 @@ use indicatif::{ProgressBar, ProgressStyle};
|
||||
use log::info;
|
||||
use reqwest::IntoUrl;
|
||||
use std::fmt::Display;
|
||||
use std::io;
|
||||
use std::io::Error;
|
||||
|
||||
pub async fn fetch_db<T: IntoUrl + Display>(url: T) -> Result<Vec<u8>, Error> {
|
||||
info!("Fetching {}", url);
|
||||
let response = reqwest::get(url)
|
||||
.await
|
||||
.map_err(|e| Error::new(io::ErrorKind::Other, e))?
|
||||
.map_err(Error::other)?
|
||||
.error_for_status()
|
||||
.map_err(|e| Error::new(io::ErrorKind::Other, e))?;
|
||||
.map_err(Error::other)?;
|
||||
|
||||
let total_size = response.content_length().unwrap_or(0);
|
||||
let pb = ProgressBar::new(total_size);
|
||||
pb.set_style(ProgressStyle::default_bar()
|
||||
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})")
|
||||
.map_err(|e| Error::new(io::ErrorKind::Other, e))?
|
||||
.map_err(Error::other)?
|
||||
.progress_chars("#>-"));
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
bytes.reserve(total_size as usize);
|
||||
let mut bytes = Vec::with_capacity(total_size as usize);
|
||||
let mut stream = response.bytes_stream();
|
||||
|
||||
while let Some(chunk_result) = stream.next().await {
|
||||
let chunk = chunk_result.map_err(|e| Error::new(io::ErrorKind::Other, e))?;
|
||||
let chunk = chunk_result.map_err(Error::other)?;
|
||||
bytes.extend(&chunk);
|
||||
pb.inc(chunk.len() as u64);
|
||||
}
|
||||
|
||||
+40
-5
@@ -17,9 +17,9 @@ pub struct Counter {
|
||||
impl Counter {
|
||||
pub fn save_results(&self, output: &PathBuf) -> anyhow::Result<()> {
|
||||
let mut out = csv::WriterBuilder::new().from_path(output)?;
|
||||
out.write_record(&["target", "evidence"])?;
|
||||
out.write_record(["target", "evidence"])?;
|
||||
for (target, evidence) in &self.results {
|
||||
out.write_record(&[target, &evidence.to_string()])?;
|
||||
out.write_record([target, &evidence.to_string()])?;
|
||||
}
|
||||
info!("Saved results to {:?}", output);
|
||||
Ok(())
|
||||
@@ -59,16 +59,51 @@ impl Counter {
|
||||
impl Display for Counter {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let total = self.total();
|
||||
let percentage = |count| {
|
||||
if total == 0 {
|
||||
0.0
|
||||
} else {
|
||||
count as f32 / total as f32 * 100.0
|
||||
}
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
"OK {} ({:.2}%) | Blocked {} (early: {}) ({:.2}%) | Error {} ({:.2}%)",
|
||||
self.ok,
|
||||
self.ok as f32 / total as f32 * 100.0,
|
||||
percentage(self.ok),
|
||||
self.block,
|
||||
self.early,
|
||||
self.block as f32 / total as f32 * 100.0,
|
||||
percentage(self.block),
|
||||
self.err,
|
||||
self.err as f32 / total as f32 * 100.0
|
||||
percentage(self.err)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Counter;
|
||||
use reports::Evidence;
|
||||
|
||||
#[test]
|
||||
fn empty_counter_uses_zero_percentages() {
|
||||
assert_eq!(
|
||||
Counter::default().to_string(),
|
||||
"OK 0 (0.00%) | Blocked 0 (early: 0) (0.00%) | Error 0 (0.00%)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn counter_tracks_all_evidence_classes() {
|
||||
let mut counter = Counter::default();
|
||||
counter.add("ok.example", Evidence::Ok);
|
||||
counter.add("blocked.example", Evidence::Blocked);
|
||||
counter.add("error.example", Evidence::ConnectError);
|
||||
|
||||
assert_eq!(counter.total(), 3);
|
||||
assert_eq!(
|
||||
counter.to_string(),
|
||||
"OK 1 (33.33%) | Blocked 1 (early: 0) (33.33%) | Error 1 (33.33%)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+40
-9
@@ -32,6 +32,14 @@ enum Verbosity {
|
||||
All,
|
||||
}
|
||||
|
||||
fn parse_positive_usize(value: &str) -> Result<usize, String> {
|
||||
match value.parse::<usize>() {
|
||||
Ok(value) if value > 0 => Ok(value),
|
||||
Ok(_) => Err("значение должно быть больше нуля".to_string()),
|
||||
Err(error) => Err(format!("некорректное целое число: {error}")),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
#[command(
|
||||
author,
|
||||
@@ -57,7 +65,7 @@ struct Args {
|
||||
timeout_secs: u64,
|
||||
|
||||
/// Maximum concurrent probes. Make sure that it doesn't exceed 'ulimit -n'
|
||||
#[arg(short, long = "probes", default_value_t = 1000)]
|
||||
#[arg(short, long = "probes", default_value_t = 1000, value_parser = parse_positive_usize)]
|
||||
probe_count: usize,
|
||||
|
||||
/// Display probing results in console
|
||||
@@ -65,7 +73,7 @@ struct Args {
|
||||
verbosity: Verbosity,
|
||||
|
||||
/// Attempts to establish connection
|
||||
#[arg(short, long, default_value_t = 2)]
|
||||
#[arg(short, long, default_value_t = 2, value_parser = parse_positive_usize)]
|
||||
retry_count: usize,
|
||||
|
||||
/// Try using plain HTTP without TLS
|
||||
@@ -102,7 +110,7 @@ impl Args {
|
||||
ReporterConfig {
|
||||
http: self.http,
|
||||
tx_junk: self.tx,
|
||||
ip: self.ip.clone(),
|
||||
ip: self.ip,
|
||||
path: self.path.clone(),
|
||||
retry_count: self.retry_count,
|
||||
timeout_secs: self.timeout_secs,
|
||||
@@ -117,10 +125,14 @@ fn build_client(args: &Args, attempt: usize) -> reqwest::Result<Client> {
|
||||
.redirect(Policy::none())
|
||||
.use_rustls_tls()
|
||||
.dns_resolver(Arc::new(Resolver::new(args.ip)))
|
||||
.read_timeout(Duration::from_secs(args.timeout_secs * attempt as u64))
|
||||
.read_timeout(retry_read_timeout(args.timeout_secs, attempt))
|
||||
.timeout(Duration::from_secs(15));
|
||||
|
||||
Ok(client.build()?)
|
||||
client.build()
|
||||
}
|
||||
|
||||
fn retry_read_timeout(timeout_secs: u64, attempt: usize) -> Duration {
|
||||
Duration::from_secs(timeout_secs.saturating_mul(attempt as u64))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
@@ -145,7 +157,7 @@ async fn main() -> Result<()> {
|
||||
let targets: Vec<String> = targets
|
||||
.lines()
|
||||
.take(args.count)
|
||||
.map(|s| s.split(",").last().unwrap().to_string())
|
||||
.map(|s| s.split(',').next_back().unwrap().to_string())
|
||||
.collect();
|
||||
|
||||
info!(
|
||||
@@ -244,9 +256,9 @@ async fn upload_results(
|
||||
let uploaded = uploaded.send().await?;
|
||||
|
||||
if uploaded.status().is_success() {
|
||||
info!("Uploaded ({})!", uploaded.status().to_string());
|
||||
info!("Uploaded ({})!", uploaded.status());
|
||||
} else {
|
||||
warn!("Upload failed: {}", uploaded.status().to_string());
|
||||
warn!("Upload failed: {}", uploaded.status());
|
||||
}
|
||||
info!("Agency response: {}", uploaded.text().await?);
|
||||
Ok(())
|
||||
@@ -288,7 +300,7 @@ async fn check_target(args: &Args, target: &str) -> Result<Verdict, reqwest::Err
|
||||
|
||||
loop {
|
||||
attempts += 1;
|
||||
let client = build_client(&args, 1)?;
|
||||
let client = build_client(args, attempts)?;
|
||||
let mut resp = client.get(&url).header("Range", "bytes=0-65536");
|
||||
if args.tx {
|
||||
resp = resp.body(JUNK)
|
||||
@@ -341,3 +353,22 @@ async fn check_target(args: &Args, target: &str) -> Result<Verdict, reqwest::Err
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{parse_positive_usize, retry_read_timeout};
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn positive_usize_rejects_zero() {
|
||||
assert_eq!(parse_positive_usize("1"), Ok(1));
|
||||
assert!(parse_positive_usize("0").is_err());
|
||||
assert!(parse_positive_usize("not-a-number").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retry_timeout_grows_with_attempt_number() {
|
||||
assert_eq!(retry_read_timeout(5, 1), Duration::from_secs(5));
|
||||
assert_eq!(retry_read_timeout(5, 3), Duration::from_secs(15));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,14 @@ pub struct Resolver {
|
||||
impl Resolver {
|
||||
pub fn new(ip: IpAddr) -> Resolver {
|
||||
Resolver {
|
||||
ip: SocketAddr::from(SocketAddr::new(ip, 0)),
|
||||
ip: SocketAddr::new(ip, 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve for Resolver {
|
||||
fn resolve(&self, _: Name) -> Resolving {
|
||||
let ip = self.ip.clone();
|
||||
let ip = self.ip;
|
||||
Box::pin(async move { Ok(Addrs::from(Box::new(vec![ip].into_iter()))) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ use sqlx::postgres::PgPool;
|
||||
|
||||
pub struct Agency {
|
||||
pub id: i32,
|
||||
#[expect(
|
||||
dead_code,
|
||||
reason = "поле входит в SQLx offline-запрос; его удаление требует обновления метаданных на эталонной PostgreSQL"
|
||||
)]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ pub async fn check(
|
||||
.map_err(|_| Status::InternalServerError)?;
|
||||
|
||||
let id: Option<String> = if let Ok(check) = &check {
|
||||
match save_query(&mut *db, &target, check, addr, checker.read().await).await {
|
||||
match save_query(&mut db, &target, check, addr, checker.read().await).await {
|
||||
Ok(id) => Some(id.to_string()),
|
||||
Err(e) => {
|
||||
warn!("api: failed to save check: {:?}", e);
|
||||
@@ -70,7 +70,7 @@ pub async fn check(
|
||||
};
|
||||
|
||||
let whitelist: Option<WhitelistedEntry> = if let Target::Domain(domain) = &target {
|
||||
check_whitelist(domain, &mut *db)
|
||||
check_whitelist(domain, &mut db)
|
||||
.await
|
||||
.map_err(|_| Status::InternalServerError)?
|
||||
} else {
|
||||
|
||||
@@ -544,7 +544,7 @@ mod tests {
|
||||
config.hosts.push(Host {
|
||||
id: "test".to_string(),
|
||||
host: "192.0.2.1".to_string(),
|
||||
host_type: HostType::Blacklist,
|
||||
host_type: HostType::Whitelist,
|
||||
file_path: String::new(),
|
||||
timeout_sec: 1,
|
||||
min_data: 1,
|
||||
@@ -567,6 +567,28 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_whitelist_when_blacklist_host_returns_data() {
|
||||
let mut config = empty_config();
|
||||
config.hosts.push(Host {
|
||||
id: "test".to_string(),
|
||||
host: "192.0.2.1".to_string(),
|
||||
host_type: HostType::Blacklist,
|
||||
file_path: String::new(),
|
||||
timeout_sec: 1,
|
||||
min_data: 1,
|
||||
});
|
||||
let results = vec![HostProbeResult {
|
||||
host_id: "test".to_string(),
|
||||
probe_evidence: ProbeEvidence::ClientHello,
|
||||
}];
|
||||
|
||||
assert_eq!(
|
||||
build_probe_verdicts(&results, &config, None, None, None, false),
|
||||
vec!["whitelist"]
|
||||
);
|
||||
}
|
||||
|
||||
fn empty_config() -> ProbeConfig {
|
||||
ProbeConfig {
|
||||
version: String::new(),
|
||||
|
||||
+2
-4
@@ -98,7 +98,7 @@ impl<'r> FromRequest<'r> for Agency {
|
||||
let mut db = try_outcome!(
|
||||
pool.acquire()
|
||||
.await
|
||||
.map_err(|e| Some(e))
|
||||
.map_err(Some)
|
||||
.or_forward(Status::InternalServerError)
|
||||
);
|
||||
let token = request.headers().get_one("Authorization");
|
||||
@@ -114,7 +114,7 @@ impl<'r> FromRequest<'r> for Agency {
|
||||
sqlx::query!("SELECT id, name FROM reporters WHERE token = $1", token)
|
||||
.fetch_optional(&mut *db)
|
||||
.await
|
||||
.map_err(|e| Some(e))
|
||||
.map_err(Some)
|
||||
.or_forward(Status::InternalServerError)
|
||||
);
|
||||
agency
|
||||
@@ -152,7 +152,6 @@ pub async fn check_whitelist(
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
.into()
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, sqlx::FromRow)]
|
||||
@@ -190,5 +189,4 @@ ORDER BY b.bin;",
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
.into()
|
||||
}
|
||||
|
||||
+1
-1
@@ -324,7 +324,7 @@ struct ParsedProbeConfig {
|
||||
}
|
||||
|
||||
fn parse_probe_hosts(contents: &str) -> Result<ParsedProbeConfig, PublishError> {
|
||||
let config: ProbeHostsFile = toml::from_str(&contents).map_err(PublishError::ConfigParse)?;
|
||||
let config: ProbeHostsFile = toml::from_str(contents).map_err(PublishError::ConfigParse)?;
|
||||
|
||||
let hosts = config
|
||||
.hosts
|
||||
|
||||
@@ -39,20 +39,14 @@ pub async fn export_csv(
|
||||
}
|
||||
};
|
||||
|
||||
let mut db = pool
|
||||
.acquire()
|
||||
.await
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
let mut db = pool.acquire().await.map_err(io::Error::other)?;
|
||||
|
||||
let mut stream = db
|
||||
.copy_out_raw(query)
|
||||
.await
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
let mut stream = db.copy_out_raw(query).await.map_err(io::Error::other)?;
|
||||
|
||||
let mut data = Vec::default();
|
||||
|
||||
while let Some(bytes_result) = stream.next().await {
|
||||
let bytes = bytes_result.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
let bytes = bytes_result.map_err(io::Error::other)?;
|
||||
data.extend(bytes)
|
||||
}
|
||||
|
||||
@@ -75,7 +69,7 @@ pub async fn histogram(
|
||||
.await
|
||||
.map_err(|_| Status::InternalServerError)?;
|
||||
Ok(Json(
|
||||
collect_histogram(&mut *db, 50, limit, filter.is_some())
|
||||
collect_histogram(&mut db, 50, limit, filter.is_some())
|
||||
.await
|
||||
.map_err(|_| Status::InternalServerError)?,
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user