Comment posting is idempotent + tests

This commit is contained in:
Alphonse Paix
2025-10-05 15:01:57 +02:00
parent 8f62c2513e
commit d96a29ee73
15 changed files with 289 additions and 95 deletions

163
tests/api/comments.rs Normal file
View File

@@ -0,0 +1,163 @@
use crate::helpers::{TestApp, fake_post_body};
use sqlx::PgPool;
#[sqlx::test]
async fn visitor_can_leave_a_comment(connection_pool: PgPool) {
let app = TestApp::spawn(connection_pool).await;
app.admin_login().await;
app.post_create_post(&fake_post_body()).await;
let post_id = {
let record = sqlx::query!("SELECT post_id FROM posts")
.fetch_one(&app.connection_pool)
.await
.unwrap();
record.post_id
};
let comment_author = "Author";
let comment_content = "Content";
let comment_body = serde_json::json!({
"author": comment_author,
"content": comment_content,
"idempotency_key": "key",
});
app.post_comment(&post_id, &comment_body).await;
let post = app.get_post_html(post_id).await;
assert!(post.contains(comment_author));
assert!(post.contains(comment_content));
}
#[sqlx::test]
async fn visitor_can_comment_anonymously(connection_pool: PgPool) {
let app = TestApp::spawn(connection_pool).await;
app.admin_login().await;
app.post_create_post(&fake_post_body()).await;
let post_id = {
let record = sqlx::query!("SELECT post_id FROM posts")
.fetch_one(&app.connection_pool)
.await
.unwrap();
record.post_id
};
let comment_content = "Content";
let comment_body = serde_json::json!({
"content": comment_content,
"idempotency_key": "key",
});
app.post_comment(&post_id, &comment_body).await;
let post = app.get_post_html(post_id).await;
assert!(post.contains("Anonymous"));
assert!(post.contains(comment_content));
}
#[sqlx::test]
async fn comment_with_invalid_body_is_rejected(connection_pool: PgPool) {
let app = TestApp::spawn(connection_pool).await;
app.admin_login().await;
app.post_create_post(&fake_post_body()).await;
let post_id = {
let record = sqlx::query!("SELECT post_id FROM posts")
.fetch_one(&app.connection_pool)
.await
.unwrap();
record.post_id
};
let test_cases = [
(
serde_json::json!({ "idempotency_key": "key" }),
"a missing content",
),
(
serde_json::json!({ "idempotency_key": "key", "content": "" }),
"an empty content",
),
];
for (invalid_body, message) in test_cases {
let response = app.post_comment(&post_id, &invalid_body).await;
let html = response.text().await.unwrap();
dbg!(&html);
assert!(
!html.contains("Your comment has been posted"),
"The API did not reject the request when the body had {}",
message
);
}
}
#[sqlx::test]
async fn comment_is_deleted_when_post_is_deleted(connection_pool: PgPool) {
let app = TestApp::spawn(connection_pool).await;
app.admin_login().await;
app.post_create_post(&fake_post_body()).await;
let post_id = {
let record = sqlx::query!("SELECT post_id FROM posts")
.fetch_one(&app.connection_pool)
.await
.unwrap();
record.post_id
};
let comment_author = "Author";
let comment_content = "Content";
let comment_body = serde_json::json!({
"author": comment_author,
"content": comment_content,
"idempotency_key": "key",
});
app.post_comment(&post_id, &comment_body).await;
let comment_id = {
let record = sqlx::query!("SELECT comment_id FROM comments")
.fetch_one(&app.connection_pool)
.await
.unwrap();
record.comment_id
};
app.delete_post(post_id).await;
let record = sqlx::query!("SELECT * FROM comments WHERE comment_id = $1", comment_id)
.fetch_optional(&app.connection_pool)
.await
.unwrap();
assert!(record.is_none());
}
#[sqlx::test]
async fn comment_posting_is_idempotent(connection_pool: PgPool) {
let app = TestApp::spawn(connection_pool).await;
app.admin_login().await;
app.post_create_post(&fake_post_body()).await;
let post_id = {
let record = sqlx::query!("SELECT post_id FROM posts")
.fetch_one(&app.connection_pool)
.await
.unwrap();
record.post_id
};
let comment_body = serde_json::json!({
"author": "author",
"content": "content",
"idempotency_key": "key",
});
let response = app.post_comment(&post_id, &comment_body).await;
assert!(
response
.text()
.await
.unwrap()
.contains("Your comment has been posted")
);
let response = app.post_comment(&post_id, &comment_body).await;
assert!(
response
.text()
.await
.unwrap()
.contains("Your comment has been posted")
);
let count = sqlx::query_scalar!("SELECT count(*) FROM comments")
.fetch_one(&app.connection_pool)
.await
.unwrap()
.unwrap_or(0);
assert_eq!(count, 1);
}

View File

@@ -359,6 +359,18 @@ impl TestApp {
.expect("Failed to execute request")
}
pub async fn post_comment<Body>(&self, post_id: &Uuid, body: &Body) -> reqwest::Response
where
Body: serde::Serialize,
{
self.api_client
.post(format!("{}/posts/{post_id}/comments", self.address))
.form(body)
.send()
.await
.expect("Failed to execute request")
}
pub async fn delete_post(&self, post_id: Uuid) {
self.api_client
.delete(format!("{}/admin/posts/{}", self.address, post_id))

View File

@@ -1,5 +1,6 @@
mod admin_dashboard;
mod change_password;
mod comments;
mod health_check;
mod helpers;
mod login;