- cardclaws-wallet::google — inline GenericObject + RS256 `savetowallet` JWT
(Rs256Signer via jsonwebtoken; FakeGoogleSigner test double)
- POST /v1/cards/{id}/wallet/google → { saveUrl }
- POST /v1/profile/{handle}/contact — validate, rate-limit, record
contact_form_submission, email the owner (HTML-escaped, Resend)
- WalletConfig gains google issuer id + service account email
84 backend tests; fmt + clippy clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
101 lines
2.8 KiB
Rust
101 lines
2.8 KiB
Rust
//! Integration tests for the public profile contact form (PRD §11.4, §13.6).
|
|
|
|
mod common;
|
|
|
|
use axum::http::StatusCode;
|
|
use serde_json::{json, Value};
|
|
|
|
use common::{unique_handle, TestApp};
|
|
|
|
async fn published_card(app: &TestApp, token: &str) -> String {
|
|
let handle = unique_handle();
|
|
let def = json!({
|
|
"face": { "layers": [], "background": { "type": "solid", "value": "#101014" } },
|
|
"back": { "layers": [] }
|
|
});
|
|
let (_, created) = app
|
|
.request(
|
|
"POST",
|
|
"/v1/cards",
|
|
Some(token),
|
|
Some(json!({"handle": handle, "definition": def})),
|
|
)
|
|
.await;
|
|
let id = created["id"].as_str().unwrap().to_string();
|
|
app.request(
|
|
"POST",
|
|
&format!("/v1/cards/{id}/publish"),
|
|
Some(token),
|
|
None,
|
|
)
|
|
.await;
|
|
handle
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn contact_form_emails_the_owner() {
|
|
let app = require_app!();
|
|
let token = app.register_and_token().await;
|
|
let handle = published_card(&app, &token).await;
|
|
|
|
let (status, _) = app
|
|
.request(
|
|
"POST",
|
|
&format!("/v1/profile/{handle}/contact"),
|
|
None,
|
|
Some(json!({ "name": "Visitor", "email": "[email protected]", "message": "Loved your card!" })),
|
|
)
|
|
.await;
|
|
assert_eq!(status, StatusCode::OK);
|
|
|
|
let sent = app.email.sent.lock().unwrap();
|
|
let msg = sent.last().expect("an email was sent");
|
|
assert!(msg.subject.contains("contact form"));
|
|
assert!(msg.html.contains("Loved your card!"));
|
|
assert!(msg.html.contains("[email protected]"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn contact_form_rejects_bad_input() {
|
|
let app = require_app!();
|
|
let token = app.register_and_token().await;
|
|
let handle = published_card(&app, &token).await;
|
|
|
|
let bad_email: Value = json!({ "name": "V", "email": "not-an-email", "message": "hi" });
|
|
let (s1, b1) = app
|
|
.request(
|
|
"POST",
|
|
&format!("/v1/profile/{handle}/contact"),
|
|
None,
|
|
Some(bad_email),
|
|
)
|
|
.await;
|
|
assert_eq!(s1, StatusCode::BAD_REQUEST);
|
|
assert_eq!(b1["code"], "validation");
|
|
|
|
let empty_name = json!({ "name": " ", "email": "[email protected]", "message": "hi" });
|
|
let (s2, _) = app
|
|
.request(
|
|
"POST",
|
|
&format!("/v1/profile/{handle}/contact"),
|
|
None,
|
|
Some(empty_name),
|
|
)
|
|
.await;
|
|
assert_eq!(s2, StatusCode::BAD_REQUEST);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn contact_form_unknown_handle_is_404() {
|
|
let app = require_app!();
|
|
let (status, _) = app
|
|
.request(
|
|
"POST",
|
|
"/v1/profile/nobody-here/contact",
|
|
None,
|
|
Some(json!({ "name": "V", "email": "[email protected]", "message": "hi" })),
|
|
)
|
|
.await;
|
|
assert_eq!(status, StatusCode::NOT_FOUND);
|
|
}
|