Skip to content

Commit

Permalink
Integration tests: run isolated SQLite instance in tests
Browse files Browse the repository at this point in the history
  • Loading branch information
esiebert committed Jul 31, 2024
1 parent 17bcc70 commit 9e755ce
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 57 deletions.
58 changes: 45 additions & 13 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use anyhow::Result;
use sqlx::{migrate::MigrateDatabase, Sqlite, SqlitePool};
use thiserror::Error;
use tokio_stream::wrappers::TcpListenerStream;
use tonic_reflection::server::Builder as ReflectionServerBuilder;
Expand All @@ -8,10 +9,7 @@ use crate::grpc::{
auth::SessionAuthService, cycling_tracker::CyclingTrackerService,
BuildError as GRPCBuildError, Builder as GRPCBuilder, GRPC,
};
use crate::handler::{
database::{DatabaseError, SQLiteHandler},
WorkoutHandler,
};
use crate::handler::{SQLiteHandler, WorkoutHandler};
use crate::FILE_DESCRIPTOR_SET;

pub struct App {
Expand Down Expand Up @@ -42,11 +40,39 @@ impl App {

pub struct Builder {
grpc: Option<GRPC>,
db: Option<SqlitePool>,
}

impl Builder {
pub fn new() -> Self {
Self { grpc: None }
fn new() -> Self {
Self {
grpc: None,
db: None,
}
}

pub async fn setup_database(mut self, db_url: &str) -> Result<Self, BuildError> {
Sqlite::create_database(db_url)
.await
.map_err(|e| BuildError::DbCreationFailed(format!("{e:?}")))?;

let db = SqlitePool::connect(db_url)
.await
.map_err(|e| BuildError::DbConnectionFailed(format!("{e:?}")))?;

sqlx::migrate!()
.run(&db)
.await
.map_err(|e| BuildError::DbMigrationFailed(format!("{e:?}")))?;

self.db = Some(db);

Ok(self)
}

pub fn with_db(mut self, db: SqlitePool) -> Self {
self.db = Some(db);
self
}

pub async fn setup_grpc(
Expand All @@ -55,11 +81,10 @@ impl Builder {
with_tls: bool,
with_session_tokens: bool,
) -> Result<Self, BuildError> {
let auth = cycling_tracker::SessionAuthServer::new(SessionAuthService {});

let sqlite_handler = SQLiteHandler::new("sqlite:ct.db")
.await
.map_err(|e| BuildError::DatabaseFailure(e))?;
self.db.as_ref().ok_or(BuildError::DatabaseNotSet)?;
let sqlite_handler = SQLiteHandler {
db: self.db.clone().unwrap(),
};

let cts = cycling_tracker::CyclingTrackerServer::new(
CyclingTrackerService::new(WorkoutHandler { sqlite_handler }),
Expand All @@ -76,6 +101,7 @@ impl Builder {
grpc_builder = grpc_builder.with_tls()?;
}

let auth = cycling_tracker::SessionAuthServer::new(SessionAuthService {});
let grpc = grpc_builder
.add_auth_service(auth)
.add_reflection_service(refl)
Expand All @@ -101,10 +127,16 @@ impl Default for Builder {

#[derive(Debug, Error)]
pub enum BuildError {
#[error("Database not set: required to setup gRPC")]
DatabaseNotSet,
#[error("Failed to create database: {0}")]
DbCreationFailed(String),
#[error("Failed to connect to database: {0}")]
DbConnectionFailed(String),
#[error("Failed to migrate database: {0}")]
DbMigrationFailed(String),
#[error("Failed to build gRPC: {0}")]
GRPCBuildFailure(#[from] GRPCBuildError),
#[error("Failed to setup SQLite: {0}")]
DatabaseFailure(#[from] DatabaseError),
#[error("Failed to build reflection server: {0}")]
ReflectionBuildError(String),
#[error("gRPC service not set")]
Expand Down
4 changes: 2 additions & 2 deletions src/handler/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub mod database;
pub mod sqlite;
pub mod workout;

pub use database::SQLiteHandler;
pub use sqlite::SQLiteHandler;
pub use workout::WorkoutHandler;
21 changes: 2 additions & 19 deletions src/handler/database.rs → src/handler/sqlite.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,14 @@
use sqlx::{migrate::MigrateDatabase, Sqlite, SqlitePool};
use sqlx::SqlitePool;
use thiserror::Error;

use crate::cycling_tracker::{Measurement, WorkoutSummary};

#[derive(Clone)]
pub struct SQLiteHandler {
db: SqlitePool,
pub db: SqlitePool,
}

impl SQLiteHandler {
pub async fn new(db_url: &str) -> Result<Self, DatabaseError> {
Sqlite::create_database(db_url)
.await
.map_err(|e| DatabaseError::CreationFailed(format!("{e:?}")))?;

let db = SqlitePool::connect(db_url)
.await
.map_err(|e| DatabaseError::ConnectionFailed(format!("{e:?}")))?;

sqlx::migrate!()
.run(&db)
.await
.map_err(|e| DatabaseError::MigrationFailed(format!("{e:?}")))?;

Ok(Self { db })
}

pub async fn save_workout(&self, summary: &WorkoutSummary) -> i32 {
let result = sqlx::query!(
"INSERT INTO WORKOUT_SUMMARY VALUES (Null, $1, $2, $3, $4, $5)",
Expand Down
2 changes: 1 addition & 1 deletion src/handler/workout.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::cycling_tracker::{Measurement, Workout, WorkoutSummary};
use crate::handler::database::SQLiteHandler;
use crate::handler::SQLiteHandler;

#[derive(Clone)]
pub struct WorkoutHandler {
Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
info!("Starting gRPC server");

let app = App::builder()
.setup_database("sqlite:ct.db")
.await?
.setup_grpc("[::1]:10000", true, true)
.await?
.build()?;
Expand Down
4 changes: 3 additions & 1 deletion tests/common.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{pin::Pin, vec::IntoIter};

use sqlx::SqlitePool;
use tokio::{net::TcpListener, task::spawn};
use tokio_stream::{wrappers::TcpListenerStream, Iter, StreamExt};
use tonic::{transport::channel::Channel, Request};
Expand All @@ -11,12 +12,13 @@ pub struct TestEnvironment {
pub grpc_client: CyclingTrackerClient<Channel>,
}

pub async fn run_test_env() -> TestEnvironment {
pub async fn run_test_env(db: SqlitePool) -> TestEnvironment {
let addr = "127.0.0.1:0";

// Build app
let app = App::builder()
// Disable TLS and session tokens for test purposes
.with_db(db)
.setup_grpc(&addr, false, false)
.await
.expect("Failed to setup gRPC")
Expand Down
38 changes: 17 additions & 21 deletions tests/service/test_cycling_tracker.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use lazy_static::lazy_static;
use pretty_assertions::assert_eq;
use sqlx::SqlitePool;
use tonic::Request;

use crate::common::{run_test_env, stream_to_vec, vec_to_stream};
Expand Down Expand Up @@ -29,7 +30,7 @@ lazy_static! {
},
];
static ref WORKOUT_SUMMARY: WorkoutSummary = WorkoutSummary {
id: Some(0),
id: Some(1),
km_ridden: 53.5,
avg_speed: 30.0,
avg_watts: 300,
Expand All @@ -39,45 +40,40 @@ lazy_static! {
};
}

#[tokio::test]
async fn test_save_workout() {
let mut test_env = run_test_env().await;
#[sqlx::test]
async fn test_save_workout_and_get_measurements(db: SqlitePool) {
let mut test_env = run_test_env(db).await;

let request = Request::new(Workout {
let save_request = Request::new(Workout {
km_ridden: 53.5,
measurements: (*MEASUREMENTS).clone(),
});

let actual_response = test_env
.grpc_client
.save_workout(request)
.save_workout(save_request)
.await
.expect("Failed to save workout")
.into_inner();

assert_eq!(actual_response, *WORKOUT_SUMMARY);
}

#[tokio::test]
async fn test_get_measurements() {
let mut test_env = run_test_env().await;

let request = Request::new(WorkoutRequest { id: 1 });
let get_request = Request::new(WorkoutRequest { id: 1 });

let response_stream = test_env
.grpc_client
.get_measurements(request)
.get_measurements(get_request)
.await
.expect("Failed to get measurements")
.into_inner();

let actual_response = stream_to_vec(response_stream).await;
assert_eq!(actual_response, (*MEASUREMENTS).clone());
let actual_response_stream = stream_to_vec(response_stream).await;
assert_eq!(actual_response_stream, (*MEASUREMENTS).clone());
}

#[tokio::test]
async fn test_record_workout() {
let mut test_env = run_test_env().await;
#[sqlx::test]
async fn test_record_workout(db: SqlitePool) {
let mut test_env = run_test_env(db).await;

let request = vec_to_stream((*MEASUREMENTS).clone());

Expand All @@ -91,9 +87,9 @@ async fn test_record_workout() {
assert_eq!(actual_response, *WORKOUT_SUMMARY);
}

#[tokio::test]
async fn test_get_current_averages() {
let mut test_env = run_test_env().await;
#[sqlx::test]
async fn test_get_current_averages(db: SqlitePool) {
let mut test_env = run_test_env(db).await;

let request = vec_to_stream((*MEASUREMENTS).clone());

Expand Down

0 comments on commit 9e755ce

Please sign in to comment.