Skip to content

Begin working on a revamped test harness #1697

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
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
3 changes: 3 additions & 0 deletions src/tests/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ macro_rules! bad_resp {
}};
}

mod helpers;
mod prelude;

mod badge;
mod builders;
mod categories;
Expand Down
2 changes: 1 addition & 1 deletion src/tests/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ impl<'a> CrateBuilder<'a> {
self
}

fn build(mut self, connection: &PgConnection) -> CargoResult<Crate> {
pub fn build(mut self, connection: &PgConnection) -> CargoResult<Crate> {
use diesel::{insert_into, select, update};

let mut krate = self
Expand Down
3 changes: 3 additions & 0 deletions src/tests/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod app;
pub mod request;
pub mod response;
94 changes: 94 additions & 0 deletions src/tests/helpers/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use super::request::RequestBuilder;
use cargo_registry::models::{ApiToken, Email, NewUser, User};
use cargo_registry::util::CargoResult;
use conduit::Method;
use conduit_middleware::MiddlewareBuilder;
use diesel::prelude::*;
use std::sync::Arc;

pub struct App {
app: Arc<cargo_registry::App>,
middleware: MiddlewareBuilder,
_bomb: crate::record::Bomb,
}

impl App {
pub fn new() -> Self {
let (bomb, app, middleware) = crate::app();
Self {
app,
middleware,
_bomb: bomb,
}
}

/// Obtain the database connection and pass it to the closure
///
/// Our tests share a database connection with the app server, so it's
/// important that the conenction is dropped before requests are made to
/// ensure it's available for the server to use. The connection will be
/// returned to the server after the given function returns.
pub fn db<T, F>(&self, f: F) -> CargoResult<T>
where
F: FnOnce(&PgConnection) -> CargoResult<T>,
{
let conn = self.app.diesel_database.get()?;
f(&conn)
}

/// Create a new user in the database with the given id
pub fn create_user(&self, username: &str) -> CargoResult<User> {
use cargo_registry::schema::emails;

self.db(|conn| {
let new_user = NewUser {
email: Some("[email protected]"),
..crate::new_user(username)
};
let user = new_user.create_or_update(conn)?;
diesel::update(Email::belonging_to(&user))
.set(emails::verified.eq(true))
.execute(conn)?;
Ok(user)
})
}

/// Sets the database in read only mode.
///
/// Any attempts to modify the database after calling this function will
/// result in an error.
pub fn set_read_only(&self) -> CargoResult<()> {
self.db(|conn| {
diesel::sql_query("SET TRANSACTION READ ONLY").execute(conn)?;
Ok(())
})
}

/// Create an HTTP `GET` request
pub fn get(&self, path: &str) -> RequestBuilder<'_> {
RequestBuilder::new(&self.middleware, Method::Get, path)
}

/// Create an HTTP `PUT` request
pub fn put(&self, path: &str) -> RequestBuilder<'_> {
RequestBuilder::new(&self.middleware, Method::Put, path)
}

/// Create an HTTP `DELETE` request
pub fn delete(&self, path: &str) -> RequestBuilder<'_> {
RequestBuilder::new(&self.middleware, Method::Delete, path)
}

/// Returns the first API token for the given user, or creates a new one
pub fn token_for(&self, user: &User) -> CargoResult<ApiToken> {
self.db(|conn| {
ApiToken::belonging_to(user)
.first(conn)
.optional()?
.map(Ok)
.unwrap_or_else(|| {
ApiToken::insert(conn, user.id, "test_token").map_err(Into::into)
})
})
}
}
63 changes: 63 additions & 0 deletions src/tests/helpers/request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use super::response::{Response, ResponseError};
use cargo_registry::middleware::current_user::AuthenticationSource;
use cargo_registry::models::{ApiToken, User};
use conduit::{Handler, Method, Request};
use conduit_middleware::MiddlewareBuilder;
use conduit_test::MockRequest;

pub struct RequestBuilder<'a> {
middleware: &'a MiddlewareBuilder,
request: MockRequest,
}

impl<'a> RequestBuilder<'a> {
pub(super) fn new(middleware: &'a MiddlewareBuilder, method: Method, path: &str) -> Self {
Self {
middleware,
request: MockRequest::new(method, path),
}
.with_header("User-Agent", "conduit-test")
}

/// Sends the request signed in as the given user
pub fn signed_in_as(mut self, user: &User) -> Self {
self.clear_auth();
self.request.mut_extensions().insert(user.clone());
self.request
.mut_extensions()
.insert(AuthenticationSource::SessionCookie);
self
}

/// Uses the given token for authentication
pub fn with_token(mut self, token: &ApiToken) -> Self {
self.clear_auth();
self.with_header("Authorization", &token.token)
}

pub fn with_header(mut self, name: &str, value: &str) -> Self {
self.request.header(name, value);
self
}

pub fn with_body<T: Into<Vec<u8>>>(mut self, body: T) -> Self {
self.request.with_body(&body.into());
self
}

/// Send the request
///
/// Returns an error if any of the middlewares returned an error, or if
/// the response status was >= 400.
pub fn send(mut self) -> Result<Response, ResponseError> {
Response::new(self.middleware.call(&mut self.request)?)
}

fn clear_auth(&mut self) {
self.request.mut_extensions().remove::<User>();
self.request
.mut_extensions()
.remove::<AuthenticationSource>();
self.request.header("Authorization", "");
}
}
93 changes: 93 additions & 0 deletions src/tests/helpers/response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use cargo_registry::util::{CargoError, CargoResult};
use std::error::Error;
use std::fmt;

pub struct Response {
inner: conduit::Response,
body: String,
}

impl Response {
pub(super) fn new(mut inner: conduit::Response) -> Result<Self, ResponseError> {
use ResponseError::*;

let mut body = Vec::new();
inner.body.write_body(&mut body).unwrap();

let resp = Response {
inner,
body: String::from_utf8(body).unwrap(),
};

match resp.status() {
400...499 => Err(BadRequest(resp)),
500...599 => Err(ServerError(resp)),
_ => Ok(resp),
}
}

pub fn status(&self) -> u32 {
self.inner.status.0
}

pub fn text(&self) -> &str {
&self.body
}

pub fn json<'a, T>(&'a self) -> CargoResult<T>
where
T: serde::Deserialize<'a>,
{
serde_json::from_str(self.text()).map_err(Into::into)
}
}

pub enum ResponseError {
MiddlewareError(Box<dyn CargoError>),
BadRequest(Response),
ServerError(Response),
}

impl From<Box<dyn Error + Send>> for ResponseError {
fn from(e: Box<dyn Error + Send>) -> Self {
ResponseError::MiddlewareError(CargoError::from_std_error(e))
}
}

impl fmt::Debug for ResponseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ResponseError::*;
match self {
MiddlewareError(e) => write!(f, "MiddlewareError({:?})", e),
BadRequest(e) => write!(f, "BadRequest({:?})", e.text()),
ServerError(e) => write!(f, "ServerError({:?})", e.text()),
}
}
}

impl fmt::Display for ResponseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ResponseError::*;
match self {
MiddlewareError(e) => write!(f, "middleware error: {}", e),
BadRequest(e) => write!(f, "bad request: {}", e.text()),
ServerError(e) => write!(f, "server error: {}", e.text()),
}
}
}

impl Error for ResponseError {}

pub trait ResultExt {
fn allow_errors(self) -> CargoResult<Response>;
}

impl ResultExt for Result<Response, ResponseError> {
fn allow_errors(self) -> CargoResult<Response> {
use ResponseError::*;
self.or_else(|e| match e {
MiddlewareError(e) => Err(e),
BadRequest(r) | ServerError(r) => Ok(r),
})
}
}
73 changes: 73 additions & 0 deletions src/tests/http-data/krate_only_owners_can_yank
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
[
{
"request": {
"uri": "http://alexcrichton-test.s3.amazonaws.com/crates/foo_not/foo_not-1.0.0.crate",
"method": "PUT",
"headers": [
[
"host",
"alexcrichton-test.s3.amazonaws.com"
],
[
"date",
"Fri, 15 Sep 2017 07:53:06 -0700"
],
[
"accept",
"*/*"
],
[
"user-agent",
"reqwest/0.9.1"
],
[
"authorization",
"AWS AKIAICL5IWUZYWWKA7JA:xCp3sUdUdmScjI6ct58zFv6BoGQ="
],
[
"content-length",
"35"
],
[
"accept-encoding",
"gzip"
],
[
"content-type",
"application/x-tar"
]
],
"body": "H4sIAAAAAAAA/+3AAQEAAACCIP+vbkhQwKsBLq+17wAEAAA="
},
"response": {
"status": 200,
"headers": [
[
"ETag",
"\"f9016ad360cebb4fe2e6e96e5949f022\""
],
[
"x-amz-id-2",
"FCKNKZUo5EeUNwVyhZ9P7ehfXoctqePzXx2RSE1VxoSX9rdfskkyAJUNHAF2AQojRon00LfTLPY="
],
[
"x-amz-request-id",
"3233F8227A852593"
],
[
"Server",
"AmazonS3"
],
[
"content-length",
"0"
],
[
"date",
"Fri, 15 Sep 2017 14:53:07 GMT"
]
],
"body": ""
}
}
]
Loading