#![cfg(feature = "http")]
use axum::body::Body;
use axum::http::{Request, StatusCode};
use sdforge::prelude::ServiceResponse;
use sdforge::{forge, http};
use serde::{Deserialize, Serialize};
use tower::ServiceExt;
#[derive(Debug, Serialize, Deserialize)]
struct CreateUserPayload {
name: String,
email: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct User {
id: u64,
name: String,
email: String,
}
#[forge(
name = "status_code_create_with_status",
version = "v1",
path = "/create-with-status",
method = "POST",
status = 201,
description = "Create user with explicit status=201"
)]
async fn create_with_status(payload: CreateUserPayload) -> User {
User {
id: 1,
name: payload.name,
email: payload.email,
}
}
#[forge(
name = "status_code_create_service_response",
version = "v1",
path = "/create-service-response",
method = "POST",
description = "Create user with ServiceResponse::success_with_status"
)]
async fn create_service_response(payload: CreateUserPayload) -> ServiceResponse<User> {
let user = User {
id: 2,
name: payload.name,
email: payload.email,
};
ServiceResponse::success_with_status(user, 201)
}
#[forge(
name = "status_code_create_default",
version = "v1",
path = "/create-default",
method = "POST",
description = "Create user with default 200"
)]
async fn create_default(payload: CreateUserPayload) -> User {
User {
id: 3,
name: payload.name,
email: payload.email,
}
}
fn build_router() -> axum::Router {
http::build()
}
async fn send_post(uri: &str, body: serde_json::Value) -> axum::http::Response<Body> {
let router = build_router();
let body_json = serde_json::to_string(&body).unwrap();
router
.oneshot(
Request::builder()
.method("POST")
.uri(uri)
.header("content-type", "application/json")
.body(Body::from(body_json))
.unwrap(),
)
.await
.unwrap()
}
fn sample_payload() -> serde_json::Value {
serde_json::json!({
"name": "Alice",
"email": "alice@example.com"
})
}
#[tokio::test]
async fn test_forge_status_arg_produces_201() {
let response = send_post("/api/v1/create-with-status", sample_payload()).await;
assert_eq!(
response.status(),
StatusCode::CREATED,
"expected 201 Created from #[forge(status = 201)]"
);
}
#[tokio::test]
async fn test_service_response_success_with_status_produces_201() {
let response = send_post("/api/v1/create-service-response", sample_payload()).await;
assert_eq!(
response.status(),
StatusCode::CREATED,
"expected 201 Created from ServiceResponse::success_with_status(_, 201)"
);
}
#[tokio::test]
async fn test_bare_type_without_status_defaults_200() {
let response = send_post("/api/v1/create-default", sample_payload()).await;
assert_eq!(
response.status(),
StatusCode::OK,
"expected 200 OK for bare type without status arg (zero-breaking)"
);
}
#[tokio::test]
async fn test_status_201_response_body_contains_user() {
let response = send_post("/api/v1/create-with-status", sample_payload()).await;
assert_eq!(response.status(), StatusCode::CREATED);
let body_bytes = axum::body::to_bytes(response.into_body(), usize::MAX)
.await
.unwrap();
let body_json: serde_json::Value = serde_json::from_slice(&body_bytes).unwrap();
assert_eq!(body_json["name"], "Alice");
assert_eq!(body_json["email"], "alice@example.com");
assert_eq!(body_json["id"], 1);
}
#[tokio::test]
async fn test_service_response_body_contains_status_code_field() {
let response = send_post("/api/v1/create-service-response", sample_payload()).await;
assert_eq!(response.status(), StatusCode::CREATED);
let body_bytes = axum::body::to_bytes(response.into_body(), usize::MAX)
.await
.unwrap();
let body_json: serde_json::Value = serde_json::from_slice(&body_bytes).unwrap();
assert_eq!(body_json["success"], true);
assert_eq!(body_json["status_code"], 201);
assert_eq!(body_json["data"]["name"], "Alice");
}