use std::{io::SeekFrom, ops::Deref, pin::pin, sync::Arc};
use crate::{
Builder, Reader,
test_util::{
skip_length,
test_entry_data::{ReaderAndData, TestEntryData},
},
};
use actix_test::TestServer;
use actix_web::{App, Responder, web};
use assert2::assert;
use async_http_range_reader::{AsyncHttpRangeReader, CheckSupportMethod};
use bytes::Bytes;
use test_strategy::proptest;
use tokio::{
io::{AsyncRead, AsyncReadExt, AsyncSeekExt},
sync::{Mutex, oneshot},
};
struct TestApp {
entry_data: TestEntryData,
receiver: Mutex<Option<oneshot::Receiver<Reader<Vec<u8>>>>>,
}
async fn download_zip_no_callback(data: web::Data<TestApp>) -> impl Responder {
let app = Arc::clone(data.deref());
let builder: Builder<_> = app.entry_data.clone().into();
let reader = builder.build();
reader.into_responder()
}
async fn download_zip_yes_callback(data: web::Data<TestApp>) -> impl Responder {
let app = data.deref();
let builder: Builder<_> = app.entry_data.clone().into();
let reader = builder.build();
let (responder, channel) = reader.into_responder_with_channel();
app.receiver.lock().await.replace(channel);
responder
}
async fn read_to_vec(reader: impl AsyncRead) -> std::io::Result<Vec<u8>> {
let mut buffer = Vec::new();
let mut reader = pin!(reader);
reader.read_to_end(&mut buffer).await?;
Ok(buffer)
}
async fn prepare(
data: ReaderAndData,
use_callback: bool,
) -> (TestServer, web::Data<TestApp>, String, Vec<u8>) {
let direct_read_result = read_to_vec(data.reader).await.unwrap();
let app_data = web::Data::new(TestApp {
entry_data: data.data,
receiver: Default::default(),
});
let app_data_cloned = app_data.clone();
let server = actix_test::start(move || {
App::new()
.app_data(app_data_cloned.clone())
.route("/no_callback", web::get().to(download_zip_no_callback))
.route("/yes_callback", web::get().to(download_zip_yes_callback))
});
let url = if use_callback {
server.url("/yes_callback")
} else {
server.url("/no_callback")
};
(server, app_data, url, direct_read_result)
}
#[proptest(async = "tokio")]
async fn read_all(data: ReaderAndData, use_callback: bool) {
let reader_size = data.reader.size();
let (_server, app_data, url, direct_read_result) = prepare(data, use_callback).await;
let direct_read_result = Bytes::from(direct_read_result);
let response = reqwest::Client::new().get(url).send().await.unwrap();
let http_read_result = response.bytes().await.unwrap();
assert!(http_read_result == direct_read_result);
if use_callback {
let receiver = app_data
.receiver
.lock()
.await
.take()
.expect("The receiver should be populated");
let extracted_reader = receiver.await.expect("The receiver shouldn't have hung up");
assert!(extracted_reader.size() == reader_size);
} else {
assert!(app_data.receiver.lock().await.is_none());
}
}
#[proptest(async = "tokio")]
async fn read_block(
data: ReaderAndData,
use_callback: bool,
#[strategy(0f64..1f64)] boundary1: f64,
#[strategy(0f64..1f64)] boundary2: f64,
) {
let reader_size = data.reader.size();
let (_server, app_data, url, direct_read_result) = prepare(data, use_callback).await;
let mut http_reader = pin!(
AsyncHttpRangeReader::new(
reqwest::Client::new(),
reqwest::Url::parse(&url).unwrap(),
CheckSupportMethod::NegativeRangeRequest(1),
Default::default(),
)
.await
.unwrap()
.0
);
let start = usize::try_from(skip_length(
direct_read_result.len(),
boundary1.min(boundary2),
))
.unwrap();
let end = usize::try_from(skip_length(
direct_read_result.len(),
boundary1.max(boundary2),
))
.unwrap();
http_reader
.seek(SeekFrom::Start(start as u64))
.await
.unwrap();
let mut http_read_result = vec![0; end - start + 1];
http_reader
.read_exact(http_read_result.as_mut())
.await
.unwrap();
assert!(http_read_result == direct_read_result[start..=end]);
if use_callback {
let receiver = app_data
.receiver
.lock()
.await
.take()
.expect("The receiver should be populated");
let extracted_reader = receiver.await.expect("The receiver shouldn't have hung up");
assert!(extracted_reader.size() == reader_size);
} else {
assert!(app_data.receiver.lock().await.is_none());
}
}