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

Fetch tests (first batch) #3977

Merged
merged 3 commits into from
Dec 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not pub fn fetch(self, fetch: T) -> Self?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would need type hints when calling ServerBuilder::new() in such case and builder would be tied to a single implementation of Fetch.
Currently you can just do ServerBuilder::new() to have a default implementation and then call fetch(fetch_instance) to change the type. We use different fetch only for tests.

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