use axum::http::StatusCode;
use crate::common::TestContext;
#[tokio::test]
async fn zone_create_read_update_delete_round_trip() {
let ctx = TestContext::new().await;
let create_zone_request = serde_json::json!({
"name": "test.com",
"primary_ns": "ns1.test.com",
"admin_email": "admin@test.com",
"ttl": 3600,
"refresh": 7200,
"retry": 3600,
"expire": 604800,
"minimum_ttl": 86400
});
let (status, body) = ctx
.make_request("POST", "/zones", Some(create_zone_request))
.await;
assert_eq!(status, StatusCode::CREATED);
let zone_name = body["zone"]["name"].as_str().unwrap();
assert_eq!(zone_name, "test.com");
let (status, body) = ctx
.make_request("GET", &format!("/zones/{}", zone_name), None)
.await;
assert_eq!(status, StatusCode::OK);
assert_eq!(body["zone"]["name"], "test.com");
let (status, body) = ctx.make_request("GET", "/zones", None).await;
assert_eq!(status, StatusCode::OK);
assert_eq!(body["items"].as_array().unwrap().len(), 1);
let update_zone_request = serde_json::json!({
"name": "updated-test.com",
"primary_ns": "ns2.external-dns.net",
"admin_email": "admin@updated-test.com",
"ttl": 7200,
"refresh": 14400,
"retry": 7200,
"expire": 1209600,
"minimum_ttl": 172800
});
let (status, body) = ctx
.make_request(
"PUT",
&format!("/zones/{}", zone_name),
Some(update_zone_request),
)
.await;
assert_eq!(status, StatusCode::OK);
let updated_zone_name = body["zone"]["name"].as_str().unwrap();
assert_eq!(updated_zone_name, "updated-test.com");
let (status, _) = ctx
.make_request("DELETE", &format!("/zones/{}", updated_zone_name), None)
.await;
assert_eq!(status, StatusCode::OK);
let (status, _) = ctx
.make_request("GET", &format!("/zones/{}", updated_zone_name), None)
.await;
assert_eq!(status, StatusCode::NOT_FOUND);
}
#[tokio::test]
async fn zone_list_filters_support_ranges_search_and_pagination() {
let ctx = TestContext::new().await;
ctx.create_test_zone().await;
let create_zone_request = serde_json::json!({
"name": "filtered.net",
"primary_ns": "ns1.filtered.net",
"admin_email": "admin@filtered.net",
"ttl": 7200,
"refresh": 7200,
"retry": 3600,
"expire": 604800,
"minimum_ttl": 86400
});
let (status, _) = ctx
.make_request("POST", "/zones", Some(create_zone_request))
.await;
assert_eq!(status, StatusCode::CREATED);
let (status, body) = ctx
.make_request(
"GET",
"/zones?search=filtered&min_ttl=7000&max_ttl=8000",
None,
)
.await;
assert_eq!(status, StatusCode::OK);
let zones = body["items"].as_array().unwrap();
assert_eq!(zones.len(), 1);
assert_eq!(zones[0]["name"], "filtered.net");
let (status, body) = ctx
.make_request("GET", "/zones?limit=1&offset=1", None)
.await;
assert_eq!(status, StatusCode::OK);
let zones = body["items"].as_array().unwrap();
assert_eq!(zones.len(), 1);
assert_eq!(zones[0]["name"], "filtered.net");
assert_eq!(body["pagination"]["total"], 2);
assert_eq!(body["pagination"]["limit"], 1);
assert_eq!(body["pagination"]["offset"], 1);
let (status, _) = ctx.make_request("GET", "/zones?limit=-1", None).await;
assert_eq!(status, StatusCode::BAD_REQUEST);
}
#[tokio::test]
async fn zone_create_rejects_invalid_admin_email_forms() {
let ctx = TestContext::new().await;
let invalid_admin_email = serde_json::json!({
"name": "invalid-admin-email.com",
"primary_ns": "ns1.invalid-admin-email.com",
"admin_email": "admin@@example.com",
"ttl": 3600
});
let (status, _) = ctx
.make_request("POST", "/zones", Some(invalid_admin_email))
.await;
assert_eq!(status, StatusCode::BAD_REQUEST);
let soa_mailbox_admin_email = serde_json::json!({
"name": "soa-mailbox.com",
"primary_ns": "ns1.soa-mailbox.com",
"admin_email": "hostmaster.soa-mailbox.com.",
"ttl": 3600
});
let (status, _) = ctx
.make_request("POST", "/zones", Some(soa_mailbox_admin_email))
.await;
assert_eq!(status, StatusCode::BAD_REQUEST);
}
#[tokio::test]
async fn zone_create_normalizes_names_and_rejects_duplicates() {
let ctx = TestContext::new().await;
let create_zone_request = serde_json::json!({
"name": " Test.Example.Com. ",
"primary_ns": "NS1.Test.Example.Com.",
"admin_email": "Host.Master@Example.Com.",
"ttl": 3600
});
let (status, body) = ctx
.make_request("POST", "/zones", Some(create_zone_request))
.await;
assert_eq!(status, StatusCode::CREATED);
assert_eq!(body["zone"]["name"], "test.example.com");
assert_eq!(body["zone"]["primary_ns"], "ns1.test.example.com");
assert_eq!(body["zone"]["admin_email"], "Host.Master@example.com");
let duplicate_zone_request = serde_json::json!({
"name": "test.example.com.",
"primary_ns": "ns2.test.example.com",
"admin_email": "hostmaster@example.com",
"ttl": 3600
});
let (status, _) = ctx
.make_request("POST", "/zones", Some(duplicate_zone_request))
.await;
assert_eq!(status, StatusCode::BAD_REQUEST);
let child_zone_request = serde_json::json!({
"name": "child.test.example.com",
"primary_ns": "ns1.child.test.example.com",
"admin_email": "hostmaster@example.com",
"ttl": 3600
});
let (status, _) = ctx
.make_request("POST", "/zones", Some(child_zone_request))
.await;
assert_eq!(status, StatusCode::CREATED);
let apex_primary_ns_request = serde_json::json!({
"name": "apex-ns.example.com",
"primary_ns": "apex-ns.example.com",
"admin_email": "hostmaster@example.com",
"ttl": 604800
});
let (status, _) = ctx
.make_request("POST", "/zones", Some(apex_primary_ns_request))
.await;
assert_eq!(status, StatusCode::CREATED);
}
#[tokio::test]
async fn zone_update_normalizes_values_and_rejects_invalid_or_duplicate_names() {
let ctx = TestContext::new().await;
let first_zone = serde_json::json!({
"name": "first.example.com",
"primary_ns": "ns1.first.example.com",
"admin_email": "hostmaster@example.com",
"ttl": 3600
});
let (status, _) = ctx.make_request("POST", "/zones", Some(first_zone)).await;
assert_eq!(status, StatusCode::CREATED);
let second_zone = serde_json::json!({
"name": "second.example.com",
"primary_ns": "ns1.second.example.com",
"admin_email": "hostmaster@example.com",
"ttl": 3600
});
let (status, _) = ctx.make_request("POST", "/zones", Some(second_zone)).await;
assert_eq!(status, StatusCode::CREATED);
let normalize_update = serde_json::json!({
"name": " First.Example.Com. ",
"primary_ns": "NS1.First.Example.Com.",
"admin_email": "Host.Master@Example.Com.",
"ttl": 7200
});
let (status, body) = ctx
.make_request("PUT", "/zones/first.example.com", Some(normalize_update))
.await;
assert_eq!(status, StatusCode::OK);
assert_eq!(body["zone"]["name"], "first.example.com");
assert_eq!(body["zone"]["primary_ns"], "ns1.first.example.com");
assert_eq!(body["zone"]["admin_email"], "Host.Master@example.com");
let duplicate_update = serde_json::json!({
"name": "Second.Example.Com.",
"primary_ns": "ns1.first.example.com",
"admin_email": "hostmaster@example.com",
"ttl": 3600
});
let (status, _) = ctx
.make_request("PUT", "/zones/first.example.com", Some(duplicate_update))
.await;
assert_eq!(status, StatusCode::BAD_REQUEST);
let invalid_name_update = serde_json::json!({
"name": "test..example.com",
"primary_ns": "ns1.test.example.com",
"admin_email": "hostmaster@example.com",
"ttl": 3600
});
let (status, _) = ctx
.make_request("PUT", "/zones/first.example.com", Some(invalid_name_update))
.await;
assert_eq!(status, StatusCode::BAD_REQUEST);
let invalid_ttl_update = serde_json::json!({
"name": "first.example.com",
"primary_ns": "ns1.first.example.com",
"admin_email": "hostmaster@example.com",
"ttl": 0
});
let (status, _) = ctx
.make_request("PUT", "/zones/first.example.com", Some(invalid_ttl_update))
.await;
assert_eq!(status, StatusCode::BAD_REQUEST);
}
#[tokio::test]
async fn zone_create_rejects_invalid_names_and_ttl_bounds() {
let ctx = TestContext::new().await;
let invalid_zone_name = serde_json::json!({
"name": "*.example.com",
"primary_ns": "ns1.example.com",
"admin_email": "hostmaster@example.com",
"ttl": 3600
});
let (status, _) = ctx
.make_request("POST", "/zones", Some(invalid_zone_name))
.await;
assert_eq!(status, StatusCode::BAD_REQUEST);
let root_zone = serde_json::json!({
"name": ".",
"primary_ns": "ns.example.com",
"admin_email": "hostmaster@example.com",
"ttl": 3600
});
let (status, _) = ctx.make_request("POST", "/zones", Some(root_zone)).await;
assert_eq!(status, StatusCode::BAD_REQUEST);
let underscore_zone = serde_json::json!({
"name": "_tcp.example.com",
"primary_ns": "ns._tcp.example.com",
"admin_email": "hostmaster@example.com",
"ttl": 3600
});
let (status, _) = ctx
.make_request("POST", "/zones", Some(underscore_zone))
.await;
assert_eq!(status, StatusCode::BAD_REQUEST);
let empty_label_zone = serde_json::json!({
"name": "test..example.com",
"primary_ns": "ns.test.example.com",
"admin_email": "hostmaster@example.com",
"ttl": 3600
});
let (status, _) = ctx
.make_request("POST", "/zones", Some(empty_label_zone))
.await;
assert_eq!(status, StatusCode::BAD_REQUEST);
let hyphen_edge_zone = serde_json::json!({
"name": "-test.example.com",
"primary_ns": "ns.-test.example.com",
"admin_email": "hostmaster@example.com",
"ttl": 3600
});
let (status, _) = ctx
.make_request("POST", "/zones", Some(hyphen_edge_zone))
.await;
assert_eq!(status, StatusCode::BAD_REQUEST);
let out_of_bailiwick_ns = serde_json::json!({
"name": "bailiwick.example.com",
"primary_ns": "ns.example.com",
"admin_email": "hostmaster@example.com",
"ttl": 3600
});
let (status, _) = ctx
.make_request("POST", "/zones", Some(out_of_bailiwick_ns))
.await;
assert_eq!(status, StatusCode::CREATED);
let suffix_boundary_ns = serde_json::json!({
"name": "test.example.com",
"primary_ns": "badtest.example.com",
"admin_email": "hostmaster@example.com",
"ttl": 3600
});
let (status, _) = ctx
.make_request("POST", "/zones", Some(suffix_boundary_ns))
.await;
assert_eq!(status, StatusCode::CREATED);
let low_ttl = serde_json::json!({
"name": "low-ttl.example.com",
"primary_ns": "ns.low-ttl.example.com",
"admin_email": "hostmaster@example.com",
"ttl": 0
});
let (status, _) = ctx.make_request("POST", "/zones", Some(low_ttl)).await;
assert_eq!(status, StatusCode::BAD_REQUEST);
let high_ttl = serde_json::json!({
"name": "high-ttl.example.com",
"primary_ns": "ns.high-ttl.example.com",
"admin_email": "hostmaster@example.com",
"ttl": 604801
});
let (status, _) = ctx.make_request("POST", "/zones", Some(high_ttl)).await;
assert_eq!(status, StatusCode::BAD_REQUEST);
}