Fetch tests (first batch) (#3977)
* Customizable fetch * Some basic Fetch tests
This commit is contained in:
parent
a95057abe1
commit
bc3dacc952
@ -224,7 +224,7 @@ mod tests {
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use util::Bytes;
|
use util::Bytes;
|
||||||
use fetch::Client;
|
use fetch::{Fetch, Client};
|
||||||
use hash_fetch::urlhint::{URLHint, URLHintResult};
|
use hash_fetch::urlhint::{URLHint, URLHintResult};
|
||||||
use parity_reactor::Remote;
|
use parity_reactor::Remote;
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ impl<F> WebProxyTokens for F where F: Fn(String) -> bool + Send + Sync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Webapps HTTP+RPC server build.
|
/// Webapps HTTP+RPC server build.
|
||||||
pub struct ServerBuilder {
|
pub struct ServerBuilder<T: Fetch = FetchClient> {
|
||||||
dapps_path: String,
|
dapps_path: String,
|
||||||
handler: Arc<IoHandler>,
|
handler: Arc<IoHandler>,
|
||||||
registrar: Arc<ContractClient>,
|
registrar: Arc<ContractClient>,
|
||||||
@ -130,10 +130,10 @@ pub struct ServerBuilder {
|
|||||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||||
signer_address: Option<(String, u16)>,
|
signer_address: Option<(String, u16)>,
|
||||||
remote: Remote,
|
remote: Remote,
|
||||||
fetch: Option<FetchClient>,
|
fetch: Option<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Extendable for ServerBuilder {
|
impl<T: Fetch> Extendable for ServerBuilder<T> {
|
||||||
fn add_delegate<D: Send + Sync + 'static>(&self, delegate: IoDelegate<D>) {
|
fn add_delegate<D: Send + Sync + 'static>(&self, delegate: IoDelegate<D>) {
|
||||||
self.handler.add_delegate(delegate);
|
self.handler.add_delegate(delegate);
|
||||||
}
|
}
|
||||||
@ -153,30 +153,44 @@ impl ServerBuilder {
|
|||||||
fetch: None,
|
fetch: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Fetch> ServerBuilder<T> {
|
||||||
/// Set a fetch client to use.
|
/// Set a fetch client to use.
|
||||||
pub fn with_fetch(&mut self, fetch: FetchClient) {
|
pub fn fetch<X: Fetch>(self, fetch: X) -> ServerBuilder<X> {
|
||||||
self.fetch = Some(fetch);
|
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.
|
/// Change default sync status.
|
||||||
pub fn with_sync_status(&mut self, status: Arc<SyncStatus>) {
|
pub fn sync_status(mut self, status: Arc<SyncStatus>) -> Self {
|
||||||
self.sync_status = status;
|
self.sync_status = status;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change default web proxy tokens validator.
|
/// Change default web proxy tokens validator.
|
||||||
pub fn with_web_proxy_tokens(&mut self, tokens: Arc<WebProxyTokens>) {
|
pub fn web_proxy_tokens(mut self, tokens: Arc<WebProxyTokens>) -> Self {
|
||||||
self.web_proxy_tokens = tokens;
|
self.web_proxy_tokens = tokens;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change default signer port.
|
/// 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.signer_address = signer_address;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asynchronously start server with no authentication,
|
/// Asynchronously start server with no authentication,
|
||||||
/// returns result with `Server` handle on success or an error.
|
/// returns result with `Server` handle on success or an error.
|
||||||
pub fn start_unsecured_http(&self, addr: &SocketAddr, hosts: Option<Vec<String>>) -> Result<Server, ServerError> {
|
pub fn start_unsecured_http(self, addr: &SocketAddr, hosts: Option<Vec<String>>) -> Result<Server, ServerError> {
|
||||||
Server::start_http(
|
Server::start_http(
|
||||||
addr,
|
addr,
|
||||||
hosts,
|
hosts,
|
||||||
@ -188,13 +202,13 @@ impl ServerBuilder {
|
|||||||
self.sync_status.clone(),
|
self.sync_status.clone(),
|
||||||
self.web_proxy_tokens.clone(),
|
self.web_proxy_tokens.clone(),
|
||||||
self.remote.clone(),
|
self.remote.clone(),
|
||||||
self.fetch()?,
|
self.fetch_client()?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asynchronously start server with `HTTP Basic Authentication`,
|
/// Asynchronously start server with `HTTP Basic Authentication`,
|
||||||
/// return result with `Server` handle on success or an error.
|
/// return result with `Server` handle on success or an error.
|
||||||
pub fn start_basic_auth_http(&self, addr: &SocketAddr, hosts: Option<Vec<String>>, username: &str, password: &str) -> Result<Server, ServerError> {
|
pub fn start_basic_auth_http(self, addr: &SocketAddr, hosts: Option<Vec<String>>, username: &str, password: &str) -> Result<Server, ServerError> {
|
||||||
Server::start_http(
|
Server::start_http(
|
||||||
addr,
|
addr,
|
||||||
hosts,
|
hosts,
|
||||||
@ -206,14 +220,14 @@ impl ServerBuilder {
|
|||||||
self.sync_status.clone(),
|
self.sync_status.clone(),
|
||||||
self.web_proxy_tokens.clone(),
|
self.web_proxy_tokens.clone(),
|
||||||
self.remote.clone(),
|
self.remote.clone(),
|
||||||
self.fetch()?,
|
self.fetch_client()?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch(&self) -> Result<FetchClient, ServerError> {
|
fn fetch_client(&self) -> Result<T, ServerError> {
|
||||||
match self.fetch.clone() {
|
match self.fetch.clone() {
|
||||||
Some(fetch) => Ok(fetch),
|
Some(fetch) => Ok(fetch),
|
||||||
None => FetchClient::new().map_err(|_| ServerError::FetchInitialization),
|
None => T::new().map_err(|_| ServerError::FetchInitialization),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,12 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
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]
|
#[test]
|
||||||
fn should_resolve_dapp() {
|
fn should_resolve_dapp() {
|
||||||
@ -32,7 +37,7 @@ fn should_resolve_dapp() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// then
|
// 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_eq!(registrar.calls.lock().len(), 2);
|
||||||
assert_security_headers_for_embed(&response.headers);
|
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() {
|
fn should_return_503_when_syncing_but_should_make_the_calls() {
|
||||||
// given
|
// given
|
||||||
let (server, registrar) = serve_with_registrar_and_sync();
|
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
|
// when
|
||||||
let response = request(server,
|
let response = request(server,
|
||||||
@ -61,7 +58,198 @@ fn should_return_503_when_syncing_but_should_make_the_calls() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// then
|
// 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_eq!(registrar.calls.lock().len(), 4);
|
||||||
assert_security_headers_for_embed(&response.headers);
|
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_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();
|
||||||
|
}
|
||||||
|
64
dapps/src/tests/helpers/fetch.rs
Normal file
64
dapps/src/tests/helpers/fetch.rs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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<AtomicUsize>,
|
||||||
|
requested: Arc<Mutex<Vec<String>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<fetch::Response, fetch::Error>;
|
||||||
|
|
||||||
|
fn new() -> Result<Self, fetch::Error> 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()
|
||||||
|
}
|
||||||
|
}
|
@ -17,50 +17,22 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use rustc_serialize::hex::FromHex;
|
|
||||||
use env_logger::LogBuilder;
|
use env_logger::LogBuilder;
|
||||||
|
|
||||||
use ServerBuilder;
|
use ServerBuilder;
|
||||||
use Server;
|
use Server;
|
||||||
use hash_fetch::urlhint::ContractClient;
|
use fetch::Fetch;
|
||||||
use util::{Bytes, Address, Mutex, ToPretty};
|
|
||||||
use devtools::http_client;
|
use devtools::http_client;
|
||||||
use parity_reactor::Remote;
|
use parity_reactor::Remote;
|
||||||
|
|
||||||
const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2";
|
mod registrar;
|
||||||
const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000";
|
mod fetch;
|
||||||
|
|
||||||
|
use self::registrar::FakeRegistrar;
|
||||||
|
use self::fetch::FakeFetch;
|
||||||
|
|
||||||
const SIGNER_PORT: u16 = 18180;
|
const SIGNER_PORT: u16 = 18180;
|
||||||
|
|
||||||
pub struct FakeRegistrar {
|
|
||||||
pub calls: Arc<Mutex<Vec<(String, String)>>>,
|
|
||||||
pub responses: Mutex<Vec<Result<Bytes, String>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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())
|
|
||||||
]
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContractClient for FakeRegistrar {
|
|
||||||
fn registrar(&self) -> Result<Address, String> {
|
|
||||||
Ok(REGISTRAR.parse().unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String> {
|
|
||||||
self.calls.lock().push((address.to_hex(), data.to_hex()));
|
|
||||||
self.responses.lock().remove(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_logger() {
|
fn init_logger() {
|
||||||
// Initialize logger
|
// Initialize logger
|
||||||
if let Ok(log) = env::var("RUST_LOG") {
|
if let Ok(log) = env::var("RUST_LOG") {
|
||||||
@ -70,16 +42,21 @@ fn init_logger() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_server(hosts: Option<Vec<String>>, is_syncing: bool) -> (Server, Arc<FakeRegistrar>) {
|
pub fn init_server<F, B>(hosts: Option<Vec<String>>, process: F) -> (Server, Arc<FakeRegistrar>) where
|
||||||
|
F: FnOnce(ServerBuilder) -> ServerBuilder<B>,
|
||||||
|
B: Fetch,
|
||||||
|
{
|
||||||
init_logger();
|
init_logger();
|
||||||
let registrar = Arc::new(FakeRegistrar::new());
|
let registrar = Arc::new(FakeRegistrar::new());
|
||||||
let mut dapps_path = env::temp_dir();
|
let mut dapps_path = env::temp_dir();
|
||||||
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
|
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());
|
let server = process(ServerBuilder::new(
|
||||||
builder.with_sync_status(Arc::new(move || is_syncing));
|
dapps_path.to_str().unwrap().into(), registrar.clone(), Remote::new_sync()
|
||||||
builder.with_signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)));
|
))
|
||||||
|
.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,
|
registrar,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -89,25 +66,49 @@ pub fn serve_with_auth(user: &str, pass: &str) -> Server {
|
|||||||
let registrar = Arc::new(FakeRegistrar::new());
|
let registrar = Arc::new(FakeRegistrar::new());
|
||||||
let mut dapps_path = env::temp_dir();
|
let mut dapps_path = env::temp_dir();
|
||||||
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
|
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());
|
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)));
|
.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()
|
.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serve_hosts(hosts: Option<Vec<String>>) -> Server {
|
pub fn serve_hosts(hosts: Option<Vec<String>>) -> Server {
|
||||||
init_server(hosts, false).0
|
init_server(hosts, |builder| builder).0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serve_with_registrar() -> (Server, Arc<FakeRegistrar>) {
|
pub fn serve_with_registrar() -> (Server, Arc<FakeRegistrar>) {
|
||||||
init_server(None, false)
|
init_server(None, |builder| builder)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serve_with_registrar_and_sync() -> (Server, Arc<FakeRegistrar>) {
|
pub fn serve_with_registrar_and_sync() -> (Server, Arc<FakeRegistrar>) {
|
||||||
init_server(None, true)
|
init_server(None, |builder| {
|
||||||
|
builder.sync_status(Arc::new(|| true))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serve_with_registrar_and_fetch() -> (Server, FakeFetch, Arc<FakeRegistrar>) {
|
||||||
|
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 {
|
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 {
|
pub fn request(server: Server, request: &str) -> http_client::Response {
|
72
dapps/src/tests/helpers/registrar.rs
Normal file
72
dapps/src/tests/helpers/registrar.rs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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<Mutex<Vec<(String, String)>>>,
|
||||||
|
pub responses: Mutex<HashMap<(String, String), Result<Bytes, String>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Bytes, String>) {
|
||||||
|
self.responses.lock().insert(
|
||||||
|
(URLHINT.into(), format!("{}{:?}", URLHINT_RESOLVE, hash)),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContractClient for FakeRegistrar {
|
||||||
|
fn registrar(&self) -> Result<Address, String> {
|
||||||
|
Ok(REGISTRAR.parse().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String> {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
@ -27,6 +27,16 @@ pub struct Response {
|
|||||||
pub body: String,
|
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<u16>) {
|
||||||
|
assert_security_headers_present(&self.headers, port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_block(lines: &mut Lines, all: bool) -> String {
|
pub fn read_block(lines: &mut Lines, all: bool) -> String {
|
||||||
let mut block = String::new();
|
let mut block = String::new();
|
||||||
loop {
|
loop {
|
||||||
|
@ -129,7 +129,7 @@ mod server {
|
|||||||
) -> Result<WebappServer, String> {
|
) -> Result<WebappServer, String> {
|
||||||
use ethcore_dapps as dapps;
|
use ethcore_dapps as dapps;
|
||||||
|
|
||||||
let mut server = dapps::ServerBuilder::new(
|
let server = dapps::ServerBuilder::new(
|
||||||
dapps_path,
|
dapps_path,
|
||||||
Arc::new(Registrar { client: deps.client.clone() }),
|
Arc::new(Registrar { client: deps.client.clone() }),
|
||||||
deps.remote.clone(),
|
deps.remote.clone(),
|
||||||
@ -137,11 +137,11 @@ mod server {
|
|||||||
let sync = deps.sync.clone();
|
let sync = deps.sync.clone();
|
||||||
let client = deps.client.clone();
|
let client = deps.client.clone();
|
||||||
let signer = deps.signer.clone();
|
let signer = deps.signer.clone();
|
||||||
|
let server = server
|
||||||
server.with_fetch(deps.fetch.clone());
|
.fetch(deps.fetch.clone())
|
||||||
server.with_sync_status(Arc::new(move || is_major_importing(Some(sync.status().state), client.queue_info())));
|
.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)));
|
.web_proxy_tokens(Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token)))
|
||||||
server.with_signer_address(deps.signer.address());
|
.signer_address(deps.signer.address());
|
||||||
|
|
||||||
let server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext);
|
let server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext);
|
||||||
let start_result = match auth {
|
let start_result = match auth {
|
||||||
|
@ -33,7 +33,7 @@ use ethsync::SyncConfig;
|
|||||||
use informant::Informant;
|
use informant::Informant;
|
||||||
use updater::{UpdatePolicy, Updater};
|
use updater::{UpdatePolicy, Updater};
|
||||||
use parity_reactor::{EventLoop, EventLoopHandle};
|
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 rpc::{HttpServer, IpcServer, HttpConfiguration, IpcConfiguration};
|
||||||
use signer::SignerServer;
|
use signer::SignerServer;
|
||||||
|
@ -27,6 +27,10 @@ pub struct TestFetch;
|
|||||||
impl Fetch for TestFetch {
|
impl Fetch for TestFetch {
|
||||||
type Result = futures::BoxFuture<fetch::Response, fetch::Error>;
|
type Result = futures::BoxFuture<fetch::Response, fetch::Error>;
|
||||||
|
|
||||||
|
fn new() -> Result<Self, fetch::Error> where Self: Sized {
|
||||||
|
Ok(TestFetch)
|
||||||
|
}
|
||||||
|
|
||||||
fn fetch_with_abort(&self, _url: &str, _abort: fetch::Abort) -> Self::Result {
|
fn fetch_with_abort(&self, _url: &str, _abort: fetch::Abort) -> Self::Result {
|
||||||
let (tx, rx) = futures::oneshot();
|
let (tx, rx) = futures::oneshot();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
|
@ -43,6 +43,8 @@ impl From<Arc<AtomicBool>> for Abort {
|
|||||||
pub trait Fetch: Clone + Send + Sync + 'static {
|
pub trait Fetch: Clone + Send + Sync + 'static {
|
||||||
type Result: Future<Item=Response, Error=Error> + Send + 'static;
|
type Result: Future<Item=Response, Error=Error> + Send + 'static;
|
||||||
|
|
||||||
|
fn new() -> Result<Self, Error> where Self: Sized;
|
||||||
|
|
||||||
/// Spawn the future in context of this `Fetch` thread pool.
|
/// Spawn the future in context of this `Fetch` thread pool.
|
||||||
/// Implementation is optional.
|
/// Implementation is optional.
|
||||||
fn process<F>(&self, f: F) -> BoxFuture<(), ()> where
|
fn process<F>(&self, f: F) -> BoxFuture<(), ()> where
|
||||||
@ -77,11 +79,6 @@ pub struct Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub fn new() -> Result<Self, Error> {
|
|
||||||
// Max 50MB will be downloaded.
|
|
||||||
Self::with_limit(Some(50*1024*1024))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_limit(limit: Option<usize>) -> Result<Self, Error> {
|
fn with_limit(limit: Option<usize>) -> Result<Self, Error> {
|
||||||
let mut client = reqwest::Client::new()?;
|
let mut client = reqwest::Client::new()?;
|
||||||
client.redirect(reqwest::RedirectPolicy::limited(5));
|
client.redirect(reqwest::RedirectPolicy::limited(5));
|
||||||
@ -97,6 +94,11 @@ impl Client {
|
|||||||
impl Fetch for Client {
|
impl Fetch for Client {
|
||||||
type Result = CpuFuture<Response, Error>;
|
type Result = CpuFuture<Response, Error>;
|
||||||
|
|
||||||
|
fn new() -> Result<Self, Error> {
|
||||||
|
// Max 50MB will be downloaded.
|
||||||
|
Self::with_limit(Some(50*1024*1024))
|
||||||
|
}
|
||||||
|
|
||||||
fn process<F>(&self, f: F) -> BoxFuture<(), ()> where
|
fn process<F>(&self, f: F) -> BoxFuture<(), ()> where
|
||||||
F: Future<Item=(), Error=()> + Send + 'static,
|
F: Future<Item=(), Error=()> + Send + 'static,
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user