Skip to content

Commit

Permalink
feat: sqlx migrations in test hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
matthew-hagemann committed Aug 30, 2023
1 parent c2c2ef5 commit 8bef01a
Show file tree
Hide file tree
Showing 14 changed files with 159 additions and 67 deletions.
4 changes: 4 additions & 0 deletions sql/bootstrap/roles.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
DROP USER IF EXISTS service;
REVOKE CONNECT ON DATABASE ratings FROM migration_user;
REVOKE USAGE, CREATE ON SCHEMA public FROM migration_user;
DROP USER IF EXISTS migration_user;
6 changes: 6 additions & 0 deletions sql/bootstrap/roles.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE USER migration_user WITH PASSWORD 'strongpassword';
CREATE USER service WITH PASSWORD 'covfefe!1';
CREATE DATABASE ratings;
/c ratings;
GRANT CONNECT ON DATABASE ratings TO migration_user;
GRANT USAGE, CREATE ON SCHEMA public TO migration_user;
Empty file removed sql/migrations/.gitkeep
Empty file.
9 changes: 9 additions & 0 deletions sql/migrations/20230829085908_ratings_init.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
REVOKE ALL PRIVILEGES ON TABLE users FROM service;
REVOKE USAGE, SELECT ON SEQUENCE users_id_seq FROM service;
REVOKE ALL PRIVILEGES ON TABLE votes FROM service;
REVOKE USAGE, SELECT ON SEQUENCE votes_id_seq FROM service;
REVOKE CONNECT ON DATABASE ratings FROM service;

DROP TABLE IF EXISTS votes;
DROP TABLE IF EXISTS users;

Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
--
-- Create database and then execute the second set of commands when connected
-- to the ratings database
--
Expand All @@ -8,6 +7,7 @@
-- CREATE DATABASE IF EXISTS ratings;

-- Stage 2
--

CREATE TABLE users (
id SERIAL PRIMARY KEY,
Expand All @@ -31,8 +31,6 @@ CREATE TABLE votes (
-- can't vote more than once for the same snap revision.
CREATE UNIQUE INDEX idx_votes_unique_user_snap ON votes (user_id_fk, snap_id, snap_revision);

-- Permissions
CREATE USER service WITH PASSWORD 'covfefe!1';
GRANT ALL PRIVILEGES ON TABLE users TO service;
GRANT USAGE, SELECT ON SEQUENCE users_id_seq TO service;
GRANT ALL PRIVILEGES ON TABLE votes TO service;
Expand Down
4 changes: 0 additions & 4 deletions sql/teardown.sql

This file was deleted.

1 change: 1 addition & 0 deletions src/utils/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub struct Config {
pub name: String,
pub port: u16,
pub postgres_uri: String,
pub migration_postgres_uri: String,
}

impl Config {
Expand Down
44 changes: 44 additions & 0 deletions src/utils/migrator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use std::error::Error;
use std::fmt::{Debug, Formatter};
use std::sync::Arc;

use sqlx::{postgres::PgPoolOptions, PgPool};
use tracing::info;

const MIGRATIONS_PATH: &str = "sql/migrations";

Check warning on line 8 in src/utils/migrator.rs

View workflow job for this annotation

GitHub Actions / build / Build Ratings

constant `MIGRATIONS_PATH` is never used

#[derive(Clone)]
pub struct Migrator {
pub pool: Arc<PgPool>,
}

impl Migrator {
pub async fn new(uri: &str) -> Result<Migrator, Box<dyn Error>> {

Check warning on line 16 in src/utils/migrator.rs

View workflow job for this annotation

GitHub Actions / build / Build Ratings

associated items `new`, `run`, and `revert` are never used
let pool = PgPoolOptions::new().max_connections(1).connect(uri).await?;
let pool = Arc::new(pool);
Ok(Migrator { pool })
}

pub async fn run(&self) -> Result<(), sqlx::Error> {
let m = sqlx::migrate::Migrator::new(std::path::Path::new(MIGRATIONS_PATH)).await?;

m.run(&mut self.pool.acquire().await?).await?;
info!("migrator.run()");
Ok(())
}

pub async fn revert(&self) -> Result<(), sqlx::Error> {
let m = sqlx::migrate::Migrator::new(std::path::Path::new(MIGRATIONS_PATH)).await?;

m.undo(&mut self.pool.acquire().await?, 1).await?;

info!("migrator.revert()");
Ok(())
}
}

impl Debug for Migrator {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("Migrator { migrations_pool }")
}
}
2 changes: 2 additions & 0 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
pub mod config;
pub mod infrastructure;
pub mod jwt;
pub mod migrator;

pub use config::Config;
pub use infrastructure::Infrastructure;
pub use migrator::Migrator;
29 changes: 17 additions & 12 deletions tests/app_tests/lifecycle_test.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use futures::FutureExt;
use ratings::{
app::AppContext,
utils::{Config, Infrastructure},
utils::{Config, Infrastructure, Migrator},
};

use super::super::helpers::with_lifecycle::with_lifecycle;
Expand All @@ -19,17 +19,22 @@ async fn app_lifecycle_test() -> Result<(), Box<dyn std::error::Error>> {
let infra = Infrastructure::new(&config).await?;
let app_ctx = AppContext::new(&config, infra);

with_lifecycle(async {
let data = TestData {
user_client: Some(UserClient::new(&config.socket())),
app_ctx,
id: None,
token: None,
app_client: Some(AppClient::new(&config.socket())),
snap_id: Some(data_faker::rnd_id()),
};
vote_once(data).then(vote_up).await;
})
let migrator = Migrator::new(&config.migration_postgres_uri).await?;
let data = TestData {
user_client: Some(UserClient::new(&config.socket())),
app_ctx,
id: None,
token: None,
app_client: Some(AppClient::new(&config.socket())),
snap_id: Some(data_faker::rnd_id()),
};

with_lifecycle(
async {
vote_once(data.clone()).then(vote_up).await;
},
migrator,
)
.await;
Ok(())
}
Expand Down
33 changes: 23 additions & 10 deletions tests/helpers/hooks.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
use std::sync::Arc;
use std::sync::{Arc, Once};

use once_cell::sync::Lazy;
use ratings::utils::Migrator;
use tokio::sync::Mutex;

static INITIALIZATION_FLAG: Lazy<Arc<Mutex<bool>>> = Lazy::new(|| Arc::new(Mutex::new(false)));

pub async fn before_all() {
let mutex = Arc::clone(&*INITIALIZATION_FLAG);
let mut initialised = mutex.lock().await;

if !*initialised {
*initialised = true;
static INIT: Once = Once::new();
static TEST_COUNTER: Lazy<Arc<Mutex<i32>>> = Lazy::new(|| Arc::new(Mutex::new(0)));

pub async fn before_all(migrator: Migrator) {
INIT.call_once(|| {
tracing_subscriber::fmt().init();
});
if let Err(e) = migrator.run().await {
panic!("{}", e)
}

let counter = Arc::clone(&*TEST_COUNTER);
let mut test_counter = counter.lock().await;
*test_counter += 1;
}

pub async fn after_all() {}
pub async fn after_all(migrator: Migrator) {
let counter = Arc::clone(&*TEST_COUNTER);
let mut test_counter = counter.lock().await;
*test_counter -= 1;
if *test_counter == 0 {
if let Err(e) = migrator.revert().await {
panic!("{}", e)
}
}
}
7 changes: 4 additions & 3 deletions tests/helpers/with_lifecycle.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use std::future::Future;

use crate::helpers::hooks::{after_all, before_all};
use ratings::utils::Migrator;

pub async fn with_lifecycle<F>(f: F)
pub async fn with_lifecycle<F>(f: F, migrator: Migrator)
where
F: Future<Output = ()>,
{
before_all().await;
before_all(migrator.clone()).await;
f.await;
after_all().await;
after_all(migrator.clone()).await;
}
46 changes: 27 additions & 19 deletions tests/user_tests/reject_invalid_register_test.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,50 @@
use ratings::utils::Config;
use ratings::utils::{Config, Migrator};
use tonic::Code;

use super::super::helpers::{client_user::UserClient, with_lifecycle::with_lifecycle};

#[tokio::test]
async fn blank() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::load()?;
let migrator = Migrator::new(&config.migration_postgres_uri).await?;

with_lifecycle(async {
let id = "";
let client = UserClient::new(&config.socket());
with_lifecycle(
async {
let id = "";
let client = UserClient::new(&config.socket());

match client.register(id).await {
Ok(response) => panic!("expected Err but got Ok: {response:?}"),
Err(status) => {
assert_eq!(status.code(), Code::InvalidArgument)
match client.register(id).await {
Ok(response) => panic!("expected Err but got Ok: {response:?}"),
Err(status) => {
assert_eq!(status.code(), Code::InvalidArgument)
}
}
}
})
},
migrator,
)
.await;
Ok(())
}

#[tokio::test]
async fn wrong_length() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::load()?;
let migrator = Migrator::new(&config.migration_postgres_uri).await?;

with_lifecycle(async {
let client_hash = "foobarbazbun";
let client = UserClient::new(&config.socket());
with_lifecycle(
async {
let client_hash = "foobarbazbun";
let client = UserClient::new(&config.socket());

match client.register(client_hash).await {
Ok(response) => panic!("expected Err but got Ok: {response:?}"),
Err(status) => {
assert_eq!(status.code(), Code::InvalidArgument)
match client.register(client_hash).await {
Ok(response) => panic!("expected Err but got Ok: {response:?}"),
Err(status) => {
assert_eq!(status.code(), Code::InvalidArgument)
}
}
}
})
},
migrator,
)
.await;
Ok(())
}
37 changes: 21 additions & 16 deletions tests/user_tests/simple_lifecycle_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use super::super::helpers::client_user::UserClient;
use super::super::helpers::with_lifecycle::with_lifecycle;
use futures::FutureExt;
use ratings::app::AppContext;
use ratings::utils::{self, Infrastructure};
use ratings::utils::{self, Infrastructure, Migrator};
use sqlx::Row;

use utils::Config;
Expand All @@ -16,22 +16,27 @@ async fn user_simple_lifecycle_test() -> Result<(), Box<dyn std::error::Error>>
let config = Config::load()?;
let infra = Infrastructure::new(&config).await?;
let app_ctx = AppContext::new(&config, infra);
let migrator = Migrator::new(&config.migration_postgres_uri).await?;

let data = TestData {
user_client: Some(UserClient::new(&config.socket())),
app_ctx,
id: None,
token: None,
app_client: None,
snap_id: None,
};

with_lifecycle(async {
let data = TestData {
user_client: Some(UserClient::new(&config.socket())),
app_ctx,
id: None,
token: None,
app_client: None,
snap_id: None,
};
register(data)
.then(authenticate)
.then(vote)
.then(delete)
.await;
})
with_lifecycle(
async {
register(data.clone())
.then(authenticate)
.then(vote)
.then(delete)
.await;
},
migrator,
)
.await;
Ok(())
}
Expand Down

0 comments on commit 8bef01a

Please sign in to comment.