diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e74992b0079cb02be4629dc6f565159e8cc0c794..5177bc91c4aac2ff10bed8d8975da55ea61f4889 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -100,7 +100,8 @@ tests:linux64:stable: script: - cd bin/dunitrust-server - RUSTFLAGS="-D warnings" cargo build --features=ssl - - cargo test --all + - cargo test --all --exclude durs-gva + - cargo test --package durs-gva -- --test-threads=1 - cargo test --all -- --ignored tests:arm-v7-:stable: @@ -114,7 +115,6 @@ tests:arm-v7-:stable: script: - cd bin/dunitrust-server - RUSTFLAGS="-D warnings" cargo build --target=armv7-unknown-linux-gnueabihf --features=ssl - - cargo test --all --target=armv7-unknown-linux-gnueabihf tests:arm-v7:stable: extends: .rust_stable_armv7 @@ -124,7 +124,6 @@ tests:arm-v7:stable: script: - cd bin/dunitrust-server - RUSTFLAGS="-D warnings" cargo build --target=armv7-unknown-linux-gnueabihf --features=ssl - - cargo test --all --target=armv7-unknown-linux-gnueabihf tests:win64:stable: extends: .rust_stable_win64 @@ -143,6 +142,7 @@ tests:win64:stable: - cargo test --package durs-blockchain --target=x86_64-pc-windows-gnu - cargo test --package durs-dbs-tools --target=x86_64-pc-windows-gnu #- cargo test --package durs-skeleton-module --target=x86_64-pc-windows-gnu + - cargo test --package durs-gva --target=x86_64-pc-windows-gnu -- --test-threads=1 - cargo test --package durs-ws2p-v1-legacy --target=x86_64-pc-windows-gnu - cargo test --package durs-ws2p --target=x86_64-pc-windows-gnu - cargo test --package durs-ws2p-messages --target=x86_64-pc-windows-gnu diff --git a/lib/modules-lib/bc-db-reader/src/lib.rs b/lib/modules-lib/bc-db-reader/src/lib.rs index 00297e27d45be54b9298d1a7aa0c787039caec7d..cd9a1ebb865bac6452ed4fddf0e1fe4389535cae 100644 --- a/lib/modules-lib/bc-db-reader/src/lib.rs +++ b/lib/modules-lib/bc-db-reader/src/lib.rs @@ -36,8 +36,8 @@ pub mod paging; pub mod tools; pub use durs_dbs_tools::kv_db::{ - KvFileDbRead as DbReadable, KvFileDbRoHandler as BcDbRo, KvFileDbSchema, KvFileDbStoreType, - KvFileDbValue as DbValue, Readable as DbReader, + KvFileDbRead as DbReadable, KvFileDbReader as Reader, KvFileDbRoHandler as BcDbRo, + KvFileDbSchema, KvFileDbStoreType, KvFileDbValue as DbValue, Readable as DbReader, }; pub use durs_dbs_tools::DbError; diff --git a/lib/modules/gva/Cargo.toml b/lib/modules/gva/Cargo.toml index 894509e341cdc6f2b1ba8add668ade7a05bed4d6..32945b0cfd9597fc6bc7df66acd789153a0f6695 100644 --- a/lib/modules/gva/Cargo.toml +++ b/lib/modules/gva/Cargo.toml @@ -12,6 +12,7 @@ path = "src/lib.rs" [dependencies] actix-web = "1.0.8" dubp-block-doc = { path = "../../dubp/block-doc"} #, version = "0.1.0" } +dup-crypto = { path = "../../crypto" } durs-bc-db-reader = { path = "../../modules-lib/bc-db-reader" } durs-conf = { path = "../../core/conf" } durs-message = { path = "../../core/message" } @@ -21,6 +22,7 @@ durs-network-documents = { path = "../../dunp/network-documents" } dubp-common-doc = { path = "../../dubp/common-doc"} #, version = "0.1.0" } durs-common-tools = { path = "../../tools/common-tools" } dubp-currency-params = { path = "../../dubp/currency-params" } +cfg-if = "0.1.10" chrono = "0.4.9" failure = "0.1.5" futures = "0.1" @@ -34,4 +36,10 @@ serde_derive = "1.0.102" serde_json = "1.0.41" structopt= "0.3.4" +[dev-dependencies] +dubp-blocks-tests-tools = { path = "../../tests-tools/blocks-tests-tools" } +dup-crypto-tests-tools = { path = "../../tests-tools/crypto-tests-tools" } +mockall = "0.5.2" +pretty_assertions = "0.5.1" + [features] diff --git a/lib/modules/gva/src/context.rs b/lib/modules/gva/src/context.rs index 6e27d67882f1f89ff433faf364038eac69aa633b..4604812af79eafd6220341ae6b517a322cc4cb14 100644 --- a/lib/modules/gva/src/context.rs +++ b/lib/modules/gva/src/context.rs @@ -13,15 +13,23 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. +#[cfg(not(test))] use durs_bc_db_reader::BcDbRo; use durs_common_tools::fatal_error; +#[cfg(test)] +use crate::db::MockBcDbTrait; + /// GVA context (access to database) static mut CONTEXT: Option<Context> = None; -#[derive(Debug)] +#[cfg(not(test))] +pub type DB = BcDbRo; +#[cfg(test)] +pub(crate) type DB = MockBcDbTrait; + pub struct Context { - db: BcDbRo, + db: DB, software_name: &'static str, software_version: &'static str, } @@ -29,7 +37,7 @@ pub struct Context { impl juniper::Context for Context {} impl Context { - pub fn new(db: BcDbRo, software_name: &'static str, software_version: &'static str) -> Self { + pub(crate) fn new(db: DB, software_name: &'static str, software_version: &'static str) -> Self { Context { db, software_name, @@ -37,7 +45,7 @@ impl Context { } } - pub fn get_db(&self) -> &BcDbRo { + pub(crate) fn get_db(&self) -> &DB { &self.db } @@ -50,7 +58,7 @@ impl Context { } } -pub fn init(db: BcDbRo, soft_name: &'static str, soft_version: &'static str) { +pub(crate) fn init(db: DB, soft_name: &'static str, soft_version: &'static str) { unsafe { CONTEXT.replace(Context::new(db, soft_name, soft_version)); } diff --git a/lib/modules/gva/src/db.rs b/lib/modules/gva/src/db.rs new file mode 100644 index 0000000000000000000000000000000000000000..9fcfac6484b5082fd2bed40ad9a1486ca1274fc9 --- /dev/null +++ b/lib/modules/gva/src/db.rs @@ -0,0 +1,91 @@ +// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// +// 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 <https://www.gnu.org/licenses/>. + +//! Gva Module: database requests + +pub use durs_bc_db_reader::DbError; + +use dubp_common_doc::{BlockNumber, Blockstamp}; +use durs_bc_db_reader::blocks::DbBlock; +use durs_bc_db_reader::{BcDbRo, DbReadable}; +use std::ops::Range; + +#[cfg(test)] +use mockall::predicate::*; +#[cfg(test)] +use mockall::*; + +#[cfg_attr(test, automock)] +pub(crate) trait BcDbTrait { + fn get_current_blockstamp(&self) -> Result<Option<Blockstamp>, DbError>; + fn get_current_block(&self) -> Result<Option<DbBlock>, DbError>; + fn get_db_block_in_local_blockchain( + &self, + block_number: BlockNumber, + ) -> Result<Option<DbBlock>, DbError>; + fn get_db_blocks_in_local_blockchain(&self, range: Range<u32>) + -> Result<Vec<DbBlock>, DbError>; +} + +impl<'a> BcDbTrait for BcDbRo { + #[inline] + fn get_current_blockstamp(&self) -> Result<Option<Blockstamp>, DbError> { + self.read(|r| durs_bc_db_reader::current_meta_datas::get_current_blockstamp_(self, r)) + } + fn get_current_block(&self) -> Result<Option<DbBlock>, DbError> { + self.read(|r| { + if let Some(current_blockstamp) = + durs_bc_db_reader::current_meta_datas::get_current_blockstamp_(self, r)? + { + durs_bc_db_reader::blocks::get_db_block_in_local_blockchain( + self, + r, + current_blockstamp.id, + ) + } else { + Ok(None) + } + }) + } + #[inline] + fn get_db_block_in_local_blockchain( + &self, + block_number: BlockNumber, + ) -> Result<Option<DbBlock>, DbError> { + self.read(|r| { + durs_bc_db_reader::blocks::get_db_block_in_local_blockchain(self, r, block_number) + }) + } + fn get_db_blocks_in_local_blockchain( + &self, + range: Range<u32>, + ) -> Result<Vec<DbBlock>, DbError> { + self.read(|r| { + range + .filter_map(|n| { + match durs_bc_db_reader::blocks::get_db_block_in_local_blockchain( + self, + r, + BlockNumber(n), + ) { + Ok(Some(db_block)) => Some(Ok(db_block)), + Ok(None) => None, + Err(e) => Some(Err(e)), + } + }) + .collect::<Result<Vec<DbBlock>, DbError>>() + }) + } +} diff --git a/lib/modules/gva/src/graphql.rs b/lib/modules/gva/src/graphql.rs new file mode 100644 index 0000000000000000000000000000000000000000..b2fa335a752a6746e1948b7dc78431a8aa223067 --- /dev/null +++ b/lib/modules/gva/src/graphql.rs @@ -0,0 +1,40 @@ +// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// +// 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 <https://www.gnu.org/licenses/>. +// web server implementaion based on actix-web + +//! Module that execute graphql queries + +use crate::schema::Schema; +use actix_web::{web, Error, HttpResponse}; +use futures::future::Future; +use juniper::http::GraphQLRequest; +use std::sync::Arc; + +pub(crate) fn graphql( + schema: web::Data<Arc<Schema>>, + data: web::Json<GraphQLRequest>, +) -> impl Future<Item = HttpResponse, Error = Error> { + let context = crate::context::get_context(); + web::block(move || { + let result = data.execute(&schema, context); + serde_json::to_string(&result) + }) + .map_err(Error::from) + .and_then(|user| { + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(user)) + }) +} diff --git a/lib/modules/gva/src/lib.rs b/lib/modules/gva/src/lib.rs index 1e71e6f6f0d39d9df2405db791c342ebc7453bb2..00826f15d19c69c33fb864dcdd1de9fe8595c9d9 100644 --- a/lib/modules/gva/src/lib.rs +++ b/lib/modules/gva/src/lib.rs @@ -14,14 +14,14 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. //! Gva Module -// This module provides a graphql API implementation of the 0003 RFC -// -// /src/schema.gql contains schema description -// /src/schema.rs contains model and resolvers implementation -// /src/webserver.rs contains web server implementaion based on actix-web -// -// Graphiql web client is accessible at -// http://127.0.0.1:3000/graphiql +//! This module provides a graphql API implementation of the 0003 RFC +//! +//! /src/schema.gql contains schema description +//! /src/schema.rs contains model and resolvers implementation +//! /src/webserver.rs contains web server implementaion based on actix-web +//! +//! Graphiql web client is accessible at +//! http://127.0.0.1:10901/graphiql #![deny( missing_docs, @@ -44,7 +44,9 @@ extern crate structopt; extern crate juniper; mod context; +mod db; mod errors; +mod graphql; mod schema; mod webserver; diff --git a/lib/modules/gva/src/schema.rs b/lib/modules/gva/src/schema.rs index b41ccf9eaa6cdc61e777523cfdcdcaac4dc4a991..18bc55508a87bfa0f9325466e7c007e0e4e5d851 100644 --- a/lib/modules/gva/src/schema.rs +++ b/lib/modules/gva/src/schema.rs @@ -13,15 +13,15 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. -// ! model and resolvers implementation +// ! Module define GraphQl schema -mod block; +mod entities; mod paging; +mod queries; -use self::block::Block; +use self::entities::block::Block; +use self::entities::node::{Node, Summary}; use crate::context::Context; -use dubp_common_doc::BlockNumber; -use durs_bc_db_reader::{BcDbRo, DbError, DbReadable}; use juniper::Executor; use juniper::FieldResult; use juniper_from_schema::graphql_schema_from_file; @@ -31,104 +31,40 @@ graphql_schema_from_file!("resources/schema.gql"); pub struct Query; -pub struct Summary { - software: &'static str, - version: &'static str, -} - -pub struct Node { - summary: Summary, -} - -fn db_err_to_juniper_err(e: DbError) -> juniper::FieldError { - juniper::FieldError::from(format!("Db error: {:?}", e)) -} - impl QueryFields for Query { + #[inline] fn field_node( &self, executor: &Executor<'_, Context>, - _trail: &QueryTrail<'_, Node, Walked>, + trail: &QueryTrail<'_, Node, Walked>, ) -> FieldResult<Node> { - Ok(Node { - summary: Summary { - software: executor.context().get_software_name(), - version: executor.context().get_software_version(), - }, - }) + queries::node::execute(executor, trail) } + #[inline] fn field_current( &self, executor: &Executor<'_, Context>, - _trail: &QueryTrail<'_, Block, Walked>, + trail: &QueryTrail<'_, Block, Walked>, ) -> FieldResult<Option<Block>> { - let db: &BcDbRo = &executor.context().get_db(); - - db.read(|r| { - if let Some(current_blockstamp) = - durs_bc_db_reader::current_meta_datas::get_current_blockstamp_(db, r)? - { - block::get_block(db, r, current_blockstamp.id) - } else { - Ok(None) - } - }) - .map_err(db_err_to_juniper_err) + queries::current::execute(executor, trail) } + #[inline] fn field_block( &self, executor: &Executor<'_, Context>, - _trail: &QueryTrail<'_, Block, Walked>, + trail: &QueryTrail<'_, Block, Walked>, number: i32, ) -> FieldResult<Option<Block>> { - let db: &BcDbRo = &executor.context().get_db(); - - let block_number = if number >= 0 { - BlockNumber(number as u32) - } else { - return Err(juniper::FieldError::from("Block number must be positive.")); - }; - - db.read(|r| block::get_block(db, r, block_number)) - .map_err(db_err_to_juniper_err) + queries::block::execute(executor, trail, number) } + #[inline] fn field_blocks( &self, executor: &Executor<'_, Context>, - _trail: &QueryTrail<'_, Block, Walked>, + trail: &QueryTrail<'_, Block, Walked>, paging_opt: Option<Paging>, ) -> FieldResult<Vec<Block>> { - let db: &BcDbRo = &executor.context().get_db(); - db.read(|r| { - paging::FilledPaging::new(db, r, paging_opt)? - .get_range() - .filter_map(|n| match block::get_block(db, r, BlockNumber(n)) { - Ok(Some(db_block)) => Some(Ok(db_block)), - Ok(None) => None, - Err(e) => Some(Err(e)), - }) - .collect::<Result<Vec<Block>, DbError>>() - }) - .map_err(db_err_to_juniper_err) - } -} - -impl NodeFields for Node { - fn field_summary( - &self, - _executor: &Executor<'_, Context>, - _trail: &QueryTrail<'_, Summary, Walked>, - ) -> &Summary { - &self.summary - } -} - -impl SummaryFields for Summary { - fn field_software(&self, _executor: &Executor<'_, Context>) -> String { - self.software.to_owned() - } - fn field_version(&self, _executor: &Executor<'_, Context>) -> String { - self.version.to_owned() + queries::blocks::execute(executor, trail, paging_opt) } } diff --git a/lib/modules/gva/src/schema/entities.rs b/lib/modules/gva/src/schema/entities.rs new file mode 100644 index 0000000000000000000000000000000000000000..a6ec3bb52a566a59449d367e9c2cb8fbb1a3a835 --- /dev/null +++ b/lib/modules/gva/src/schema/entities.rs @@ -0,0 +1,19 @@ +// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// +// 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 <https://www.gnu.org/licenses/>. + +// ! Module define GraphQl schema entities + +pub mod block; +pub mod node; diff --git a/lib/modules/gva/src/schema/block.rs b/lib/modules/gva/src/schema/entities/block.rs similarity index 64% rename from lib/modules/gva/src/schema/block.rs rename to lib/modules/gva/src/schema/entities/block.rs index b155e8d5453a2b0eb9906417a92757c0ef2f709c..3a2900a3b542538ff5bd724b75d52dae5549fecf 100644 --- a/lib/modules/gva/src/schema/block.rs +++ b/lib/modules/gva/src/schema/entities/block.rs @@ -13,14 +13,13 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. -// ! Block model and resolvers +// ! Module define graphql Block type use crate::context::Context; use chrono::NaiveDateTime; use dubp_block_doc::block::BlockDocumentTrait; use dubp_common_doc::traits::Document; -use dubp_common_doc::BlockNumber; -use durs_bc_db_reader::{BcDbRo, DbError, DbReader}; +use durs_bc_db_reader::blocks::DbBlock; use durs_common_tools::fatal_error; use juniper::{Executor, FieldResult}; @@ -33,7 +32,7 @@ pub struct Block { common_time: NaiveDateTime, } -impl super::BlockFields for Block { +impl super::super::BlockFields for Block { fn field_version(&self, _executor: &Executor<'_, Context>) -> FieldResult<&i32> { Ok(&self.version) } @@ -59,25 +58,19 @@ impl super::BlockFields for Block { } } -pub fn get_block<R: DbReader>( - db: &BcDbRo, - r: &R, - block_number: BlockNumber, -) -> Result<Option<Block>, DbError> { - durs_bc_db_reader::blocks::get_db_block_in_local_blockchain(db, r, block_number).map( - |block_opt| { - block_opt.map(|db_block| Block { - version: db_block.block.version() as i32, - currency: db_block.block.currency().to_string(), - issuer: db_block.block.issuers()[0].to_string(), - number: db_block.block.number().0 as i32, - hash: db_block - .block - .hash() - .unwrap_or_else(|| fatal_error!("DbBlock without hash.")) - .to_string(), - common_time: NaiveDateTime::from_timestamp(db_block.block.common_time() as i64, 0), - }) - }, - ) +impl Block { + pub fn from_db_block(db_block: DbBlock) -> Block { + Block { + version: db_block.block.version() as i32, + currency: db_block.block.currency().to_string(), + issuer: db_block.block.issuers()[0].to_string(), + number: db_block.block.number().0 as i32, + hash: db_block + .block + .hash() + .unwrap_or_else(|| fatal_error!("DbBlock without hash.")) + .to_string(), + common_time: NaiveDateTime::from_timestamp(db_block.block.common_time() as i64, 0), + } + } } diff --git a/lib/modules/gva/src/schema/entities/node.rs b/lib/modules/gva/src/schema/entities/node.rs new file mode 100644 index 0000000000000000000000000000000000000000..be7668bc595c7ce74ced7ac8e6b683ea6fb27a8f --- /dev/null +++ b/lib/modules/gva/src/schema/entities/node.rs @@ -0,0 +1,48 @@ +// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// +// 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 <https://www.gnu.org/licenses/>. + +// ! Module define graphql Node type and subtypes + +use crate::context::Context; +use juniper::Executor; +use juniper_from_schema::{QueryTrail, Walked}; + +pub struct Summary { + pub software: &'static str, + pub version: &'static str, +} + +pub struct Node { + pub summary: Summary, +} + +impl super::super::NodeFields for Node { + fn field_summary( + &self, + _executor: &Executor<'_, Context>, + _trail: &QueryTrail<'_, Summary, Walked>, + ) -> &Summary { + &self.summary + } +} + +impl super::super::SummaryFields for Summary { + fn field_software(&self, _executor: &Executor<'_, Context>) -> String { + self.software.to_owned() + } + fn field_version(&self, _executor: &Executor<'_, Context>) -> String { + self.version.to_owned() + } +} diff --git a/lib/modules/gva/src/schema/paging.rs b/lib/modules/gva/src/schema/paging.rs index af47c177c1a088d2cb129d24c9f63a6b625829ba..4b85efa8d6222c3914e373636c93aca8cad52d56 100644 --- a/lib/modules/gva/src/schema/paging.rs +++ b/lib/modules/gva/src/schema/paging.rs @@ -16,16 +16,15 @@ // ! Schema paging input use super::Paging; -use durs_bc_db_reader::{BcDbRo, DbError, DbReader}; +use crate::db::BcDbTrait; +use durs_bc_db_reader::DbError; use std::ops::Range; const DEFAULT_PAGE_NUMBER: i32 = 0; const DEFAULT_PAGE_SIZE: i32 = 50; const DEFAULT_FROM_BLOCK: i32 = 0; -const MAX_PAGE_NUMBER: i32 = std::i32::MAX; const MAX_PAGE_SIZE: i32 = 500; -const MAX_FROM_BLOCK: i32 = std::i32::MAX; /// Paging with all values filled in pub struct FilledPaging { @@ -49,25 +48,16 @@ fn i32_opt_to_positive_i32(int_opt: Option<i32>, default: i32) -> i32 { } impl FilledPaging { - pub fn new<R: DbReader>( - db: &BcDbRo, - r: &R, - paging_opt: Option<Paging>, - ) -> Result<Self, DbError> { + pub(crate) fn new<DB: BcDbTrait>(db: &DB, paging_opt: Option<Paging>) -> Result<Self, DbError> { if let Some(paging) = paging_opt { Ok(FilledPaging { - page_number: std::cmp::min( - MAX_PAGE_NUMBER, - i32_opt_to_positive_i32(paging.page_number, DEFAULT_PAGE_NUMBER), - ) as usize, + page_number: i32_opt_to_positive_i32(paging.page_number, DEFAULT_PAGE_NUMBER) + as usize, page_size: std::cmp::min( MAX_PAGE_SIZE, i32_opt_to_positive_i32(paging.page_size, DEFAULT_PAGE_SIZE), ) as usize, - from_block: std::cmp::min( - MAX_FROM_BLOCK, - i32_opt_to_positive_i32(paging.from_block, DEFAULT_FROM_BLOCK), - ) as u32, + from_block: i32_opt_to_positive_i32(paging.from_block, DEFAULT_FROM_BLOCK) as u32, to_block: if let Some(to_block) = paging.to_block { if to_block < 0 { 0 @@ -75,7 +65,7 @@ impl FilledPaging { to_block as u32 } } else { - Self::get_default_to_block(db, r)? + Self::get_default_to_block(db)? }, }) } else { @@ -83,14 +73,12 @@ impl FilledPaging { page_number: DEFAULT_PAGE_NUMBER as usize, page_size: DEFAULT_PAGE_SIZE as usize, from_block: DEFAULT_FROM_BLOCK as u32, - to_block: Self::get_default_to_block(db, r)?, + to_block: Self::get_default_to_block(db)?, }) } } - fn get_default_to_block<R: DbReader>(db: &BcDbRo, r: &R) -> Result<u32, DbError> { - if let Some(current_blockstamp) = - durs_bc_db_reader::current_meta_datas::get_current_blockstamp_(db, r)? - { + fn get_default_to_block<DB: BcDbTrait>(db: &DB) -> Result<u32, DbError> { + if let Some(current_blockstamp) = db.get_current_blockstamp()? { Ok(current_blockstamp.id.0) } else { Ok(0) @@ -106,3 +94,102 @@ impl FilledPaging { } } } + +#[cfg(test)] +mod tests { + + use super::*; + use crate::db::MockBcDbTrait; + use dubp_common_doc::{BlockHash, BlockNumber, Blockstamp}; + + #[test] + fn test_i32_opt_to_positive_i32() { + assert_eq!(3, i32_opt_to_positive_i32(Some(3), 1)); + assert_eq!(0, i32_opt_to_positive_i32(Some(-2), 1)); + assert_eq!(50, i32_opt_to_positive_i32(None, 50)); + assert_eq!(0, i32_opt_to_positive_i32(Some(0), 1)); + } + + #[test] + fn test_filled_paging_range_with_short_bc() -> Result<(), DbError> { + let mut mock_db = MockBcDbTrait::new(); + mock_db + .expect_get_current_blockstamp() + .times(1) + .returning(|| { + Ok(Some(Blockstamp { + id: BlockNumber(42), + hash: BlockHash(dup_crypto::hashs::Hash::default()), + })) + }); + + let filled_paging = FilledPaging::new(&mock_db, None)?; + assert_eq! { + Range { + start: 0, + end: 43, + }, + filled_paging.get_range() + } + Ok(()) + } + + #[test] + fn test_filled_paging_range() -> Result<(), DbError> { + let mut mock_db = MockBcDbTrait::new(); + mock_db + .expect_get_current_blockstamp() + .times(3) + .returning(|| { + Ok(Some(Blockstamp { + id: BlockNumber(750), + hash: BlockHash(dup_crypto::hashs::Hash::default()), + })) + }); + + let filled_paging = FilledPaging::new(&mock_db, None)?; + assert_eq! { + Range { + start: 0, + end: 50, + }, + filled_paging.get_range() + } + + let filled_paging = FilledPaging::new( + &mock_db, + Some(Paging { + page_number: Some(1), + page_size: Some(100), + from_block: Some(500), + to_block: None, + }), + )?; + assert_eq! { + Range { + start: 600, + end: 700, + }, + filled_paging.get_range() + } + + let filled_paging = FilledPaging::new( + &mock_db, + Some(Paging { + page_number: Some(2), + page_size: Some(100), + from_block: Some(500), + to_block: None, + }), + )?; + assert_eq! { + Range { + start: 700, + end: 751, + }, + filled_paging.get_range() + } + + Ok(()) + } +} diff --git a/lib/modules/gva/src/schema/queries.rs b/lib/modules/gva/src/schema/queries.rs new file mode 100644 index 0000000000000000000000000000000000000000..6a30b6653ae34f7f1d4fd398d3410b0ee490d66f --- /dev/null +++ b/lib/modules/gva/src/schema/queries.rs @@ -0,0 +1,75 @@ +// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// +// 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 <https://www.gnu.org/licenses/>. + +// ! Module execute GraphQl schema queries + +pub mod block; +pub mod blocks; +pub mod current; +pub mod node; + +use durs_bc_db_reader::DbError; + +pub(crate) fn db_err_to_juniper_err(e: DbError) -> juniper::FieldError { + juniper::FieldError::from(format!("Db error: {:?}", e)) +} + +#[cfg(test)] +mod tests { + + use crate::context; + use crate::db::MockBcDbTrait; + use crate::graphql::graphql; + use crate::schema::{create_schema, Schema}; + use actix_web::dev::Body; + use actix_web::http; + use actix_web::test; + use actix_web::web; + use juniper::http::GraphQLRequest; + use pretty_assertions::assert_eq; + use std::str::FromStr; + use std::sync::Arc; + + pub(crate) fn setup(mock_db: MockBcDbTrait) -> web::Data<Arc<Schema>> { + context::init(mock_db, "soft_name", "soft_version"); + + web::Data::new(std::sync::Arc::new(create_schema())) + } + + pub(crate) fn test_gql_query( + schema: web::Data<Arc<Schema>>, + gql_query: &str, + expected_response: serde_json::Value, + ) { + let resp = test::block_on(graphql( + schema, + web::Json(GraphQLRequest::new(gql_query.to_owned(), None, None)), + )) + .unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + if let Some(Body::Bytes(ref body_bytes)) = resp.body().as_ref() { + assert_eq!( + expected_response, + serde_json::Value::from_str( + &String::from_utf8(body_bytes.to_vec()) + .expect("response have invalid utf8 format.") + ) + .expect("response have invalid JSON format.") + ) + } else { + panic!("Response must contain body in bytes format.") + } + } +} diff --git a/lib/modules/gva/src/schema/queries/block.rs b/lib/modules/gva/src/schema/queries/block.rs new file mode 100644 index 0000000000000000000000000000000000000000..73547478d374d493dd978374d0b5d1a655ff1161 --- /dev/null +++ b/lib/modules/gva/src/schema/queries/block.rs @@ -0,0 +1,114 @@ +// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// +// 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 <https://www.gnu.org/licenses/>. + +// ! Module execute GraphQl schema block query + +use super::db_err_to_juniper_err; +use crate::context::Context; +use crate::db::BcDbTrait; +use crate::schema::entities::block::Block; +use dubp_common_doc::BlockNumber; +use juniper::Executor; +use juniper::FieldResult; +use juniper_from_schema::{QueryTrail, Walked}; + +pub(crate) fn execute( + executor: &Executor<'_, Context>, + _trail: &QueryTrail<'_, Block, Walked>, + number: i32, +) -> FieldResult<Option<Block>> { + let block_number = if number >= 0 { + BlockNumber(number as u32) + } else { + return Err(juniper::FieldError::from("Block number must be positive.")); + }; + + executor + .context() + .get_db() + .get_db_block_in_local_blockchain(block_number) + .map_err(db_err_to_juniper_err) + .map(|db_block_opt| db_block_opt.map(Block::from_db_block)) +} + +#[cfg(test)] +mod tests { + use crate::db::MockBcDbTrait; + use crate::schema::queries::tests; + use dubp_block_doc::block::BlockDocument; + use dubp_blocks_tests_tools::mocks::gen_empty_timed_block_v10; + use dubp_common_doc::{BlockHash, BlockNumber, Blockstamp}; + use dup_crypto::hashs::Hash; + use dup_crypto_tests_tools::mocks::{hash, pubkey}; + use durs_bc_db_reader::blocks::DbBlock; + use mockall::predicate::eq; + use serde_json::json; + + #[test] + fn test_graphql_block() { + let mut mock_db = MockBcDbTrait::new(); + mock_db + .expect_get_db_block_in_local_blockchain() + .with(eq(BlockNumber(42))) + .returning(|_| { + let mut block = gen_empty_timed_block_v10( + Blockstamp { + id: BlockNumber(42), + hash: BlockHash(hash('A')), + }, + 1_488_987_127, + Hash::default(), + ); + block.issuers = vec![pubkey('B')]; + Ok(Some(DbBlock { + block: BlockDocument::V10(block), + expire_certs: None, + })) + }); + + let schema = tests::setup(mock_db); + + tests::test_gql_query( + schema.clone(), + "{ block { commonTime, currency, hash, issuer, number, version } }", + json!({ + "errors": [{ + "message": "Field \"block\" argument \"number\" of type \"Int!\" is required but not provided", + "locations": [{ + "line": 1, + "column": 3, + }] + }] + }), + ); + + tests::test_gql_query( + schema, + "{ block(number: 42) { commonTime, currency, hash, issuer, number, version } }", + json!({ + "data": { + "block": { + "commonTime": 1_488_987_127.0, + "currency": "test_currency", + "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "issuer": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "number": 42, + "version": 10 + } + } + }), + ); + } +} diff --git a/lib/modules/gva/src/schema/queries/blocks.rs b/lib/modules/gva/src/schema/queries/blocks.rs new file mode 100644 index 0000000000000000000000000000000000000000..fa88927b745c145ba5bc637382d4aea92f225952 --- /dev/null +++ b/lib/modules/gva/src/schema/queries/blocks.rs @@ -0,0 +1,168 @@ +// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// +// 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 <https://www.gnu.org/licenses/>. + +// ! Module execute GraphQl schema blocks query + +use super::db_err_to_juniper_err; +use crate::context::Context; +use crate::db::BcDbTrait; +use crate::schema::entities::block::Block; +use crate::schema::paging; +use crate::schema::Paging; +use durs_bc_db_reader::blocks::DbBlock; +use juniper::Executor; +use juniper::FieldResult; +use juniper_from_schema::{QueryTrail, Walked}; + +pub(crate) fn execute( + executor: &Executor<'_, Context>, + _trail: &QueryTrail<'_, Block, Walked>, + paging_opt: Option<Paging>, +) -> FieldResult<Vec<Block>> { + let db = executor.context().get_db(); + + let blocks: Vec<DbBlock> = db + .get_db_blocks_in_local_blockchain( + paging::FilledPaging::new(db, paging_opt) + .map_err(db_err_to_juniper_err)? + .get_range(), + ) + .map_err(db_err_to_juniper_err)?; + + Ok(blocks.into_iter().map(Block::from_db_block).collect()) + + /*let db: &BcDbRo = &executor.context().get_db(); + db.read(|r| { + paging::FilledPaging::new(db, paging_opt)? + .get_range() + .filter_map(|n| match block::get_block(db, r, BlockNumber(n)) { + Ok(Some(db_block)) => Some(Ok(db_block)), + Ok(None) => None, + Err(e) => Some(Err(e)), + }) + .collect::<Result<Vec<Block>, DbError>>() + }) + .map_err(db_err_to_juniper_err)*/ +} + +#[cfg(test)] +mod tests { + use crate::db::MockBcDbTrait; + use crate::schema::queries::tests; + use dubp_block_doc::block::BlockDocument; + use dubp_blocks_tests_tools::mocks::gen_empty_timed_block_v10; + use dubp_common_doc::traits::Document; + use dubp_common_doc::{BlockHash, BlockNumber, Blockstamp}; + use dup_crypto::hashs::Hash; + use dup_crypto_tests_tools::mocks::{hash, pubkey}; + use durs_bc_db_reader::blocks::DbBlock; + use mockall::predicate::eq; + use serde_json::json; + use std::ops::Range; + + #[test] + fn test_graphql_blocks() { + let mut mock_db = MockBcDbTrait::new(); + + let mut block_0 = gen_empty_timed_block_v10( + Blockstamp { + id: BlockNumber(0), + hash: BlockHash(hash('A')), + }, + 1_488_987_127, + Hash::default(), + ); + block_0.issuers = vec![pubkey('A')]; + let mut block_1 = gen_empty_timed_block_v10( + Blockstamp { + id: BlockNumber(1), + hash: BlockHash(hash('B')), + }, + 1_488_987_128, + Hash::default(), + ); + block_1.issuers = vec![pubkey('B')]; + let mut current_block = gen_empty_timed_block_v10( + Blockstamp { + id: BlockNumber(2), + hash: BlockHash(hash('C')), + }, + 1_488_987_129, + Hash::default(), + ); + current_block.issuers = vec![pubkey('C')]; + + let current_blockstamp = current_block.blockstamp(); + mock_db + .expect_get_current_blockstamp() + .times(1) + .returning(move || Ok(Some(current_blockstamp))); + + mock_db + .expect_get_db_blocks_in_local_blockchain() + .with(eq(Range { start: 0, end: 3 })) + .returning(move |_| { + Ok(vec![ + DbBlock { + block: BlockDocument::V10(block_0.clone()), + expire_certs: None, + }, + DbBlock { + block: BlockDocument::V10(block_1.clone()), + expire_certs: None, + }, + DbBlock { + block: BlockDocument::V10(current_block.clone()), + expire_certs: None, + }, + ]) + }); + + let schema = tests::setup(mock_db); + + tests::test_gql_query( + schema, + "{ blocks { commonTime, currency, hash, issuer, number, version } }", + json!({ + "data": { + "blocks": [{ + "commonTime": 1_488_987_127.0, + "currency": "test_currency", + "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "issuer": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "number": 0, + "version": 10 + }, + { + "commonTime": 1_488_987_128.0, + "currency": "test_currency", + "hash": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "issuer": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "number": 1, + "version": 10 + }, + { + "commonTime": 1_488_987_129.0, + "currency": "test_currency", + "hash": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", + "issuer": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", + "number": 2, + "version": 10 + }] + } + }), + ); + } +} diff --git a/lib/modules/gva/src/schema/queries/current.rs b/lib/modules/gva/src/schema/queries/current.rs new file mode 100644 index 0000000000000000000000000000000000000000..309bc56df99fbf5abf9e999e842d2f87f4e36ba2 --- /dev/null +++ b/lib/modules/gva/src/schema/queries/current.rs @@ -0,0 +1,88 @@ +// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// +// 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 <https://www.gnu.org/licenses/>. + +// ! Module execute GraphQl schema current query + +use super::db_err_to_juniper_err; +use crate::context::Context; +use crate::db::BcDbTrait; +use crate::schema::entities::block::Block; +use juniper::Executor; +use juniper::FieldResult; +use juniper_from_schema::{QueryTrail, Walked}; + +pub(crate) fn execute( + executor: &Executor<'_, Context>, + _trail: &QueryTrail<'_, Block, Walked>, +) -> FieldResult<Option<Block>> { + executor + .context() + .get_db() + .get_current_block() + .map_err(db_err_to_juniper_err) + .map(|db_block_opt| db_block_opt.map(Block::from_db_block)) +} + +#[cfg(test)] +mod tests { + use crate::db::MockBcDbTrait; + use crate::schema::queries::tests; + use dubp_block_doc::block::BlockDocument; + use dubp_blocks_tests_tools::mocks::gen_empty_timed_block_v10; + use dubp_common_doc::{BlockHash, BlockNumber, Blockstamp}; + use dup_crypto::hashs::Hash; + use dup_crypto_tests_tools::mocks::{hash, pubkey}; + use durs_bc_db_reader::blocks::DbBlock; + use serde_json::json; + + #[test] + fn test_graphql_current() { + let mut mock_db = MockBcDbTrait::new(); + mock_db.expect_get_current_block().returning(|| { + let mut current_block = gen_empty_timed_block_v10( + Blockstamp { + id: BlockNumber(42), + hash: BlockHash(hash('A')), + }, + 1_488_987_127, + Hash::default(), + ); + current_block.issuers = vec![pubkey('B')]; + Ok(Some(DbBlock { + block: BlockDocument::V10(current_block), + expire_certs: None, + })) + }); + + let schema = tests::setup(mock_db); + + tests::test_gql_query( + schema, + "{ current { commonTime, currency, hash, issuer, number, version } }", + json!({ + "data": { + "current": { + "commonTime": 1_488_987_127.0, + "currency": "test_currency", + "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "issuer": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "number": 42, + "version": 10 + } + } + }), + ) + } +} diff --git a/lib/modules/gva/src/schema/queries/node.rs b/lib/modules/gva/src/schema/queries/node.rs new file mode 100644 index 0000000000000000000000000000000000000000..b9b797231c327df91b5e501e9dd1c4cb02f585ba --- /dev/null +++ b/lib/modules/gva/src/schema/queries/node.rs @@ -0,0 +1,61 @@ +// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// +// 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 <https://www.gnu.org/licenses/>. + +// ! Module execute GraphQl schema node query + +use crate::context::Context; +use crate::schema::entities::node::{Node, Summary}; +use juniper::Executor; +use juniper::FieldResult; +use juniper_from_schema::{QueryTrail, Walked}; + +pub(crate) fn execute( + executor: &Executor<'_, Context>, + _trail: &QueryTrail<'_, Node, Walked>, +) -> FieldResult<Node> { + Ok(Node { + summary: Summary { + software: executor.context().get_software_name(), + version: executor.context().get_software_version(), + }, + }) +} + +#[cfg(test)] +mod tests { + use crate::db::MockBcDbTrait; + use crate::schema::queries::tests; + use serde_json::json; + + #[test] + fn test_graphql_current() { + let schema = tests::setup(MockBcDbTrait::new()); + + tests::test_gql_query( + schema, + "{ node { summary { software, version } } }", + json!({ + "data": { + "node": { + "summary": { + "software": "soft_name", + "version": "soft_version" + } + } + } + }), + ) + } +} diff --git a/lib/modules/gva/src/webserver.rs b/lib/modules/gva/src/webserver.rs index fe06567436bcf6cd055ba5bc8ba28568531dc34e..309bc4ed2213133cac790a2a7f112e5cd56e2cd5 100644 --- a/lib/modules/gva/src/webserver.rs +++ b/lib/modules/gva/src/webserver.rs @@ -15,18 +15,20 @@ // web server implementaion based on actix-web use crate::context; -use crate::schema::{create_schema, Schema}; -use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer}; +use crate::graphql::graphql; +use crate::schema::create_schema; +use actix_web::{middleware, web, App, HttpResponse, HttpServer}; +#[cfg(not(test))] use durs_common_tools::fatal_error; use durs_conf::DuRsConf; use durs_module::SoftwareMetaDatas; use durs_network_documents::host::Host; use durs_network_documents::url::Url; -use futures::future::Future; use juniper::http::graphiql::graphiql_source; -use juniper::http::GraphQLRequest; use std::net::SocketAddr; -use std::sync::Arc; + +#[cfg(test)] +use crate::db::MockBcDbTrait; fn graphiql() -> HttpResponse { let html = graphiql_source("/graphql"); @@ -35,23 +37,6 @@ fn graphiql() -> HttpResponse { .body(html) } -fn graphql( - schema: web::Data<Arc<Schema>>, - data: web::Json<GraphQLRequest>, -) -> impl Future<Item = HttpResponse, Error = Error> { - let context = crate::context::get_context(); - web::block(move || { - let result = data.execute(&schema, context); - serde_json::to_string(&result) - }) - .map_err(Error::from) - .and_then(|user| { - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(user)) - }) -} - pub fn start_web_server( soft_meta_datas: &SoftwareMetaDatas<DuRsConf>, host: Host, @@ -65,13 +50,29 @@ pub fn start_web_server( // Create Juniper schema let schema = std::sync::Arc::new(create_schema()); - // Instanciate the context - let db_path = durs_conf::get_blockchain_db_path(soft_meta_datas.profile_path.clone()); - if let Ok(db) = durs_bc_db_reader::open_db_ro(&std::path::Path::new(&db_path)) { - context::init(db, soft_meta_datas.soft_name, soft_meta_datas.soft_version); - } else { - fatal_error!("GVA: fail to open DB."); + // Get DB + #[cfg(not(test))] + let db = { + let db_path = durs_conf::get_blockchain_db_path(soft_meta_datas.profile_path.clone()); + if let Ok(db) = durs_bc_db_reader::open_db_ro(&std::path::Path::new(&db_path)) { + db + } else { + fatal_error!("GVA: fail to open DB."); + } }; + #[cfg(test)] + let db = MockBcDbTrait::new(); + + cfg_if::cfg_if! { + if #[cfg(test)] { + MockBcDbTrait::new() + } else { + + } + }; + + // Instanciate the context + context::init(db, soft_meta_datas.soft_name, soft_meta_datas.soft_version); // Start http server HttpServer::new(move || {