Chapter 3 Part 1 - Patches (#4)

* Adjust content encoding.

* Refactor assertion.

* Add one more test case

* Fix assertions.

* Fix route.

* Stricter assertions

* Few fixes.

* Formatting
This commit is contained in:
Luca Palmieri 2020-08-31 02:05:21 +01:00 committed by GitHub
parent 656ee46c09
commit 24dccda00c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 49 additions and 23 deletions

1
Cargo.lock generated
View file

@ -444,7 +444,6 @@ dependencies = [
"config 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "config 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest 0.10.7 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.10.7 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)",
"sqlx 0.4.0-beta.1 (registry+https://github.com/rust-lang/crates.io-index)", "sqlx 0.4.0-beta.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
"uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -25,4 +25,3 @@ chrono = "0.4.15"
[dev-dependencies] [dev-dependencies]
reqwest = { version = "0.10.7", features = ["json"] } reqwest = { version = "0.10.7", features = ["json"] }
serde_json = "1.0.57"

View file

@ -1,3 +1,4 @@
application_port: 8000
database: database:
host: "localhost" host: "localhost"
port: 5432 port: 5432

View file

@ -10,8 +10,6 @@ DB_PASSWORD="${POSTGRES_PASSWORD:=password}"
DB_NAME="${POSTGRES_DB:=newsletter}" DB_NAME="${POSTGRES_DB:=newsletter}"
# Check if a custom port has been set, otherwise default to '5432' # Check if a custom port has been set, otherwise default to '5432'
DB_PORT="${POSTGRES_PORT:=5432}" DB_PORT="${POSTGRES_PORT:=5432}"
# Check if a custom host has been set, otherwise default to 'localhost'
DB_HOST="${POSTGRES_HOST:=localhost}"
# Allow to skip Docker if a dockerized Postgres database is already running # Allow to skip Docker if a dockerized Postgres database is already running
if [[ -z "${SKIP_DOCKER}" ]] if [[ -z "${SKIP_DOCKER}" ]]
@ -28,14 +26,14 @@ then
fi fi
# Keep pinging Postgres until it's ready to accept commands # Keep pinging Postgres until it's ready to accept commands
until PGPASSWORD="${DB_PASSWORD}" psql -h "${DB_HOST}" -U "${DB_USER}" -p "${DB_PORT}" -d "postgres" -c '\q'; do until PGPASSWORD="${DB_PASSWORD}" psql -h "localhost" -U "${DB_USER}" -p "${DB_PORT}" -d "postgres" -c '\q'; do
>&2 echo "Postgres is still unavailable - sleeping" >&2 echo "Postgres is still unavailable - sleeping"
sleep 1 sleep 1
done done
>&2 echo "Postgres is up and running on port ${DB_PORT} - running migrations now!" >&2 echo "Postgres is up and running on port ${DB_PORT} - running migrations now!"
export DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME} export DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@localhost:${DB_PORT}/${DB_NAME}
sqlx database create sqlx database create
sqlx migrate run sqlx migrate run

View file

@ -1,6 +1,7 @@
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
pub struct Settings { pub struct Settings {
pub database: DatabaseSettings, pub database: DatabaseSettings,
pub application_port: u16,
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]

View file

@ -11,7 +11,8 @@ async fn main() -> Result<(), anyhow::Error> {
.await .await
.map_err(anyhow::Error::from) .map_err(anyhow::Error::from)
.with_context(|| "Failed to connect to Postgres.")?; .with_context(|| "Failed to connect to Postgres.")?;
let address = TcpListener::bind("127.0.0.1:8000")?; let address = format!("127.0.0.1:{}", configuration.application_port);
run(address, connection_pool)?.await?; let listener = TcpListener::bind(address)?;
run(listener, connection_pool)?.await?;
Ok(()) Ok(())
} }

View file

@ -10,7 +10,7 @@ pub struct SubscribeRequest {
} }
pub async fn subscribe( pub async fn subscribe(
payload: web::Json<SubscribeRequest>, payload: web::Form<SubscribeRequest>,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
) -> Result<HttpResponse, HttpResponse> { ) -> Result<HttpResponse, HttpResponse> {
sqlx::query!( sqlx::query!(

View file

@ -14,6 +14,7 @@ pub async fn spawn_app() -> TestApp {
let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind random port"); let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind random port");
// We retrieve the port assigned to us by the OS // We retrieve the port assigned to us by the OS
let port = listener.local_addr().unwrap().port(); let port = listener.local_addr().unwrap().port();
let address = format!("http://127.0.0.1:{}", port);
let mut configuration = get_configuration().expect("Failed to read configuration."); let mut configuration = get_configuration().expect("Failed to read configuration.");
configuration.database.database_name = Uuid::new_v4().to_string(); configuration.database.database_name = Uuid::new_v4().to_string();
@ -22,9 +23,8 @@ pub async fn spawn_app() -> TestApp {
let server = run(listener, connection_pool.clone()).expect("Failed to bind address"); let server = run(listener, connection_pool.clone()).expect("Failed to bind address");
let _ = tokio::spawn(server); let _ = tokio::spawn(server);
// We return the application address to the caller!
TestApp { TestApp {
address: format!("http://127.0.0.1:{}", port), address,
db_pool: connection_pool, db_pool: connection_pool,
} }
} }

View file

@ -1,34 +1,61 @@
use crate::helpers::spawn_app; use crate::helpers::spawn_app;
use serde_json::json;
#[actix_rt::test] #[actix_rt::test]
async fn subscribe_works() { async fn subscribe_accepts_valid_form_data() {
// Arrange // Arrange
let app = spawn_app().await; let app = spawn_app().await;
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let email = "myemail@mydomain.com"; let body = "name=le%20guin&email=ursula_le_guin%40gmail.com";
let name = "my name";
let payload = json!({
"email": email,
"name": name
});
// Act // Act
let response = client let response = client
.post(&format!("{}/subscriptions", &app.address)) .post(&format!("{}/subscriptions", &app.address))
.json(&payload) .header("Content-Type", "application/x-www-form-urlencoded")
.body(body)
.send() .send()
.await .await
.expect("Failed to execute request."); .expect("Failed to execute request.");
// Assert // Assert
assert!(response.status().is_success()); assert_eq!(200, response.status().as_u16());
let saved = sqlx::query!("SELECT email, name FROM subscriptions",) let saved = sqlx::query!("SELECT email, name FROM subscriptions",)
.fetch_one(&app.db_pool) .fetch_one(&app.db_pool)
.await .await
.expect("Failed to fetch saved subscription."); .expect("Failed to fetch saved subscription.");
assert_eq!(saved.email, email); assert_eq!(saved.email, "ursula_le_guin@gmail.com");
assert_eq!(saved.name, name); assert_eq!(saved.name, "le guin");
}
#[actix_rt::test]
async fn subscribe_returns_a_400_when_data_is_missing() {
// Arrange
let app = spawn_app().await;
let client = reqwest::Client::new();
let test_cases = vec![
("name=le%20guin", "missing the email"),
("email=ursula_le_guin%40gmail.com", "missing the name"),
("", "missing both name and email"),
];
for (invalid_body, error_message) in test_cases {
// Act
let response = client
.post(&format!("{}/subscriptions", &app.address))
.header("Content-Type", "application/x-www-form-urlencoded")
.body(invalid_body)
.send()
.await
.expect("Failed to execute request.");
// Assert
assert_eq!(
400,
response.status().as_u16(),
// Additional customised error message on test failure
"The API did not fail with 400 Bad Request when the payload was {}.",
error_message
);
}
} }