Skip to content

Commit

Permalink
Merge pull request #41 from rodneylab/test__refactor_post_tests
Browse files Browse the repository at this point in the history
test: ☑️ refactor post model tests
  • Loading branch information
rodneylab authored Nov 25, 2024
2 parents 7436ea5 + 3a8ad11 commit d452796
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 173 deletions.
5 changes: 5 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@ repos:
- id: check-json
exclude: .vscode
- id: no-commit-to-branch
- repo: https://github.com/EmbarkStudios/cargo-deny
rev: 0.16.2
hooks:
- id: cargo-deny
args: ["--all-features", "check"]
10 changes: 5 additions & 5 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion src/model/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mod post;
pub mod post;

#[cfg(test)]
mod tests;
Expand Down
174 changes: 7 additions & 167 deletions src/model/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,34 @@ pub struct Post {
/// Detail of the user input error
pub struct UserInputError {
/// Field which had the invalid data
field: String,
pub field: String,

/// Error description
message: String,
pub message: String,

/// Input value for field
received: String,
pub received: String,
}

/// Errors generated while parsing or validating input parameters
#[derive(Interface)]
#[graphql(field(name = "message", ty = "String"))]
pub enum ValidationError {
/// User input error, such as requesting an operation on a post with an `id` that does not
/// exist
/// User input error, such as requesting an operation on a post with an `id` that does
/// not exist
UserInputError(UserInputError),
}

/// Response sent on valid delete draft mutation
#[derive(Debug, PartialEq, SimpleObject)]
pub struct DeleteDraftSuccessResponse {
post: Post,
pub post: Post,
}

/// Response sent on delete draft mutation with validation issues identified
#[derive(Debug, PartialEq, SimpleObject)]
pub struct DeleteDraftErrorResponse {
error: UserInputError,
pub error: UserInputError,
}

/// Union of responses sent on delete draft mutation
Expand Down Expand Up @@ -258,163 +258,3 @@ RETURNING
)),
}
}

#[cfg(test)]
mod tests {
use sqlx::{sqlite::SqlitePoolOptions, SqlitePool};

use crate::{
database::run_migrations,
model::post::{
create_draft_mutation, delete_draft_mutation, publish_mutation,
DeleteDraftErrorResponse, DeleteDraftResponse, DeleteDraftSuccessResponse,
UserInputError,
},
};

use super::{posts_query, Post};

/// Generates fresh in-memory `SQLite` database and runs migrations. Can be called from each
/// test.
async fn get_db_pool() -> SqlitePool {
let database_url = "sqlite://:memory:";

let db_pool = SqlitePoolOptions::new()
.max_connections(1)
.connect(database_url)
.await
.unwrap();

run_migrations(&db_pool).await;

db_pool
}

#[tokio::test]
async fn posts_query_returns_expected_output_with_no_posts() {
// arrange
let db_pool = get_db_pool().await;

// act
let result = posts_query(&db_pool).await.unwrap();

// assert
assert_eq!(result, Vec::<Post>::new());
}

#[tokio::test]
async fn posts_query_returns_expected_output_with_posts() {
// arrange
let db_pool = get_db_pool().await;
let title = String::from("New Post Title");
let body = String::from("# New Post\nNew post body");
let Post { id, .. } = create_draft_mutation(&db_pool, &title, &body)
.await
.unwrap();
let _ = publish_mutation(&db_pool, id).await;

// act
let result = posts_query(&db_pool).await.unwrap();

// assert
assert_eq!(
result,
vec![Post {
id,
title,
body,
published: true
}]
);
}

#[tokio::test]
async fn create_draft_mutation_fails_if_db_is_not_initialised() {
// arrange
let database_url = "sqlite://:memory:";

let db_pool = SqlitePoolOptions::new()
.max_connections(1)
.connect(database_url)
.await
.unwrap();

// act
let outcome = create_draft_mutation(&db_pool, "Draft Post Title", "Draft Post Body")
.await
.unwrap_err();

// assert
assert_eq!(format!("{outcome}"), "run create draft mutation for post");
let mut chain = outcome.chain();
assert_eq!(
chain.next().map(|val| format!("{val}")),
Some(String::from("run create draft mutation for post"))
);
assert_eq!(
chain.next().map(|val| format!("{val}")),
Some(String::from(
"error returned from database: (code: 1) no such table: Post"
))
);
assert_eq!(
chain.next().map(|val| format!("{val}")),
Some(String::from("(code: 1) no such table: Post"))
);
assert_eq!(chain.next().map(|val| format!("{val}")), None);
}

#[tokio::test]
async fn create_draft_mutation_returns_error_message_if_draft_does_not_exist() {
// arrange
let db_pool = get_db_pool().await;
let title = String::from("New Post Title");
let body = String::from("# New Post\nNew post body");
let Post { id, .. } = create_draft_mutation(&db_pool, &title, &body)
.await
.unwrap();
let _ = publish_mutation(&db_pool, id).await;

// act
let outcome = delete_draft_mutation(&db_pool, 999).await.unwrap();

// assert
assert_eq!(
outcome,
DeleteDraftResponse::DeleteDraftErrorResponse(DeleteDraftErrorResponse {
error: UserInputError {
field: String::from("id"),
message: String::from("Did not find draft post with id `999`"),
received: String::from("999")
}
})
);
}

#[tokio::test]
async fn create_draft_mutation_returns_draft_on_valid_input() {
// arrange
let db_pool = get_db_pool().await;
let title = String::from("New Post Title");
let body = String::from("# New Post\nNew post body");
let Post { id, .. } = create_draft_mutation(&db_pool, &title, &body)
.await
.unwrap();

// act
let outcome = delete_draft_mutation(&db_pool, id).await.unwrap();

// assert
assert_eq!(
outcome,
DeleteDraftResponse::DeleteDraftSuccessResponse(DeleteDraftSuccessResponse {
post: Post {
id,
title,
body,
published: false
},
})
);
}
}
18 changes: 18 additions & 0 deletions tests/api/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use metrics_exporter_prometheus::PrometheusHandle;
use once_cell::sync::Lazy;
use sqlx::{sqlite::SqlitePoolOptions, SqlitePool};

use axum_graphql::{
database::run_migrations,
observability::{
metrics::create_prometheus_recorder, tracing::create_tracing_subscriber_from_env,
},
Expand Down Expand Up @@ -47,4 +49,20 @@ impl TestApp {
metrics_server_port,
}
}

/// Generates fresh in-memory `SQLite` database and runs migrations. Can be called from
/// each test.
pub async fn get_db_pool() -> SqlitePool {
let database_url = "sqlite://:memory:";

let db_pool = SqlitePoolOptions::new()
.max_connections(1)
.connect(database_url)
.await
.unwrap();

run_migrations(&db_pool).await;

db_pool
}
}
1 change: 1 addition & 0 deletions tests/api/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![warn(clippy::all, clippy::pedantic)]

mod helpers;
mod model;
mod startup;
1 change: 1 addition & 0 deletions tests/api/model/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod post;
Loading

0 comments on commit d452796

Please sign in to comment.