Add testing and automatic content type detection

This commit is contained in:
Harrison Burt 2022-03-27 21:04:22 +01:00
parent 43ff1229eb
commit 114137b7b0
5 changed files with 101 additions and 22 deletions

View File

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -19,6 +19,7 @@ pub fn config() -> &'static RuntimeConfig {
#[cfg(test)]
pub fn init_test(data: &str) -> Result<()> {
let cfg: RuntimeConfig = serde_yaml::from_str(data)?;
dbg!(&cfg);
let _ = CONFIG.set(cfg);
Ok(())
}
@ -84,7 +85,7 @@ impl RuntimeConfig {
self
.max_upload_size
.map(|limit| size <= limit)
.unwrap_or(false)
.unwrap_or(true)
}
}
@ -198,6 +199,16 @@ impl ImageKind {
}
}
pub fn from_guessed_format(fmt: image::ImageFormat) -> Option<Self> {
match fmt {
image::ImageFormat::Png => Some(Self::Png),
image::ImageFormat::Jpeg => Some(Self::Jpeg),
image::ImageFormat::Gif => Some(Self::Gif),
image::ImageFormat::WebP => Some(Self::Webp),
_ => None
}
}
pub fn as_content_type(&self) -> String {
format!("image/{}", self.as_file_extension())
}

View File

@ -16,10 +16,6 @@ pub fn init_buckets(buckets: hashbrown::HashMap<u32, BucketController>) {
let _ = BUCKETS.set(buckets);
}
pub fn buckets() -> &'static hashbrown::HashMap<u32, BucketController> {
BUCKETS.get_or_init(hashbrown::HashMap::new)
}
pub fn get_bucket_by_id(bucket_id: u32) -> Option<&'static BucketController> {
BUCKETS.get_or_init(hashbrown::HashMap::new).get(&bucket_id)
}

View File

@ -29,6 +29,11 @@ pub enum UploadResponse {
#[oai(status = 404)]
NotFound,
/// The image format was incorrect or the system was
/// unable to guess the format of the image.
#[oai(status = 400)]
InvalidImageFormat,
/// The upload exceeds the configured maximum file size.
#[oai(status = 413)]
TooBig,
@ -127,7 +132,7 @@ impl LustApi {
/// The total size of the image in bytes.
#[oai(name = "content-length")] content_length: Header<usize>,
format: Query<ImageKind>,
format: Query<Option<ImageKind>>,
/// The raw binary data of the image.
file: Binary<Body>,
@ -164,7 +169,26 @@ impl LustApi {
}
}
let info = bucket.upload(format.0, allocated_image).await?;
let format = if let Some(format) = format.0 {
let validate = image::load_from_memory_with_format(&allocated_image, format.into());
if let Err(_) = validate {
return Ok(UploadResponse::InvalidImageFormat)
}
format
} else {
let maybe_guessed = image::guess_format(&allocated_image)
.map(ImageKind::from_guessed_format)
.map_err(anyhow::Error::from)?;
if let Some(guessed) = maybe_guessed {
guessed
} else {
return Ok(UploadResponse::InvalidImageFormat)
}
};
let info = bucket.upload(format, allocated_image).await?;
Ok(UploadResponse::Ok(Json(info)))
}

View File

@ -1,13 +1,48 @@
use poem::{IntoEndpoint, Route};
use std::sync::Arc;
use poem::Route;
use poem::http::StatusCode;
use poem_openapi::OpenApiService;
use poem::test::TestClient;
use poem::web::headers;
use tokio::sync::Semaphore;
use crate::ServerConfig;
use crate::{BucketController, config, controller, StorageBackend};
const EXAMPLE_CONFIG: &str = include_str!("../examples/example.yaml");
const TEST_IMAGE: &[u8] = include_bytes!()
const TEST_IMAGE: &[u8] = include_bytes!("../examples/example.jpeg");
async fn setup_environment() -> anyhow::Result<TestClient<Route>> {
config::init_test(EXAMPLE_CONFIG)?;
let global_limiter = config::config()
.max_concurrency
.map(Semaphore::new)
.map(Arc::new);
let storage: Arc<dyn StorageBackend> = config::config()
.backend
.connect()
.await?;
let buckets = config::config()
.buckets
.iter()
.map(|(bucket, cfg)| {
let bucket_id = crate::utils::crc_hash(bucket);
let pipeline = cfg.mode.build_pipeline(cfg);
let controller = BucketController::new(
bucket_id,
global_limiter.clone(),
cfg.clone(),
pipeline,
storage.clone(),
);
(bucket_id, controller)
})
.collect();
controller::init_buckets(buckets);
fn setup_environment() -> TestClient<Route> {
let app = OpenApiService::new(
crate::routes::LustApi,
"Lust API",
@ -15,41 +50,54 @@ fn setup_environment() -> TestClient<Route> {
);
let app = Route::new().nest("/v1", app);
TestClient::new(app)
Ok(TestClient::new(app))
}
#[tokio::test]
async fn test_basic_aot_upload_retrieval() -> anyhow::Result<()> {
crate::config::init_test(EXAMPLE_CONFIG)?;
let app = setup_environment();
let app = setup_environment().await?;
app.post("/v1/user-profiles")
.body()
let res = app.post("/v1/user-profiles")
.body(TEST_IMAGE)
.content_type("application/octet-stream".to_string())
.typed_header(headers::ContentLength(TEST_IMAGE.len() as u64))
.query("format".to_string(), &"jpeg".to_string())
.send()
.await;
res.assert_status(StatusCode::OK);
// let res = app.post("/v1/user-profiles")
// .body(TEST_IMAGE)
// .content_type("application/octet-stream".to_string())
// .typed_header(headers::ContentLength(TEST_IMAGE.len() as u64))
// .query("format".to_string(), &"jpeg".to_string())
// .send()
// .await;
//
// res.assert_status(StatusCode::OK);
Ok(())
}
#[tokio::test]
async fn test_basic_jit_upload_retrieval() -> anyhow::Result<()> {
crate::config::init_test(EXAMPLE_CONFIG)?;
let app = setup_environment();
let app = setup_environment().await?;
Ok(())
}
#[tokio::test]
async fn test_basic_realtime_upload_retrieval() -> anyhow::Result<()> {
crate::config::init_test(EXAMPLE_CONFIG)?;
let app = setup_environment();
let app = setup_environment().await?;
Ok(())
}
#[tokio::test]
async fn test_realtime_resizing() -> anyhow::Result<()> {
crate::config::init_test(EXAMPLE_CONFIG)?;
let app = setup_environment();
let app = setup_environment().await?;
Ok(())
}