From 48c0132e365b1e36d46163300fdd41551d22ca35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20St=C3=BCrmer?= Date: Fri, 18 Aug 2023 00:45:55 +0200 Subject: [PATCH] add tests for media list request --- Cargo.toml | 2 +- crates/media/Cargo.toml | 3 +- crates/media/src/api/router.rs | 109 +++++++++++++++------- crates/media/src/api/routes/get_media.rs | 21 ++++- crates/media/src/api/routes/post_media.rs | 17 +++- crates/media/src/data/media_item.rs | 12 +-- crates/media/src/repository/mod.rs | 48 +--------- 7 files changed, 123 insertions(+), 89 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 911710f..65deadd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ anyhow = { version = "1.0.72" } chrono = { version = "0.4.26", features = ["serde"] } hyper = { version = "0.14", features = ["full"] } - +mime = { version = "0.3" } mockall = { version = "0.11.4" } rstest = { version = "0.18.1" } diff --git a/crates/media/Cargo.toml b/crates/media/Cargo.toml index a906252..e361dc2 100644 --- a/crates/media/Cargo.toml +++ b/crates/media/Cargo.toml @@ -21,8 +21,9 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } # Router -axum = { workspace = true } +axum = { workspace = true, features = ["multipart"] } tower-http = { workspace = true } +mime = { workspace = true } # persistency sea-orm = { workspace = true } diff --git a/crates/media/src/api/router.rs b/crates/media/src/api/router.rs index b91ac88..6d81823 100644 --- a/crates/media/src/api/router.rs +++ b/crates/media/src/api/router.rs @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -use axum::routing::{get, patch, post, delete}; +use axum::routing::{delete, get, patch, post}; use axum::Router; use super::routes::delete_media_id::delete_media_id; @@ -45,32 +45,26 @@ impl MediaApi { // 403 Forbidden // 500 Internal Server Error .route("/media", get(get_media)) - // Creates a new media item to aggregate related files for current user // 201 - Created - // 400 Bad Request - The request body was malformed or a field violated its constraints. + // 400 Bad Request - The request body was malformed or a field violated its constraints. // 401 Unauthorized - You are unauthenticated // 403 Forbidden - You are authenticated but have no permission to manage the target user. // 500 Internal Server Error .route("/media", post(post_media)) - // Returns a specific owned or shared media item for current user // 200 - Ok - // 400 Bad Request - The request body was malformed or a field violated its constraints. + // 400 Bad Request - The request body was malformed or a field violated its constraints. // 401 Unauthorized - You are unauthenticated // 403 Forbidden - You are authenticated but have no permission to manage the target user. // 500 Internal Server Error .route("/media/:media_id", get(get_media_id)) - // Add files for a specific media item .route("/media/:media_id", post(post_media_id)) - // Updates fields from a specific media item for current user .route("/media/:media_id", patch(patch_media_id)) - // Deletes the given item owned by the user .route("/media/:media_id", delete(delete_media_id)) - // list owned and shared albums .route("/albums", get(get_albums)) // create new album @@ -83,44 +77,95 @@ impl MediaApi { .route("/albums/:entity_id/share", patch(patch_albums_id_share)) // unshares the given album .route("/albums/:entity_id/unshare", patch(patch_albums_id_unshare)) - .layer(tower_http::trace::TraceLayer::new_for_http()) } } #[cfg(test)] mod tests { - use std::sync::Arc; + use super::*; + use axum::{ + body::Body, + http::{self, Request, StatusCode}, + }; + use serde_json::json; + use tower::ServiceExt; - use crate::repository::{MediaRepositoryState, MediaRepository}; + #[tokio::test] + async fn get_media_with_query_success() { + // given + let app = Router::new().nest("/", MediaApi::routes()); - use super::*; - use axum::{body::Body, http::{Request, StatusCode}}; - use rstest::rstest; + // when + let response = app + .oneshot( + Request::builder() + .uri("/media?limit=100000&offset=1") + .method("GET") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + // then + assert_eq!(response.status(), StatusCode::OK); + + let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); + let body: String = serde_json::from_slice(&body).unwrap(); + + assert_eq!(body, "list media items. limit=100000, offset=1"); + } - #[rstest] - #[case("/?name=Wonder")] #[tokio::test] - async fn get_media_success(#[case] uri: &'static str) { + async fn get_media_without_query_success() { // given - let repo: MediaRepositoryState = Arc::new(MediaRepository::new().await); - let api: Router = MediaApi::routes().with_state(repo); + let app = Router::new().nest("/", MediaApi::routes()); // when - /* - TODO: find replacement for `oneshot` - let response = api::oneshot( - Request::builder() - .uri("/media") - .method("GET") - .body(Body::empty()) - .unwrap() - ).await.unwrap; + let response = app + .oneshot( + Request::builder() + .uri("/media") + .method("GET") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + // then + assert_eq!(response.status(), StatusCode::OK); + let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); - let body = serde_json::from_slice(&body).unwrap(); + let body: String = serde_json::from_slice(&body).unwrap(); + + assert_eq!(body, "list media items. limit=1000, offset=0"); + } + + // TODO: re-enable test + // #[tokio::test] + async fn post_media_success() { + // given + let app = Router::new().nest("/", MediaApi::routes()); + + // when + let response = app + .oneshot( + Request::builder() + .uri("/media") + .method("POST") + .header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) + // add multipart file to body + .body(Body::from( + serde_json::to_vec(&json!([1, 2, 3, 4])).unwrap(), + )) + .unwrap(), + ) + .await + .unwrap(); // then - assert_eq!(response.status(), StatusCode::NOT_FOUND); - */ + assert_eq!(response.status(), StatusCode::NOT_IMPLEMENTED); } } diff --git a/crates/media/src/api/routes/get_media.rs b/crates/media/src/api/routes/get_media.rs index 57e89d2..7a2a801 100644 --- a/crates/media/src/api/routes/get_media.rs +++ b/crates/media/src/api/routes/get_media.rs @@ -1,6 +1,6 @@ //! Returns a list of owned media items for current user -//! -use axum::{http::StatusCode, extract::Query, Json}; +//! +use axum::{extract::Query, http::StatusCode, Json}; use serde::{Deserialize, Serialize}; use std::result::Result; @@ -10,7 +10,18 @@ pub(crate) struct MediaListQuery { limit: Option, } -pub(crate) async fn get_media(Query(query): Query) -> Result, StatusCode> { - // Err(StatusCode::UNAUTHORIZED) - Ok(Json(format!("list media items. limit={}, offset={}", query.limit.unwrap(), query.offset.unwrap()).to_owned())) +pub(crate) async fn get_media( + Query(query): Query, +) -> Result, StatusCode> { + // TODO: check auth header + // TODO: read list from persistency + // TODO: return list + Ok(Json( + format!( + "list media items. limit={}, offset={}", + query.limit.unwrap_or_else(|| 1000), + query.offset.unwrap_or_else(|| 0) + ) + .to_owned(), + )) } diff --git a/crates/media/src/api/routes/post_media.rs b/crates/media/src/api/routes/post_media.rs index 09ee0a7..f9fd86b 100644 --- a/crates/media/src/api/routes/post_media.rs +++ b/crates/media/src/api/routes/post_media.rs @@ -2,8 +2,23 @@ //! use axum::http::StatusCode; +use axum::extract::Multipart; -pub(crate) async fn post_media() -> std::result::Result { +pub(crate) async fn post_media(mut multipart: Multipart) -> std::result::Result { + while let Some(field) = multipart.next_field().await.unwrap() { + let name = field.name().unwrap().to_string(); + let file_name = field.file_name().unwrap().to_string(); + let content_type = field.content_type().unwrap().to_string(); + let data = field.bytes().await.unwrap(); + + println!( + "Length of `{}` (`{}`: `{}`) is {} bytes", + name, + file_name, + content_type, + data.len() + ); + } // TODO: read authentication header Err(StatusCode::NOT_IMPLEMENTED) } diff --git a/crates/media/src/data/media_item.rs b/crates/media/src/data/media_item.rs index 68ae190..36e24d6 100644 --- a/crates/media/src/data/media_item.rs +++ b/crates/media/src/data/media_item.rs @@ -1,16 +1,16 @@ /* Photos.network ยท A privacy first photo storage and sharing service for fediverse. * Copyright (C) 2020 Photos network developers - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ @@ -29,11 +29,11 @@ pub struct MediaItem { pub details: Option, pub tags: Option>, pub location: Option, - pub references: Option> + pub references: Option>, } impl MediaItem { - #[warn(dead_code)] + #[allow(dead_code)] fn new(name: &'static str) -> Self { MediaItem { uuid: "", @@ -43,7 +43,7 @@ impl MediaItem { location: None, details: None, tags: None, - references: None + references: None, } } } diff --git a/crates/media/src/repository/mod.rs b/crates/media/src/repository/mod.rs index d4c6637..c908f3a 100644 --- a/crates/media/src/repository/mod.rs +++ b/crates/media/src/repository/mod.rs @@ -26,16 +26,20 @@ use crate::data::media_item::MediaItem; use crate::data::open_db_conn; pub struct MediaRepository { + #[allow(dead_code)] db_url: &'static str, + #[allow(dead_code)] db: DatabaseConnection, } +#[allow(dead_code)] pub(crate) type MediaRepositoryState = Arc; /// MockPhotosRepositoryTrait is created by automock macro #[cfg_attr(test, mockall::automock)] #[async_trait] trait MediaRepositoryTrait { + #[allow(dead_code)] async fn new(db_url: &'static str) -> Self; // Gets a list of media items from the DB filted by user_id @@ -43,6 +47,7 @@ trait MediaRepositoryTrait { } impl MediaRepository { + #[allow(dead_code)] pub(crate) async fn new() -> Self { Self { db_url: "", @@ -69,46 +74,3 @@ impl MediaRepositoryTrait for MediaRepository { Err(DataAccessError::OtherError) } } - - -#[cfg(test)] -mod tests { - use sea_orm::{DbConn, Schema, DbBackend, sea_query::TableCreateStatement}; - - use super::*; - - async fn setup_schema(db: &DbConn) { - let schema = Schema::new(DbBackend::Sqlite); - - // Derive from Entity - // let stmt: TableCreateStatement = schema.create_table_from_entity(MyEntity); - - // Or setup manually - // assert_eq!( - // stmt.build(SqliteQueryBuilder), - // Table::create() - // .table(MyEntity) - // .col( - // ColumnDef::new(MyEntity::Column::Id) - // .integer() - // .not_null() - // ) - // //... - // .build(SqliteQueryBuilder) - // ); - - // // Execute create table statement - // let result = db - // .execute(db.get_database_backend().build(&stmt)) - // .await; - } - - // #[rstest] - // #[case("/?name=Wonder", "Wonder%")] // Verify that % is appended to the filter - // async fn get_media_items_for_user_success(#[case] uri: &'static str, #[case] expected_filter: &'static str) { - - // let mut repo_mock = MockMediaRepositoryTrait::new("sqlite::memory:"); - // setup_schema(&db).await?; - // testcase(&db).await?; - // } -}