From 1f62fb42662cdb2d5090831ed53edc6252783f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 27 Dec 2016 10:02:44 +0100 Subject: [PATCH 1/2] Customizable fetch --- dapps/src/apps/fetcher/mod.rs | 2 +- dapps/src/lib.rs | 42 ++++++++++++++++++++----------- dapps/src/tests/fetch.rs | 5 ++++ dapps/src/tests/helpers.rs | 17 +++++++------ parity/dapps.rs | 12 ++++----- parity/run.rs | 2 +- rpc/src/v1/tests/helpers/fetch.rs | 4 +++ util/fetch/src/client.rs | 12 +++++---- 8 files changed, 62 insertions(+), 34 deletions(-) diff --git a/dapps/src/apps/fetcher/mod.rs b/dapps/src/apps/fetcher/mod.rs index 3358af00c90..17539393c7b 100644 --- a/dapps/src/apps/fetcher/mod.rs +++ b/dapps/src/apps/fetcher/mod.rs @@ -224,7 +224,7 @@ mod tests { use std::env; use std::sync::Arc; use util::Bytes; - use fetch::Client; + use fetch::{Fetch, Client}; use hash_fetch::urlhint::{URLHint, URLHintResult}; use parity_reactor::Remote; diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 50efbf466ea..caedc875234 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -122,7 +122,7 @@ impl WebProxyTokens for F where F: Fn(String) -> bool + Send + Sync { } /// Webapps HTTP+RPC server build. -pub struct ServerBuilder { +pub struct ServerBuilder { dapps_path: String, handler: Arc, registrar: Arc, @@ -130,10 +130,10 @@ pub struct ServerBuilder { web_proxy_tokens: Arc, signer_address: Option<(String, u16)>, remote: Remote, - fetch: Option, + fetch: Option, } -impl Extendable for ServerBuilder { +impl Extendable for ServerBuilder { fn add_delegate(&self, delegate: IoDelegate) { self.handler.add_delegate(delegate); } @@ -153,30 +153,44 @@ impl ServerBuilder { fetch: None, } } +} +impl ServerBuilder { /// Set a fetch client to use. - pub fn with_fetch(&mut self, fetch: FetchClient) { - self.fetch = Some(fetch); + pub fn fetch(self, fetch: X) -> ServerBuilder { + ServerBuilder { + dapps_path: self.dapps_path, + handler: self.handler, + registrar: self.registrar, + sync_status: self.sync_status, + web_proxy_tokens: self.web_proxy_tokens, + signer_address: self.signer_address, + remote: self.remote, + fetch: Some(fetch), + } } /// Change default sync status. - pub fn with_sync_status(&mut self, status: Arc) { + pub fn sync_status(mut self, status: Arc) -> Self { self.sync_status = status; + self } /// Change default web proxy tokens validator. - pub fn with_web_proxy_tokens(&mut self, tokens: Arc) { + pub fn web_proxy_tokens(mut self, tokens: Arc) -> Self { self.web_proxy_tokens = tokens; + self } /// Change default signer port. - pub fn with_signer_address(&mut self, signer_address: Option<(String, u16)>) { + pub fn signer_address(mut self, signer_address: Option<(String, u16)>) -> Self { self.signer_address = signer_address; + self } /// Asynchronously start server with no authentication, /// returns result with `Server` handle on success or an error. - pub fn start_unsecured_http(&self, addr: &SocketAddr, hosts: Option>) -> Result { + pub fn start_unsecured_http(self, addr: &SocketAddr, hosts: Option>) -> Result { Server::start_http( addr, hosts, @@ -188,13 +202,13 @@ impl ServerBuilder { self.sync_status.clone(), self.web_proxy_tokens.clone(), self.remote.clone(), - try!(self.fetch()), + try!(self.fetch_client()), ) } /// Asynchronously start server with `HTTP Basic Authentication`, /// return result with `Server` handle on success or an error. - pub fn start_basic_auth_http(&self, addr: &SocketAddr, hosts: Option>, username: &str, password: &str) -> Result { + pub fn start_basic_auth_http(self, addr: &SocketAddr, hosts: Option>, username: &str, password: &str) -> Result { Server::start_http( addr, hosts, @@ -206,14 +220,14 @@ impl ServerBuilder { self.sync_status.clone(), self.web_proxy_tokens.clone(), self.remote.clone(), - try!(self.fetch()), + try!(self.fetch_client()), ) } - fn fetch(&self) -> Result { + fn fetch_client(&self) -> Result { match self.fetch.clone() { Some(fetch) => Ok(fetch), - None => FetchClient::new().map_err(|_| ServerError::FetchInitialization), + None => T::new().map_err(|_| ServerError::FetchInitialization), } } } diff --git a/dapps/src/tests/fetch.rs b/dapps/src/tests/fetch.rs index 8540a407f6f..fbffb1daa78 100644 --- a/dapps/src/tests/fetch.rs +++ b/dapps/src/tests/fetch.rs @@ -65,3 +65,8 @@ fn should_return_503_when_syncing_but_should_make_the_calls() { assert_eq!(registrar.calls.lock().len(), 4); assert_security_headers_for_embed(&response.headers); } + +#[test] +fn should_fetch_the_content() { + +} diff --git a/dapps/src/tests/helpers.rs b/dapps/src/tests/helpers.rs index e3870b93dcb..bcfce38d3d1 100644 --- a/dapps/src/tests/helpers.rs +++ b/dapps/src/tests/helpers.rs @@ -75,11 +75,14 @@ pub fn init_server(hosts: Option>, is_syncing: bool) -> (Server, Arc let registrar = Arc::new(FakeRegistrar::new()); let mut dapps_path = env::temp_dir(); dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading"); - let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone(), Remote::new_sync()); - builder.with_sync_status(Arc::new(move || is_syncing)); - builder.with_signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))); + let server = ServerBuilder::new( + dapps_path.to_str().unwrap().into(), registrar.clone(), Remote::new_sync() + ) + .sync_status(Arc::new(move || is_syncing)) + .signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))) + .start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap(); ( - builder.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap(), + server, registrar, ) } @@ -89,9 +92,9 @@ pub fn serve_with_auth(user: &str, pass: &str) -> Server { let registrar = Arc::new(FakeRegistrar::new()); let mut dapps_path = env::temp_dir(); dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading"); - let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone(), Remote::new_sync()); - builder.with_signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))); - builder.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap() + ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone(), Remote::new_sync()) + .signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))) + .start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap() } pub fn serve_hosts(hosts: Option>) -> Server { diff --git a/parity/dapps.rs b/parity/dapps.rs index 698c0616e32..3d0b4eada21 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -129,7 +129,7 @@ mod server { ) -> Result { use ethcore_dapps as dapps; - let mut server = dapps::ServerBuilder::new( + let server = dapps::ServerBuilder::new( dapps_path, Arc::new(Registrar { client: deps.client.clone() }), deps.remote.clone(), @@ -137,11 +137,11 @@ mod server { let sync = deps.sync.clone(); let client = deps.client.clone(); let signer = deps.signer.clone(); - - server.with_fetch(deps.fetch.clone()); - server.with_sync_status(Arc::new(move || is_major_importing(Some(sync.status().state), client.queue_info()))); - server.with_web_proxy_tokens(Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token))); - server.with_signer_address(deps.signer.address()); + let server = server + .fetch(deps.fetch.clone()) + .sync_status(Arc::new(move || is_major_importing(Some(sync.status().state), client.queue_info()))) + .web_proxy_tokens(Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token))) + .signer_address(deps.signer.address()); let server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext); let start_result = match auth { diff --git a/parity/run.rs b/parity/run.rs index 10f61b5afa1..db8024087e4 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -33,7 +33,7 @@ use ethsync::SyncConfig; use informant::Informant; use updater::{UpdatePolicy, Updater}; use parity_reactor::{EventLoop, EventLoopHandle}; -use hash_fetch::fetch::Client as FetchClient; +use hash_fetch::fetch::{Fetch, Client as FetchClient}; use rpc::{HttpServer, IpcServer, HttpConfiguration, IpcConfiguration}; use signer::SignerServer; diff --git a/rpc/src/v1/tests/helpers/fetch.rs b/rpc/src/v1/tests/helpers/fetch.rs index b55e4caca66..71852a90f34 100644 --- a/rpc/src/v1/tests/helpers/fetch.rs +++ b/rpc/src/v1/tests/helpers/fetch.rs @@ -27,6 +27,10 @@ pub struct TestFetch; impl Fetch for TestFetch { type Result = futures::BoxFuture; + fn new() -> Result where Self: Sized { + Ok(TestFetch) + } + fn fetch_with_abort(&self, _url: &str, _abort: fetch::Abort) -> Self::Result { let (tx, rx) = futures::oneshot(); thread::spawn(move || { diff --git a/util/fetch/src/client.rs b/util/fetch/src/client.rs index 4fcc3d14883..4b3ae111db0 100644 --- a/util/fetch/src/client.rs +++ b/util/fetch/src/client.rs @@ -43,6 +43,8 @@ impl From> for Abort { pub trait Fetch: Clone + Send + Sync + 'static { type Result: Future + Send + 'static; + fn new() -> Result where Self: Sized; + /// Spawn the future in context of this `Fetch` thread pool. /// Implementation is optional. fn process(&self, f: F) -> BoxFuture<(), ()> where @@ -77,11 +79,6 @@ pub struct Client { } impl Client { - pub fn new() -> Result { - // Max 50MB will be downloaded. - Self::with_limit(Some(50*1024*1024)) - } - fn with_limit(limit: Option) -> Result { let mut client = try!(reqwest::Client::new()); client.redirect(reqwest::RedirectPolicy::limited(5)); @@ -97,6 +94,11 @@ impl Client { impl Fetch for Client { type Result = CpuFuture; + fn new() -> Result { + // Max 50MB will be downloaded. + Self::with_limit(Some(50*1024*1024)) + } + fn process(&self, f: F) -> BoxFuture<(), ()> where F: Future + Send + 'static, { From 17a86d10147b83832e8b8605fbfbd8599a8b4cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 27 Dec 2016 12:23:46 +0100 Subject: [PATCH 2/2] Some basic Fetch tests --- dapps/src/tests/fetch.rs | 207 +++++++++++++++++- dapps/src/tests/helpers/fetch.rs | 64 ++++++ .../src/tests/{helpers.rs => helpers/mod.rs} | 82 ++++--- dapps/src/tests/helpers/registrar.rs | 72 ++++++ devtools/src/http_client.rs | 10 + 5 files changed, 381 insertions(+), 54 deletions(-) create mode 100644 dapps/src/tests/helpers/fetch.rs rename dapps/src/tests/{helpers.rs => helpers/mod.rs} (68%) create mode 100644 dapps/src/tests/helpers/registrar.rs diff --git a/dapps/src/tests/fetch.rs b/dapps/src/tests/fetch.rs index fbffb1daa78..56c96faff1b 100644 --- a/dapps/src/tests/fetch.rs +++ b/dapps/src/tests/fetch.rs @@ -14,7 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use tests::helpers::{serve_with_registrar, serve_with_registrar_and_sync, request, assert_security_headers_for_embed}; +use devtools::http_client; +use rustc_serialize::hex::FromHex; +use tests::helpers::{ + serve_with_registrar, serve_with_registrar_and_sync, serve_with_registrar_and_fetch, serve_with_fetch, + request, assert_security_headers_for_embed, +}; #[test] fn should_resolve_dapp() { @@ -32,7 +37,7 @@ fn should_resolve_dapp() { ); // then - assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); + response.assert_status("HTTP/1.1 404 Not Found"); assert_eq!(registrar.calls.lock().len(), 2); assert_security_headers_for_embed(&response.headers); } @@ -41,14 +46,6 @@ fn should_resolve_dapp() { fn should_return_503_when_syncing_but_should_make_the_calls() { // given let (server, registrar) = serve_with_registrar_and_sync(); - { - let mut responses = registrar.responses.lock(); - let res1 = responses.get(0).unwrap().clone(); - let res2 = responses.get(1).unwrap().clone(); - // Registrar will be called twice - fill up the responses. - responses.push(res1); - responses.push(res2); - } // when let response = request(server, @@ -61,12 +58,198 @@ fn should_return_503_when_syncing_but_should_make_the_calls() { ); // then - assert_eq!(response.status, "HTTP/1.1 503 Service Unavailable".to_owned()); + response.assert_status("HTTP/1.1 503 Service Unavailable"); assert_eq!(registrar.calls.lock().len(), 4); assert_security_headers_for_embed(&response.headers); } +const GAVCOIN_DAPP: &'static str = "00000000000000000000000000000000000000000000000000000000000000609faf32e1e3845e237cc6efd27187cee13b3b99db000000000000000000000000000000000000000000000000d8bd350823e28ff75e74a34215faefdc8a52fd8e00000000000000000000000000000000000000000000000000000000000000116761766f66796f726b2f676176636f696e000000000000000000000000000000"; +const GAVCOIN_ICON: &'static str = "00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d8bd350823e28ff75e74a34215faefdc8a52fd8e000000000000000000000000000000000000000000000000000000000000007768747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f657468636f72652f646170702d6173736574732f623838653938336162616131613661363334356238643934343863313562313137646462353430652f746f6b656e732f676176636f696e2d36347836342e706e67000000000000000000"; + +#[test] +fn should_return_502_on_hash_mismatch() { + // given + let (server, fetch, registrar) = serve_with_registrar_and_fetch(); + let gavcoin = GAVCOIN_DAPP.from_hex().unwrap(); + registrar.set_result( + "94f093625c06887d94d9fee0d5f9cc4aaa46f33d24d1c7e4b5237e7c37d547dd".parse().unwrap(), + Ok(gavcoin.clone()) + ); + + // when + let response = request(server, + "\ + GET / HTTP/1.1\r\n\ + Host: 94f093625c06887d94d9fee0d5f9cc4aaa46f33d24d1c7e4b5237e7c37d547dd.parity\r\n\ + Connection: close\r\n\ + \r\n\ + " + ); + + // then + assert_eq!(registrar.calls.lock().len(), 4); + + fetch.assert_requested("https://codeload.github.com/gavofyork/gavcoin/zip/9faf32e1e3845e237cc6efd27187cee13b3b99db"); + fetch.assert_no_more_requests(); + + response.assert_status("HTTP/1.1 502 Bad Gateway"); + assert!(response.body.contains("HashMismatch"), "Expected hash mismatch response, got: {:?}", response.body); + assert_security_headers_for_embed(&response.headers); +} + +#[test] +fn should_return_error_for_invalid_dapp_zip() { + // given + let (server, fetch, registrar) = serve_with_registrar_and_fetch(); + let gavcoin = GAVCOIN_DAPP.from_hex().unwrap(); + registrar.set_result( + "2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(), + Ok(gavcoin.clone()) + ); + + // when + let response = request(server, + "\ + GET / HTTP/1.1\r\n\ + Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.parity\r\n\ + Connection: close\r\n\ + \r\n\ + " + ); + + // then + assert_eq!(registrar.calls.lock().len(), 4); + + fetch.assert_requested("https://codeload.github.com/gavofyork/gavcoin/zip/9faf32e1e3845e237cc6efd27187cee13b3b99db"); + fetch.assert_no_more_requests(); + + response.assert_status("HTTP/1.1 502 Bad Gateway"); + assert!(response.body.contains("InvalidArchive"), "Expected invalid zip response, got: {:?}", response.body); + assert_security_headers_for_embed(&response.headers); +} + +#[test] +fn should_return_fetched_content() { + // given + let (server, fetch, registrar) = serve_with_registrar_and_fetch(); + let gavcoin = GAVCOIN_ICON.from_hex().unwrap(); + registrar.set_result( + "2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(), + Ok(gavcoin.clone()) + ); + + // when + let response = request(server, + "\ + GET / HTTP/1.1\r\n\ + Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.parity\r\n\ + Connection: close\r\n\ + \r\n\ + " + ); + + // then + assert_eq!(registrar.calls.lock().len(), 4); + + fetch.assert_requested("https://raw.githubusercontent.com/ethcore/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/tokens/gavcoin-64x64.png"); + fetch.assert_no_more_requests(); + + response.assert_status("HTTP/1.1 200 OK"); + response.assert_security_headers_present(None); +} + +#[test] +fn should_cache_content() { + // given + let (server, fetch, registrar) = serve_with_registrar_and_fetch(); + let gavcoin = GAVCOIN_ICON.from_hex().unwrap(); + registrar.set_result( + "2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(), + Ok(gavcoin.clone()) + ); + let request_str = "\ + GET / HTTP/1.1\r\n\ + Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.parity\r\n\ + Connection: close\r\n\ + \r\n\ + "; + + let response = http_client::request(server.addr(), request_str); + fetch.assert_requested("https://raw.githubusercontent.com/ethcore/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/tokens/gavcoin-64x64.png"); + fetch.assert_no_more_requests(); + response.assert_status("HTTP/1.1 200 OK"); + + // when + let response = http_client::request(server.addr(), request_str); + + // then + fetch.assert_no_more_requests(); + response.assert_status("HTTP/1.1 200 OK"); +} + +#[test] +fn should_stream_web_content() { + // given + let (server, fetch) = serve_with_fetch("token"); + + // when + let response = request(server, + "\ + GET /web/token/https/ethcore.io/ HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Connection: close\r\n\ + \r\n\ + " + ); + + // then + response.assert_status("HTTP/1.1 200 OK"); + assert_security_headers_for_embed(&response.headers); + + fetch.assert_requested("https://ethcore.io/"); + fetch.assert_no_more_requests(); +} + #[test] -fn should_fetch_the_content() { +fn should_return_error_on_invalid_token() { + // given + let (server, fetch) = serve_with_fetch("token"); + + // when + let response = request(server, + "\ + GET /web/invalidtoken/https/ethcore.io/ HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Connection: close\r\n\ + \r\n\ + " + ); + + // then + response.assert_status("HTTP/1.1 400 Bad Request"); + assert_security_headers_for_embed(&response.headers); + + fetch.assert_no_more_requests(); +} + +#[test] +fn should_return_error_on_invalid_protocol() { + // given + let (server, fetch) = serve_with_fetch("token"); + + // when + let response = request(server, + "\ + GET /web/token/ftp/ethcore.io/ HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Connection: close\r\n\ + \r\n\ + " + ); + + // then + response.assert_status("HTTP/1.1 400 Bad Request"); + assert_security_headers_for_embed(&response.headers); + fetch.assert_no_more_requests(); } diff --git a/dapps/src/tests/helpers/fetch.rs b/dapps/src/tests/helpers/fetch.rs new file mode 100644 index 00000000000..4f62be65630 --- /dev/null +++ b/dapps/src/tests/helpers/fetch.rs @@ -0,0 +1,64 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity 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 General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use std::{io, thread}; +use std::sync::Arc; +use std::sync::atomic::{self, AtomicUsize}; +use util::Mutex; + +use futures::{self, Future}; +use fetch::{self, Fetch}; + +#[derive(Clone, Default)] +pub struct FakeFetch { + asserted: Arc, + requested: Arc>>, +} + +impl FakeFetch { + pub fn assert_requested(&self, url: &str) { + let requests = self.requested.lock(); + let idx = self.asserted.fetch_add(1, atomic::Ordering::SeqCst); + + assert_eq!(requests.get(idx), Some(&url.to_owned()), "Expected fetch from specific URL."); + } + + pub fn assert_no_more_requests(&self) { + let requests = self.requested.lock(); + let len = self.asserted.load(atomic::Ordering::SeqCst); + assert_eq!(requests.len(), len, "Didn't expect any more requests, got: {:?}", &requests[len..]); + } +} + +impl Fetch for FakeFetch { + type Result = futures::BoxFuture; + + fn new() -> Result where Self: Sized { + Ok(FakeFetch::default()) + } + + fn fetch_with_abort(&self, url: &str, _abort: fetch::Abort) -> Self::Result { + self.requested.lock().push(url.into()); + + let (tx, rx) = futures::oneshot(); + thread::spawn(move || { + let cursor = io::Cursor::new(b"Some content"); + tx.complete(fetch::Response::from_reader(cursor)); + }); + + rx.map_err(|_| fetch::Error::Aborted).boxed() + } +} diff --git a/dapps/src/tests/helpers.rs b/dapps/src/tests/helpers/mod.rs similarity index 68% rename from dapps/src/tests/helpers.rs rename to dapps/src/tests/helpers/mod.rs index bcfce38d3d1..cacfff02d38 100644 --- a/dapps/src/tests/helpers.rs +++ b/dapps/src/tests/helpers/mod.rs @@ -17,49 +17,21 @@ use std::env; use std::str; use std::sync::Arc; -use rustc_serialize::hex::FromHex; use env_logger::LogBuilder; use ServerBuilder; use Server; -use hash_fetch::urlhint::ContractClient; -use util::{Bytes, Address, Mutex, ToPretty}; +use fetch::Fetch; use devtools::http_client; use parity_reactor::Remote; -const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2"; -const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000"; -const SIGNER_PORT: u16 = 18180; - -pub struct FakeRegistrar { - pub calls: Arc>>, - pub responses: Mutex>>, -} +mod registrar; +mod fetch; -impl FakeRegistrar { - fn new() -> Self { - FakeRegistrar { - calls: Arc::new(Mutex::new(Vec::new())), - responses: Mutex::new( - vec![ - Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()), - Ok(Vec::new()) - ] - ), - } - } -} +use self::registrar::FakeRegistrar; +use self::fetch::FakeFetch; -impl ContractClient for FakeRegistrar { - fn registrar(&self) -> Result { - Ok(REGISTRAR.parse().unwrap()) - } - - fn call(&self, address: Address, data: Bytes) -> Result { - self.calls.lock().push((address.to_hex(), data.to_hex())); - self.responses.lock().remove(0) - } -} +const SIGNER_PORT: u16 = 18180; fn init_logger() { // Initialize logger @@ -70,15 +42,17 @@ fn init_logger() { } } -pub fn init_server(hosts: Option>, is_syncing: bool) -> (Server, Arc) { +pub fn init_server(hosts: Option>, process: F) -> (Server, Arc) where + F: FnOnce(ServerBuilder) -> ServerBuilder, + B: Fetch, +{ init_logger(); let registrar = Arc::new(FakeRegistrar::new()); let mut dapps_path = env::temp_dir(); dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading"); - let server = ServerBuilder::new( + let server = process(ServerBuilder::new( dapps_path.to_str().unwrap().into(), registrar.clone(), Remote::new_sync() - ) - .sync_status(Arc::new(move || is_syncing)) + )) .signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))) .start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap(); ( @@ -98,19 +72,43 @@ pub fn serve_with_auth(user: &str, pass: &str) -> Server { } pub fn serve_hosts(hosts: Option>) -> Server { - init_server(hosts, false).0 + init_server(hosts, |builder| builder).0 } pub fn serve_with_registrar() -> (Server, Arc) { - init_server(None, false) + init_server(None, |builder| builder) } pub fn serve_with_registrar_and_sync() -> (Server, Arc) { - init_server(None, true) + init_server(None, |builder| { + builder.sync_status(Arc::new(|| true)) + }) +} + +pub fn serve_with_registrar_and_fetch() -> (Server, FakeFetch, Arc) { + let fetch = FakeFetch::default(); + let f = fetch.clone(); + let (server, reg) = init_server(None, move |builder| { + builder.fetch(f.clone()) + }); + + (server, fetch, reg) +} + +pub fn serve_with_fetch(web_token: &'static str) -> (Server, FakeFetch) { + let fetch = FakeFetch::default(); + let f = fetch.clone(); + let (server, _) = init_server(None, move |builder| { + builder + .fetch(f.clone()) + .web_proxy_tokens(Arc::new(move |token| &token == web_token)) + }); + + (server, fetch) } pub fn serve() -> Server { - init_server(None, false).0 + init_server(None, |builder| builder).0 } pub fn request(server: Server, request: &str) -> http_client::Response { diff --git a/dapps/src/tests/helpers/registrar.rs b/dapps/src/tests/helpers/registrar.rs new file mode 100644 index 00000000000..342f3440c5b --- /dev/null +++ b/dapps/src/tests/helpers/registrar.rs @@ -0,0 +1,72 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity 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 General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use std::str; +use std::sync::Arc; +use std::collections::HashMap; +use rustc_serialize::hex::FromHex; + +use hash_fetch::urlhint::ContractClient; +use util::{Bytes, Address, Mutex, H256, ToPretty}; + +const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2"; +const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000"; +const URLHINT_RESOLVE: &'static str = "267b6922"; +const DEFAULT_HASH: &'static str = "1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d"; + +pub struct FakeRegistrar { + pub calls: Arc>>, + pub responses: Mutex>>, +} + +impl FakeRegistrar { + pub fn new() -> Self { + FakeRegistrar { + calls: Arc::new(Mutex::new(Vec::new())), + responses: Mutex::new({ + let mut map = HashMap::new(); + map.insert( + (REGISTRAR.into(), "6795dbcd058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000014100000000000000000000000000000000000000000000000000000000000000".into()), + Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()), + ); + map.insert( + (URLHINT.into(), format!("{}{}", URLHINT_RESOLVE, DEFAULT_HASH)), + Ok(vec![]) + ); + map + }), + } + } + + pub fn set_result(&self, hash: H256, result: Result) { + self.responses.lock().insert( + (URLHINT.into(), format!("{}{:?}", URLHINT_RESOLVE, hash)), + result + ); + } +} + +impl ContractClient for FakeRegistrar { + fn registrar(&self) -> Result { + Ok(REGISTRAR.parse().unwrap()) + } + + fn call(&self, address: Address, data: Bytes) -> Result { + let call = (address.to_hex(), data.to_hex()); + self.calls.lock().push(call.clone()); + self.responses.lock().get(&call).cloned().expect(&format!("No response for call: {:?}", call)) + } +} diff --git a/devtools/src/http_client.rs b/devtools/src/http_client.rs index bb3b896af54..60a587e0212 100644 --- a/devtools/src/http_client.rs +++ b/devtools/src/http_client.rs @@ -27,6 +27,16 @@ pub struct Response { pub body: String, } +impl Response { + pub fn assert_status(&self, status: &str) { + assert_eq!(self.status, status.to_owned(), "Got unexpected code. Body: {:?}", self.body); + } + + pub fn assert_security_headers_present(&self, port: Option) { + assert_security_headers_present(&self.headers, port) + } +} + pub fn read_block(lines: &mut Lines, all: bool) -> String { let mut block = String::new(); loop {