aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--rust/Cargo.lock24
-rw-r--r--rust/Cargo.toml2
-rw-r--r--rust/src/api_helpers.rs84
-rw-r--r--rust/src/api_server.rs55
-rw-r--r--rust/src/api_wrappers.rs8
-rw-r--r--rust/src/lib.rs7
-rw-r--r--rust/tests/test_api_server.rs215
7 files changed, 380 insertions, 15 deletions
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index 07bf17bf..0603c7f2 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -316,7 +316,9 @@ dependencies = [
"iron 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"iron-slog 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"iron-test 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
"slog 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"slog-async 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -833,6 +835,18 @@ dependencies = [
]
[[package]]
+name = "regex"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "regex-syntax"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -841,6 +855,14 @@ dependencies = [
]
[[package]]
+name = "regex-syntax"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "remove_dir_all"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1350,7 +1372,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384"
+"checksum regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5bbbea44c5490a1e84357ff28b7d518b4619a159fed5d25f6c1de2d19cc42814"
"checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7"
+"checksum regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "747ba3b235651f6e2f67dfa8bcdcd073ddb7c243cb21c442fc12395dfcac212d"
"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
"checksum route-recognizer 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3255338088df8146ba63d60a9b8e3556f1146ce2973bc05a75181a42ce2256"
"checksum router 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dc63b6f3b8895b0d04e816b2b1aa58fdba2d5acca3cbb8f0ab8e017347d57397"
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 0868d7f1..23747ddf 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -16,6 +16,8 @@ error-chain = "0.12"
uuid = "0.5"
log = "*"
data-encoding = "2.1"
+regex = "1"
+lazy_static = "1.0"
# API server
chrono = { version = "0.4", features = ["serde"] }
diff --git a/rust/src/api_helpers.rs b/rust/src/api_helpers.rs
index f0f56a6b..ef07ee55 100644
--- a/rust/src/api_helpers.rs
+++ b/rust/src/api_helpers.rs
@@ -5,6 +5,8 @@ use diesel;
use diesel::prelude::*;
use errors::*;
use uuid::Uuid;
+use regex::Regex;
+
pub fn get_or_create_editgroup(editor_id: Uuid, conn: &PgConnection) -> Result<Uuid> {
// check for current active
@@ -109,3 +111,85 @@ pub fn uuid2fcid(id: &Uuid) -> String {
let raw = id.as_bytes();
BASE32_NOPAD.encode(raw).to_lowercase()
}
+
+pub fn check_pmcid(raw: &str) -> Result<()> {
+ lazy_static! {
+ static ref RE: Regex = Regex::new(r"^PMC\d+$").unwrap();
+ }
+ if RE.is_match(raw) {
+ Ok(())
+ } else {
+ Err(ErrorKind::MalformedExternalId(
+ format!("not a valid PubMed Central ID (PMCID): '{}' (expected, eg, 'PMC12345')", raw)
+ ).into())
+ }
+}
+
+pub fn check_pmid(raw: &str) -> Result<()> {
+ lazy_static! {
+ static ref RE: Regex = Regex::new(r"^\d+$").unwrap();
+ }
+ if RE.is_match(raw) {
+ Ok(())
+ } else {
+ Err(ErrorKind::MalformedExternalId(
+ format!("not a valid PubMed ID (PMID): '{}' (expected, eg, '1234')", raw)
+ ).into())
+ }
+}
+
+pub fn check_wikidata_qid(raw: &str) -> Result<()> {
+ lazy_static! {
+ static ref RE: Regex = Regex::new(r"^Q\d+$").unwrap();
+ }
+ if RE.is_match(raw) {
+ Ok(())
+ } else {
+ Err(ErrorKind::MalformedExternalId(
+ format!("not a valid Wikidata QID: '{}' (expected, eg, 'Q1234')", raw)
+ ).into())
+ }
+}
+
+pub fn check_doi(raw: &str) -> Result<()> {
+ lazy_static! {
+ static ref RE: Regex = Regex::new(r"^10.\d{3,6}/.+$").unwrap();
+ }
+ if RE.is_match(raw) {
+ Ok(())
+ } else {
+ Err(ErrorKind::MalformedExternalId(
+ format!("not a valid DOI: '{}' (expected, eg, '10.1234/aksjdfh')", raw)
+ ).into())
+ }
+}
+
+pub fn check_issn(raw: &str) -> Result<()> {
+ lazy_static! {
+ static ref RE: Regex = Regex::new(r"^\d{4}-\d{3}[0-9X]$").unwrap();
+ }
+ if RE.is_match(raw) {
+ Ok(())
+ } else {
+ Err(ErrorKind::MalformedExternalId(
+ format!("not a valid ISSN: '{}' (expected, eg, '1234-5678')", raw)
+ ).into())
+ }
+}
+
+pub fn check_orcid(raw: &str) -> Result<()> {
+ lazy_static! {
+ static ref RE: Regex = Regex::new(r"^\d{4}-\d{4}-\d{4}-\d{4}$").unwrap();
+ }
+ if RE.is_match(raw) {
+ Ok(())
+ } else {
+ Err(ErrorKind::MalformedExternalId(
+ format!("not a valid ORCID: '{}' (expected, eg, '0123-4567-3456-6789')", raw)
+ ).into())
+ }
+}
+
+// TODO: make the above checks "more correct"
+// TODO: check ISBN-13
+// TODO: check hashes (SHA-1, etc)
diff --git a/rust/src/api_server.rs b/rust/src/api_server.rs
index 5aa075dd..64c028be 100644
--- a/rust/src/api_server.rs
+++ b/rust/src/api_server.rs
@@ -1,6 +1,6 @@
//! API endpoint handlers
-use api_helpers::{accept_editgroup, fcid2uuid, get_or_create_editgroup, uuid2fcid};
+use api_helpers::*;
use chrono;
use database_models::*;
use database_schema::{
@@ -95,6 +95,7 @@ fn container_row2entity(
};
Ok(ContainerEntity {
issnl: rev.issnl,
+ wikidata_qid: rev.wikidata_qid,
publisher: rev.publisher,
name: rev.name,
abbrev: rev.abbrev,
@@ -122,6 +123,7 @@ fn creator_row2entity(ident: Option<CreatorIdentRow>, rev: CreatorRevRow) -> Res
given_name: rev.given_name,
surname: rev.surname,
orcid: rev.orcid,
+ wikidata_qid: rev.wikidata_qid,
state: state,
ident: ident_id,
revision: Some(rev.id.to_string()),
@@ -249,6 +251,7 @@ fn release_row2entity(
pmid: rev.pmid,
pmcid: rev.pmcid,
isbn13: rev.isbn13,
+ wikidata_qid: rev.wikidata_qid,
volume: rev.volume,
issue: rev.issue,
pages: rev.pages,
@@ -304,6 +307,7 @@ impl Server {
pub fn lookup_container_handler(&self, issnl: &str) -> Result<ContainerEntity> {
let conn = self.db_pool.get().expect("db_pool error");
+ check_issn(issnl)?;
let (ident, rev): (ContainerIdentRow, ContainerRevRow) = container_ident::table
.inner_join(container_rev::table)
.filter(container_rev::issnl.eq(issnl))
@@ -329,6 +333,7 @@ impl Server {
pub fn lookup_creator_handler(&self, orcid: &str) -> Result<CreatorEntity> {
let conn = self.db_pool.get().expect("db_pool error");
+ check_orcid(orcid)?;
let (ident, rev): (CreatorIdentRow, CreatorRevRow) = creator_ident::table
.inner_join(creator_rev::table)
.filter(creator_rev::orcid.eq(orcid))
@@ -397,6 +402,7 @@ impl Server {
pub fn lookup_release_handler(&self, doi: &str) -> Result<ReleaseEntity> {
let conn = self.db_pool.get().expect("db_pool error");
+ check_doi(doi)?;
let (ident, rev): (ReleaseIdentRow, ReleaseRevRow) = release_ident::table
.inner_join(release_rev::table)
.filter(release_rev::doi.eq(doi))
@@ -472,20 +478,27 @@ impl Server {
None => get_or_create_editgroup(editor_id, &conn)?,
Some(param) => fcid2uuid(&param)?,
};
+ if let Some(ref extid) = entity.wikidata_qid {
+ check_wikidata_qid(extid)?;
+ }
+ if let Some(ref extid) = entity.issnl {
+ check_issn(extid)?;
+ }
let edit: ContainerEditRow = diesel::sql_query(
- "WITH rev AS ( INSERT INTO container_rev (name, publisher, issnl, abbrev, coden, extra_json)
- VALUES ($1, $2, $3, $4, $5, $6)
+ "WITH rev AS ( INSERT INTO container_rev (name, publisher, issnl, wikidata_qid, abbrev, coden, extra_json)
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id ),
ident AS ( INSERT INTO container_ident (rev_id)
VALUES ((SELECT rev.id FROM rev))
RETURNING id )
INSERT INTO container_edit (editgroup_id, ident_id, rev_id) VALUES
- ($7, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))
+ ($8, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))
RETURNING *",
).bind::<diesel::sql_types::Text, _>(entity.name)
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.publisher)
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.issnl)
+ .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.wikidata_qid)
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.abbrev)
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.coden)
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(entity.extra)
@@ -514,21 +527,28 @@ impl Server {
None => get_or_create_editgroup(editor_id, &conn).expect("current editgroup"),
Some(param) => fcid2uuid(&param)?,
};
+ if let Some(ref extid) = entity.orcid {
+ check_orcid(extid)?;
+ }
+ if let Some(ref extid) = entity.wikidata_qid {
+ check_wikidata_qid(extid)?;
+ }
let edit: CreatorEditRow = diesel::sql_query(
- "WITH rev AS ( INSERT INTO creator_rev (display_name, given_name, surname, orcid, extra_json)
- VALUES ($1, $2, $3, $4, $5)
+ "WITH rev AS ( INSERT INTO creator_rev (display_name, given_name, surname, orcid, wikidata_qid, extra_json)
+ VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id ),
ident AS ( INSERT INTO creator_ident (rev_id)
VALUES ((SELECT rev.id FROM rev))
RETURNING id )
INSERT INTO creator_edit (editgroup_id, ident_id, rev_id) VALUES
- ($6, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))
+ ($7, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))
RETURNING *",
).bind::<diesel::sql_types::Text, _>(entity.display_name)
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.given_name)
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.surname)
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.orcid)
+ .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.wikidata_qid)
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(entity.extra)
.bind::<diesel::sql_types::Uuid, _>(editgroup_id)
.get_result(conn)?;
@@ -644,6 +664,18 @@ impl Server {
None => get_or_create_editgroup(editor_id, &conn).expect("current editgroup"),
Some(param) => fcid2uuid(&param)?,
};
+ if let Some(ref extid) = entity.doi {
+ check_doi(extid)?;
+ }
+ if let Some(ref extid) = entity.pmid {
+ check_pmid(extid)?;
+ }
+ if let Some(ref extid) = entity.pmcid {
+ check_pmcid(extid)?;
+ }
+ if let Some(ref extid) = entity.wikidata_qid {
+ check_wikidata_qid(extid)?;
+ }
let work_id = match entity.work_id {
Some(work_id) => fcid2uuid(&work_id)?,
@@ -668,14 +700,14 @@ impl Server {
};
let edit: ReleaseEditRow = diesel::sql_query(
- "WITH rev AS ( INSERT INTO release_rev (title, release_type, release_status, release_date, doi, isbn13, volume, issue, pages, work_ident_id, container_ident_id, publisher, language, extra_json)
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
+ "WITH rev AS ( INSERT INTO release_rev (title, release_type, release_status, release_date, doi, pmid, pmcid, wikidata_qid, isbn13, volume, issue, pages, work_ident_id, container_ident_id, publisher, language, extra_json)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
RETURNING id ),
ident AS ( INSERT INTO release_ident (rev_id)
VALUES ((SELECT rev.id FROM rev))
RETURNING id )
INSERT INTO release_edit (editgroup_id, ident_id, rev_id) VALUES
- ($15, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))
+ ($18, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))
RETURNING *",
).bind::<diesel::sql_types::Text, _>(entity.title)
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.release_type)
@@ -683,6 +715,9 @@ impl Server {
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Date>, _>(
entity.release_date.map(|v| v.naive_utc().date()))
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.doi)
+ .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.pmid)
+ .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.pmcid)
+ .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.wikidata_qid)
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.isbn13)
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.volume)
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.issue)
diff --git a/rust/src/api_wrappers.rs b/rust/src/api_wrappers.rs
index 8be661e1..e66f3ccd 100644
--- a/rust/src/api_wrappers.rs
+++ b/rust/src/api_wrappers.rs
@@ -35,6 +35,8 @@ macro_rules! wrap_entity_handlers {
Err(Error(ErrorKind::InvalidFatcatId(e), _)) =>
$get_resp::BadRequest(ErrorResponse {
message: ErrorKind::InvalidFatcatId(e).to_string() }),
+ Err(Error(ErrorKind::MalformedExternalId(e), _)) =>
+ $get_resp::BadRequest(ErrorResponse { message: e.to_string() }),
Err(e) => {
error!("{}", e);
$get_resp::GenericError(ErrorResponse { message: e.to_string() })
@@ -58,6 +60,8 @@ macro_rules! wrap_entity_handlers {
Err(Error(ErrorKind::InvalidFatcatId(e), _)) =>
$post_resp::BadRequest(ErrorResponse {
message: ErrorKind::InvalidFatcatId(e).to_string() }),
+ Err(Error(ErrorKind::MalformedExternalId(e), _)) =>
+ $post_resp::BadRequest(ErrorResponse { message: e.to_string() }),
Err(e) => {
error!("{}", e);
$post_resp::GenericError(ErrorResponse { message: e.to_string() })
@@ -81,6 +85,8 @@ macro_rules! wrap_entity_handlers {
Err(Error(ErrorKind::InvalidFatcatId(e), _)) =>
$post_batch_resp::BadRequest(ErrorResponse {
message: ErrorKind::InvalidFatcatId(e).to_string() }),
+ Err(Error(ErrorKind::MalformedExternalId(e), _)) =>
+ $post_batch_resp::BadRequest(ErrorResponse { message: e.to_string() }),
Err(e) => {
error!("{}", e);
$post_batch_resp::GenericError(ErrorResponse { message: e.to_string() })
@@ -127,6 +133,8 @@ macro_rules! wrap_lookup_handler {
$get_resp::FoundEntity(entity),
Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) =>
$get_resp::NotFound(ErrorResponse { message: format!("Not found: {}", $idname) }),
+ Err(Error(ErrorKind::MalformedExternalId(e), _)) =>
+ $get_resp::BadRequest(ErrorResponse { message: e.to_string() }),
Err(e) => {
error!("{}", e);
$get_resp::BadRequest(ErrorResponse { message: e.to_string() })
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index fd871f55..eff487b3 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -17,6 +17,9 @@ extern crate serde_json;
#[macro_use]
extern crate log;
extern crate data_encoding;
+extern crate regex;
+#[macro_use]
+extern crate lazy_static;
pub mod api_helpers;
pub mod api_server;
@@ -39,6 +42,10 @@ mod errors {
description("invalid fatcat identifier syntax")
display("invalid fatcat identifier (expect 26-char base32 encoded): {}", id)
}
+ MalformedExternalId(id: String) {
+ description("external identifier doesn't match required pattern")
+ display("external identifier doesn't match required pattern")
+ }
}
}
}
diff --git a/rust/tests/test_api_server.rs b/rust/tests/test_api_server.rs
index b831f122..b9880ba1 100644
--- a/rust/tests/test_api_server.rs
+++ b/rust/tests/test_api_server.rs
@@ -299,7 +299,7 @@ fn test_post_creator() {
#[test]
fn test_post_file() {
- let (headers, router, _conn) = setup();
+ let (headers, router, conn) = setup();
check_response(
request::post(
@@ -315,12 +315,15 @@ fn test_post_file() {
check_response(
request::post(
"http://localhost:9411/v0/file",
- headers,
+ headers.clone(),
r#"{"size": 76543,
- "sha1": "f013d66c7f6817d08b7eb2a93e6d0440c1f3e7f8",
+ "sha1": "f0000000000000008b7eb2a93e6d0440c1f3e7f8",
"md5": "0b6d347b01d437a092be84c2edfce72c",
"sha256": "a77e4c11a57f1d757fca5754a8f83b5d4ece49a2d28596889127c1a2f3f28832",
- "url": "http://archive.org/asdf.txt",
+ "urls": [
+ {"url": "http://archive.org/asdf.txt", "rel": "web" },
+ {"url": "http://web.archive.org/2/http://archive.org/asdf.txt", "rel": "webarchive" }
+ ],
"mimetype": "application/pdf",
"releases": [
"aaaaaaaaaaaaarceaaaaaaaaae",
@@ -332,7 +335,33 @@ fn test_post_file() {
),
status::Created,
None,
- ); // TODO: "secret paper"
+ );
+
+ let editor_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001").unwrap();
+ let editgroup_id = get_or_create_editgroup(editor_id, &conn).unwrap();
+ check_response(
+ request::post(
+ &format!(
+ "http://localhost:9411/v0/editgroup/{}/accept",
+ uuid2fcid(&editgroup_id)
+ ),
+ headers.clone(),
+ "",
+ &router,
+ ),
+ status::Ok,
+ None,
+ );
+
+ check_response(
+ request::get(
+ "http://localhost:9411/v0/file/lookup?sha1=f0000000000000008b7eb2a93e6d0440c1f3e7f8",
+ headers.clone(),
+ &router,
+ ),
+ status::Ok,
+ Some("web.archive.org/2/http"),
+ );
}
#[test]
@@ -377,6 +406,9 @@ fn test_post_release() {
r#"{"title": "secret paper",
"release_type": "journal-article",
"doi": "10.1234/abcde.781231231239",
+ "pmid": "54321",
+ "pmcid": "PMC12345",
+ "wikidata_qid": "Q12345",
"volume": "439",
"issue": "IV",
"pages": "1-399",
@@ -637,3 +669,176 @@ fn test_edit_gets() {
None,
);
}
+
+#[test]
+fn test_bad_external_idents() {
+ let (headers, router, _conn) = setup();
+
+ // Bad wikidata QID
+ check_response(
+ request::post(
+ "http://localhost:9411/v0/release",
+ headers.clone(),
+ r#"{"title": "secret paper",
+ "wikidata_qid": "P12345"
+ }"#,
+ &router,
+ ),
+ status::BadRequest,
+ Some("Wikidata QID"),
+ );
+ check_response(
+ request::post(
+ "http://localhost:9411/v0/container",
+ headers.clone(),
+ r#"{"name": "my journal",
+ "wikidata_qid": "P12345"
+ }"#,
+ &router,
+ ),
+ status::BadRequest,
+ Some("Wikidata QID"),
+ );
+ check_response(
+ request::post(
+ "http://localhost:9411/v0/creator",
+ headers.clone(),
+ r#"{"display_name": "some body",
+ "wikidata_qid": "P12345"
+ }"#,
+ &router,
+ ),
+ status::BadRequest,
+ Some("Wikidata QID"),
+ );
+
+ // Bad PMCID
+ check_response(
+ request::post(
+ "http://localhost:9411/v0/release",
+ headers.clone(),
+ r#"{"title": "secret paper",
+ "pmcid": "12345"
+ }"#,
+ &router,
+ ),
+ status::BadRequest,
+ Some("PMCID"),
+ );
+
+ // Bad PMID
+ check_response(
+ request::post(
+ "http://localhost:9411/v0/release",
+ headers.clone(),
+ r#"{"title": "secret paper",
+ "pmid": "not-a-number"
+ }"#,
+ &router,
+ ),
+ status::BadRequest,
+ Some("PMID"),
+ );
+
+ // Bad DOI
+ check_response(
+ request::post(
+ "http://localhost:9411/v0/release",
+ headers.clone(),
+ r#"{"title": "secret paper",
+ "doi": "asdf"
+ }"#,
+ &router,
+ ),
+ status::BadRequest,
+ Some("DOI"),
+ );
+
+ // Good identifiers
+ check_response(
+ request::post(
+ "http://localhost:9411/v0/release",
+ headers.clone(),
+ r#"{"title": "secret paper",
+ "doi": "10.1234/abcde.781231231239",
+ "pmid": "54321",
+ "pmcid": "PMC12345",
+ "wikidata_qid": "Q12345"
+ }"#,
+ &router,
+ ),
+ status::Created,
+ None,
+ );
+}
+
+#[test]
+fn test_abstracts() {
+ let (headers, router, conn) = setup();
+
+ check_response(
+ request::post(
+ "http://localhost:9411/v0/release",
+ headers.clone(),
+ r#"{"title": "some paper",
+ "doi": "10.1234/iiiiiii",
+ "abstracts": [
+ {"lang": "zh",
+ "mimetype": "text/plain",
+ "content": "some rando abstract 24iu3i25u2" },
+ {"lang": "en",
+ "mimetype": "application/xml+jats",
+ "content": "some other abstract 99139405" }
+ ]
+ }"#,
+ &router,
+ ),
+ status::Created,
+ None,
+ );
+
+ let editor_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001").unwrap();
+ let editgroup_id = get_or_create_editgroup(editor_id, &conn).unwrap();
+ check_response(
+ request::post(
+ &format!(
+ "http://localhost:9411/v0/editgroup/{}/accept",
+ uuid2fcid(&editgroup_id)
+ ),
+ headers.clone(),
+ "",
+ &router,
+ ),
+ status::Ok,
+ None,
+ );
+
+ check_response(
+ request::get(
+ "http://localhost:9411/v0/release/lookup?doi=10.1234/iiiiiii",
+ headers.clone(),
+ &router,
+ ),
+ status::Ok,
+ // SHA-1 of first abstract string
+ Some("4e30ded694c6a7775b9e7b019dfda6be0dd60944"),
+ );
+ check_response(
+ request::get(
+ "http://localhost:9411/v0/release/lookup?doi=10.1234/iiiiiii",
+ headers.clone(),
+ &router,
+ ),
+ status::Ok,
+ Some("99139405"),
+ );
+ check_response(
+ request::get(
+ "http://localhost:9411/v0/release/lookup?doi=10.1234/iiiiiii",
+ headers.clone(),
+ &router,
+ ),
+ status::Ok,
+ Some("24iu3i25u2"),
+ );
+}