Skip to content

Commit

Permalink
chore: add intergration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Duyet Le committed Jan 31, 2022
1 parent b3ec226 commit afad35d
Show file tree
Hide file tree
Showing 9 changed files with 840 additions and 12 deletions.
18 changes: 17 additions & 1 deletion .github/workflows/build-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ jobs:
build:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:latest
env:
POSTGRES_DB: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_PORT: 5432
POSTGRES_USER: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout
uses: actions/checkout@v2
Expand All @@ -29,7 +45,7 @@ jobs:
run: cargo build --verbose

- name: Rust tests
run: cargo test --verbose
run: TEST_DATABASE_URL=postgres://postgres:postgres@localhost cargo test --verbose

- name: Set up QEMU
uses: docker/setup-qemu-action@v1
Expand Down
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ actix-cors = "0.5"
actix-web = "3"
actix-web-validator = "2.2.0"
ammonia = "3"
anyhow = "1.0.53"
chrono = { version = "0.4", features = ["serde"] }
diesel = {version = "1", features = ["postgres", "extras"]}
diesel_migrations = "1.4.0"
dotenv = "0.15"
env_logger = "0.9"
log = "0.4"
serde = "1"
serde_json = "1.0.78"
thiserror = "1.0.30"
uuid = { version = "0.6", features = ["serde", "v4"] }
validator = { version = "0.12", features = ["derive"] }

Expand Down
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#[macro_use]
extern crate diesel;
extern crate actix_web_validator;
#[macro_use]
extern crate diesel_migrations;
extern crate actix_web_validator;
extern crate dotenv;

pub mod response;
pub mod schema;
pub mod test_utils;
pub mod v1;

pub use diesel::pg::PgConnection;
Expand Down
6 changes: 4 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ pub async fn ping() -> impl Responder {
HttpResponse::Ok().body("pong")
}

// Embed the migration into the binary
embed_migrations!("./migrations");

#[actix_web::main]
async fn main() -> std::io::Result<()> {
env::set_var("RUST_LOG", "actix_web=debug,actix_server=info");
Expand All @@ -42,8 +45,7 @@ async fn main() -> std::io::Result<()> {
.build(manager)
.expect("Failed to create connection pool");

// Migration schema
embed_migrations!("./migrations");
// Start migration if needed
let conn = pool.get().expect("could not get db connection from pool");
embedded_migrations::run_with_output(&conn, &mut std::io::stdout()).unwrap();

Expand Down
9 changes: 8 additions & 1 deletion src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use actix_web::error::ResponseError;
use actix_web::{error, HttpRequest, HttpResponse};
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use thiserror::Error;

#[derive(Debug, Deserialize, Serialize)]
pub struct ListResp<T> {
Expand Down Expand Up @@ -50,7 +51,7 @@ impl Message {
}
}

#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Error, Deserialize, Serialize)]
pub struct ErrorResp {
pub error: String,
}
Expand All @@ -61,6 +62,12 @@ impl ErrorResp {
error: error.to_string(),
}
}

pub fn from(error: diesel::result::Error) -> Self {
Self {
error: error.to_string(),
}
}
}

impl Display for ErrorResp {
Expand Down
106 changes: 106 additions & 0 deletions src/test_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use crate::diesel::Connection;
use crate::diesel::RunQueryDsl;
use diesel::pg::PgConnection;
use diesel::r2d2::{ConnectionManager, Pool};
use diesel::sql_query;
use diesel_migrations::embed_migrations;
use std::env;

use crate::DBPool;

pub struct TestContext {
conn: PgConnection,
base_url: String,
db_name: String,
}

embed_migrations!("migrations/");

impl TestContext {
pub fn new(db_name: &str) -> Self {
let base_url =
env::var("TEST_DATABASE_URL").unwrap_or_else(|_| "postgres://localhost".to_string());
println!(
"Creating test database {} in {}, please set TEST_DATABASE_URL to change this",
db_name, base_url
);

let database_url = format!("{}/postgres", base_url);
let conn = PgConnection::establish(&database_url).expect("Could not connect to database");

// Create database
sql_query(format!("CREATE DATABASE {}", db_name).as_str())
.execute(&conn)
.expect("Failed to create database");

// Migation
let conn_migrations = PgConnection::establish(&format!("{}/{}", base_url, db_name))
.unwrap_or_else(|_| panic!("Could not connect to database {}", db_name));
embedded_migrations::run(&conn_migrations).expect("Failed to run migrations");

Self {
conn,
base_url,
db_name: db_name.to_string(),
}
}

pub fn get_conn(&self) -> PgConnection {
let database_url = format!("{}/{}", self.base_url, self.db_name);

PgConnection::establish(&database_url).expect("Could not connect to database")
}

pub fn get_pool(&self) -> DBPool {
let database_url = format!("{}/{}", self.base_url, self.db_name);
let manager = ConnectionManager::<PgConnection>::new(database_url);

Pool::builder()
.build(manager)
.expect("Failed to create connection pool")
}
}

impl Drop for TestContext {
fn drop(&mut self) {
println!("Dropping test database {}", self.db_name);

// Postgres will refuse to delete a database
// if there is any connected user
sql_query(format!(
"SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = '{}';",
self.db_name
))
.execute(&self.conn)
.unwrap();

// Drop the database
sql_query(format!("DROP DATABASE {}", self.db_name).as_str())
.execute(&self.conn)
.unwrap_or_else(|_| panic!("Couldn't drop database {}", self.db_name));
}
}

pub use actix_web::body::{Body, ResponseBody};

pub trait BodyTest {
fn as_str(&self) -> &str;
}

/// Get the body from ResponseBody::Body
impl BodyTest for ResponseBody<Body> {
fn as_str(&self) -> &str {
match self {
ResponseBody::Body(ref b) => match b {
Body::Bytes(ref by) => std::str::from_utf8(by).unwrap(),
_ => panic!(),
},
ResponseBody::Other(ref b) => match b {
Body::Bytes(ref by) => std::str::from_utf8(by).unwrap(),
_ => panic!(),
},
}
}
}
Loading

0 comments on commit afad35d

Please sign in to comment.