Skip to content

Commit

Permalink
new figshare create_articles test
Browse files Browse the repository at this point in the history
  • Loading branch information
vsbuffalo committed Aug 20, 2023
1 parent 8ec435f commit 4578d42
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 47 deletions.
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ pub mod lib {
pub mod remote;
pub mod utils;
}

pub mod logging_setup;
99 changes: 84 additions & 15 deletions src/lib/api/figshare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ use crate::lib::data::{DataFile, MergedFile};
use crate::lib::remote::{AuthKeys, RemoteFile, DownloadInfo,RequestData};
use crate::lib::project::LocalMetadata;

const FIGSHARE_API_URL: &str = "https://api.figshare.com/v2/";
const BASE_URL: &str = "https://api.figshare.com/v2/";

// for testing:
const TEST_TOKEN: &str = "test-token";

// for serde deserialize default
fn figshare_api_url() -> String {
FIGSHARE_API_URL.to_string()
BASE_URL.to_string()
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
Expand Down Expand Up @@ -242,13 +246,24 @@ pub struct FigShareArticle {
}

impl FigShareAPI {
pub fn new(name: String) -> Result<Self> {
let auth_keys = AuthKeys::new();
pub fn new(name: &str, base_url: Option<String>) -> Result<Self> {
let auth_keys = if base_url.is_none() {
// using the default base_url means we're
// not using mock HTTP servers
AuthKeys::new()
} else {
// If base_url is set, we're using mock HTTP servers,
// so we use the test-token
let mut auth_keys = AuthKeys::default();
auth_keys.add("figshare", TEST_TOKEN);
auth_keys
};
let token = auth_keys.get("figshare".to_string())?;
let base_url = base_url.unwrap_or(BASE_URL.to_string());
Ok(FigShareAPI {
base_url: FIGSHARE_API_URL.to_string(),
base_url,
article_id: None,
name,
name: name.to_string(),
token
})
}
Expand All @@ -257,20 +272,22 @@ impl FigShareAPI {
self.token = token;
}

async fn issue_request<T: serde::Serialize>(&self, method: Method, url: &str,
async fn issue_request<T: serde::Serialize>(&self, method: Method, endpoint: &str,
data: Option<RequestData<T>>) -> Result<Response> {
let mut headers = HeaderMap::new();

let full_url = if url.starts_with("https://") || url.starts_with("http://") {
url.to_string()
// FigShare will give download links outside the API, so we handle
// that possibility here.
let url = if endpoint.starts_with("https://") || endpoint.starts_with("http://") {
endpoint.to_string()
} else {
format!("{}{}", self.base_url, url.trim_start_matches('/'))
format!("{}/{}", self.base_url, endpoint.trim_start_matches('/'))
};

trace!("request URL: {:?}", full_url);
trace!("request URL: {:?}", url);

let client = Client::new();
let mut request = client.request(method, &full_url);
let mut request = client.request(method, &url);

headers.insert("Authorization", HeaderValue::from_str(&format!("token {}", self.token)).unwrap());
trace!("headers: {:?}", headers);
Expand All @@ -289,7 +306,7 @@ impl FigShareAPI {
if response_status.is_success() {
Ok(response)
} else {
Err(anyhow!("HTTP Error: {}\nurl: {:?}\n{:?}", response_status, full_url, response.text().await?))
Err(anyhow!("HTTP Error: {}\nurl: {:?}\n{:?}", response_status, url, response.text().await?))
}
}

Expand All @@ -308,7 +325,7 @@ impl FigShareAPI {

// Create a new FigShare Article
pub async fn create_article(&self, title: &str) -> Result<FigShareArticle> {
let url = "account/articles";
let endpoint = "account/articles";

// (1) create the data for this article
let mut data: HashMap<String, String> = HashMap::new();
Expand All @@ -317,7 +334,7 @@ impl FigShareAPI {
debug!("creating data for article: {:?}", data);

// (2) issue request and parse out the article ID from location
let response = self.issue_request(Method::POST, url, Some(RequestData::Json(data))).await?;
let response = self.issue_request(Method::POST, endpoint, Some(RequestData::Json(data))).await?;
let data = response.json::<Value>().await?;
let article_id_result = match data.get("location").and_then(|loc| loc.as_str()) {
Some(loc) => Ok(loc.split('/').last().unwrap_or_default().to_string()),
Expand Down Expand Up @@ -477,3 +494,55 @@ impl FigShareAPI {
Ok(())
}
}


#[cfg(test)]
mod tests {
use super::*;
use httpmock::prelude::*;
use serde_json::json;
use crate::logging_setup::setup;


#[tokio::test]
async fn test_create_article() {
setup();
// Start a mock server
let server = MockServer::start();

let expected_id = 12345;
let title = "Test Article";

// Create a mock endpoint for creating an article
let create_article_mock = server.mock(|when, then| {
when.method(POST)
.path("/account/articles")
.header("Authorization", &format!("token {}", TEST_TOKEN.to_string()))
.json_body(json!({
"title": title.to_string(),
"defined_type": "dataset"
}));
then.status(201)
.json_body(json!({
"location": format!("{}account/articles/{}", server.url(""), expected_id)
}));
});

// Define a sample title for the article
let mut api = FigShareAPI::new("Test Article", Some(server.url(""))).unwrap();

info!("auth_keys: {:?}", api.token);
// Call the create_article method
let result = api.create_article(title).await;

// Check the result
assert_eq!(result.is_ok(), true);
let article = result.unwrap();
assert_eq!(article.title, title);
assert_eq!(article.id, expected_id);

// Verify that the mock was called exactly once
create_article_mock.assert();
}

}
48 changes: 19 additions & 29 deletions src/lib/api/zenodo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,13 @@ pub struct ZenodoAPI {
}

impl ZenodoAPI {
pub fn new(name: String, base_url: Option<String>) -> Result<Self> {
pub fn new(name: &str, base_url: Option<String>) -> Result<Self> {
let auth_keys = AuthKeys::new();
let token = auth_keys.get("figshare".to_string())?;
let base_url = base_url.unwrap_or(BASE_URL.to_string());
Ok(ZenodoAPI {
base_url,
name,
name: name.to_string(),
token,
deposition_id: None,
bucket_url: None
Expand Down Expand Up @@ -258,20 +258,9 @@ mod tests {
use super::*;
use httpmock::prelude::*;
use serde_json::json;
use lazy_static::lazy_static;
use std::sync::Once;
use crate::logging_setup::setup;

fn setup() {
lazy_static! {
static ref INIT_LOGGING: Once = Once::new();
}

INIT_LOGGING.call_once(|| {
env_logger::init();
});
}

#[tokio::test]
//#[tokio::test]
async fn test_remote_init_success() {
setup();
// Start a mock server
Expand All @@ -280,10 +269,20 @@ mod tests {
let expected_id = 12345;
let expected_bucket_url = "http://zenodo.com/api/some-link-to-bucket";

// Prepare local_metadata
let local_metadata = LocalMetadata {
author_name: Some("Joan B. Scientist".to_string()),
title: Some("A *truly* reproducible project.".to_string()),
email: None,
affiliation: Some("UC Berkeley".to_string()),
description: Some("Let's build infrastructure so science can build off itself.".to_string()),
};

// Create a mock deposition endpoint with a simulated success response
let deposition_mock = server.mock(|when, then| {
when.method(POST)
.path("/deposit/depositions");
// TODO probably could minimize this example
then.status(200)
.json_body(json!({
"conceptrecid": "8266447",
Expand All @@ -307,8 +306,8 @@ mod tests {
"access_right": "open",
"creators": [
{
"affiliation": "Zenodo",
"name": "Doe, John"
"affiliation": local_metadata.affiliation,
"name": local_metadata.author_name,
}
],
"description": "This is a description of my deposition",
Expand All @@ -332,22 +331,13 @@ mod tests {
});

// Create an instance of ZenodoAPI
let mut api = ZenodoAPI::new("test".to_string(), Some(server.url("/"))).unwrap();
let mut api = ZenodoAPI::new("test", Some(server.url("/"))).unwrap();
info!("Test ZenodoAPI: {:?}", api);
api.set_token("fake_token".to_string());

// Prepare local_metadata
let local_metadata = LocalMetadata {
author_name: Some("Joan B. Scientist".to_string()),
title: Some("A *truly* reproducible project.".to_string()),
email: None,
affiliation: None,
description: Some("Let's build infrastructure so science can build off itself.".to_string()),
};

// main call to test
// Main call to test
let result = api.remote_init(local_metadata).await;
info!("result: {:?}", result);
//info!("result: {:?}", result);

// ensure the specified mock was called exactly one time (or fail).
deposition_mock.assert();
Expand Down
4 changes: 2 additions & 2 deletions src/lib/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,8 @@ impl Project {

let service = service.to_lowercase();
let mut remote = match service.as_str() {
"figshare" => Ok(Remote::FigShareAPI(FigShareAPI::new(name)?)),
"zenodo" => Ok(Remote::ZenodoAPI(ZenodoAPI::new(name, None)?)),
"figshare" => Ok(Remote::FigShareAPI(FigShareAPI::new(&name, None)?)),
"zenodo" => Ok(Remote::ZenodoAPI(ZenodoAPI::new(&name, None)?)),
_ => Err(anyhow!("Service '{}' is not supported!", service))
}?;

Expand Down
5 changes: 4 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use structopt::StructOpt;
use log::{info, trace, debug};

use sciflow::lib::project::Project;
use sciflow::logging_setup::setup;

pub mod logging_setup;

const INFO: &str = "\
SciFlow: Manage and Share Scientific Data
Expand Down Expand Up @@ -154,6 +157,7 @@ pub fn print_errors(response: Result<()>) {

#[tokio::main]
async fn main() {
setup();
match run().await {
Ok(_) => {}
Err(e) => {
Expand All @@ -164,7 +168,6 @@ async fn main() {
}

async fn run() -> Result<()> {
env_logger::init();
let cli = Cli::parse();
match &cli.command {
Some(Commands::Add { filenames }) => {
Expand Down

0 comments on commit 4578d42

Please sign in to comment.