Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Commit

Permalink
Fetch tests (first batch) (#3977)
Browse files Browse the repository at this point in the history
* Customizable fetch

* Some basic Fetch tests
  • Loading branch information
tomusdrw authored and gavofyork committed Dec 27, 2016
1 parent a95057a commit bc3dacc
Show file tree
Hide file tree
Showing 11 changed files with 439 additions and 84 deletions.
2 changes: 1 addition & 1 deletion dapps/src/apps/fetcher/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
42 changes: 28 additions & 14 deletions dapps/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,18 @@ impl<F> WebProxyTokens for F where F: Fn(String) -> bool + Send + Sync {
}

/// Webapps HTTP+RPC server build.
pub struct ServerBuilder {
pub struct ServerBuilder<T: Fetch = FetchClient> {
dapps_path: String,
handler: Arc<IoHandler>,
registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
web_proxy_tokens: Arc<WebProxyTokens>,
signer_address: Option<(String, u16)>,
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>) {
self.handler.add_delegate(delegate);
}
Expand All @@ -153,30 +153,44 @@ impl ServerBuilder {
fetch: None,
}
}
}

impl<T: Fetch> ServerBuilder<T> {
/// Set a fetch client to use.
pub fn with_fetch(&mut self, fetch: FetchClient) {
self.fetch = Some(fetch);
pub fn fetch<X: Fetch>(self, fetch: X) -> ServerBuilder<X> {
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<SyncStatus>) {
pub fn sync_status(mut self, status: Arc<SyncStatus>) -> Self {
self.sync_status = status;
self
}

/// 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
}

/// 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<Vec<String>>) -> Result<Server, ServerError> {
pub fn start_unsecured_http(self, addr: &SocketAddr, hosts: Option<Vec<String>>) -> Result<Server, ServerError> {
Server::start_http(
addr,
hosts,
Expand All @@ -188,13 +202,13 @@ impl ServerBuilder {
self.sync_status.clone(),
self.web_proxy_tokens.clone(),
self.remote.clone(),
self.fetch()?,
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<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(
addr,
hosts,
Expand All @@ -206,14 +220,14 @@ impl ServerBuilder {
self.sync_status.clone(),
self.web_proxy_tokens.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() {
Some(fetch) => Ok(fetch),
None => FetchClient::new().map_err(|_| ServerError::FetchInitialization),
None => T::new().map_err(|_| ServerError::FetchInitialization),
}
}
}
Expand Down
210 changes: 199 additions & 11 deletions dapps/src/tests/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@
// You should have received a copy of the GNU General Public License
// 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]
fn should_resolve_dapp() {
Expand All @@ -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);
}
Expand All @@ -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,
Expand All @@ -61,7 +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_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();
}
Loading

0 comments on commit bc3dacc

Please sign in to comment.