2021-12-22 12:03:29 +00:00
|
|
|
use std::fs::File;
|
|
|
|
use std::io::{self, Read, Write};
|
|
|
|
use std::str;
|
2022-02-01 10:31:08 +00:00
|
|
|
use std::time::Duration;
|
2021-12-22 12:03:29 +00:00
|
|
|
|
2022-02-04 21:38:45 +00:00
|
|
|
use async_trait::async_trait;
|
2023-01-17 15:48:07 +00:00
|
|
|
use epp_client::connect::connect_with_connector;
|
2021-12-22 12:03:29 +00:00
|
|
|
use regex::Regex;
|
2022-03-10 11:21:26 +00:00
|
|
|
use tokio::time::timeout;
|
2021-12-22 12:03:29 +00:00
|
|
|
use tokio_test::io::Builder;
|
2023-01-17 16:20:24 +00:00
|
|
|
use tracing::trace;
|
2021-12-22 12:03:29 +00:00
|
|
|
|
2022-03-10 11:21:26 +00:00
|
|
|
use epp_client::domain::{DomainCheck, DomainContact, DomainCreate, Period};
|
2021-12-22 12:03:29 +00:00
|
|
|
use epp_client::login::Login;
|
2022-01-23 22:24:27 +00:00
|
|
|
use epp_client::response::ResultCode;
|
2021-12-22 12:03:29 +00:00
|
|
|
|
|
|
|
const CLTRID: &str = "cltrid:1626454866";
|
|
|
|
|
|
|
|
struct TestWriter;
|
|
|
|
|
|
|
|
impl Write for TestWriter {
|
|
|
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
|
|
print!(
|
|
|
|
"{}",
|
|
|
|
str::from_utf8(buf).expect("tried to log invalid UTF-8")
|
|
|
|
);
|
|
|
|
Ok(buf.len())
|
|
|
|
}
|
|
|
|
fn flush(&mut self) -> io::Result<()> {
|
|
|
|
io::stdout().flush()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn log_to_stdout() -> tracing::subscriber::DefaultGuard {
|
|
|
|
let sub = tracing_subscriber::FmtSubscriber::builder()
|
|
|
|
.with_max_level(tracing::Level::TRACE)
|
|
|
|
.with_writer(|| TestWriter)
|
|
|
|
.finish();
|
|
|
|
tracing::subscriber::set_default(sub)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn len_bytes(bytes: &str) -> [u8; 4] {
|
|
|
|
((bytes.len() as u32) + 4).to_be_bytes()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn xml(path: &str) -> String {
|
|
|
|
let ws_regex = Regex::new(r"[\s]{2,}").unwrap();
|
|
|
|
let end_regex = Regex::new(r"\?>").unwrap();
|
|
|
|
|
|
|
|
let mut f = File::open(format!("tests/resources/{}", path)).unwrap();
|
|
|
|
let mut buf = String::new();
|
|
|
|
f.read_to_string(&mut buf).unwrap();
|
|
|
|
|
|
|
|
if !buf.is_empty() {
|
|
|
|
let mat = end_regex.find(buf.as_str()).unwrap();
|
|
|
|
let start = mat.end();
|
|
|
|
buf = format!(
|
|
|
|
"{}\r\n{}",
|
|
|
|
&buf[..start],
|
|
|
|
ws_regex.replace_all(&buf[start..], "")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
buf
|
|
|
|
}
|
|
|
|
|
|
|
|
fn build_stream(units: &[&str]) -> Builder {
|
|
|
|
let mut builder = Builder::new();
|
|
|
|
for (i, path) in units.iter().enumerate() {
|
|
|
|
let buf = xml(path);
|
|
|
|
match i % 2 {
|
|
|
|
0 => builder.read(&len_bytes(&buf)).read(buf.as_bytes()),
|
|
|
|
1 => builder.write(&len_bytes(&buf)).write(buf.as_bytes()),
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
builder
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn client() {
|
|
|
|
let _guard = log_to_stdout();
|
2022-02-04 21:38:45 +00:00
|
|
|
|
|
|
|
struct FakeConnector;
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
impl epp_client::client::Connector for FakeConnector {
|
|
|
|
type Connection = tokio_test::io::Mock;
|
|
|
|
|
|
|
|
async fn connect(&self, _: Duration) -> Result<Self::Connection, epp_client::Error> {
|
|
|
|
Ok(build_stream(&[
|
|
|
|
"response/greeting.xml",
|
|
|
|
"request/login.xml",
|
|
|
|
"response/login.xml",
|
|
|
|
"request/domain/check.xml",
|
|
|
|
"response/domain/check.xml",
|
|
|
|
])
|
|
|
|
.build())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-17 16:20:24 +00:00
|
|
|
let (mut client, mut connection) =
|
2023-01-17 14:48:18 +00:00
|
|
|
connect_with_connector(FakeConnector, "test".into(), Duration::from_secs(5), None)
|
2023-01-17 16:20:24 +00:00
|
|
|
.await
|
|
|
|
.unwrap();
|
2022-02-01 10:31:08 +00:00
|
|
|
|
2023-01-17 16:20:24 +00:00
|
|
|
tokio::spawn(async move {
|
|
|
|
connection.run().await.unwrap();
|
|
|
|
});
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
client.xml_greeting().await.unwrap(),
|
|
|
|
xml("response/greeting.xml")
|
|
|
|
);
|
2023-01-17 16:06:20 +00:00
|
|
|
let rsp = client
|
2021-12-22 12:03:29 +00:00
|
|
|
.transact(
|
|
|
|
&Login::new(
|
|
|
|
"username",
|
|
|
|
"password",
|
2022-09-06 21:51:48 +00:00
|
|
|
Some("new-password"),
|
2022-01-20 08:04:14 +00:00
|
|
|
Some(&["http://schema.ispapi.net/epp/xml/keyvalue-1.0"]),
|
2021-12-22 12:03:29 +00:00
|
|
|
),
|
|
|
|
CLTRID,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
2023-01-17 16:20:24 +00:00
|
|
|
assert_eq!(rsp.result.code, ResultCode::CommandCompletedSuccessfully);
|
2021-12-22 12:03:29 +00:00
|
|
|
|
2023-01-17 16:06:20 +00:00
|
|
|
assert_eq!(rsp.result.code, ResultCode::CommandCompletedSuccessfully);
|
|
|
|
|
2021-12-22 12:03:29 +00:00
|
|
|
let rsp = client
|
2022-01-27 11:58:05 +00:00
|
|
|
.transact(
|
|
|
|
&DomainCheck {
|
|
|
|
domains: &["eppdev.com", "eppdev.net"],
|
|
|
|
},
|
|
|
|
CLTRID,
|
|
|
|
)
|
2021-12-22 12:03:29 +00:00
|
|
|
.await
|
|
|
|
.unwrap();
|
2022-01-23 22:24:27 +00:00
|
|
|
assert_eq!(rsp.result.code, ResultCode::CommandCompletedSuccessfully);
|
2021-12-22 12:03:29 +00:00
|
|
|
|
|
|
|
let result = rsp.res_data().unwrap();
|
2022-01-27 11:35:50 +00:00
|
|
|
assert_eq!(result.list[0].id, "eppdev.com");
|
2021-12-22 12:03:29 +00:00
|
|
|
}
|
2022-03-10 11:21:26 +00:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn dropped() {
|
|
|
|
let _guard = log_to_stdout();
|
|
|
|
|
|
|
|
struct FakeConnector;
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
impl epp_client::client::Connector for FakeConnector {
|
|
|
|
type Connection = tokio_test::io::Mock;
|
|
|
|
|
|
|
|
async fn connect(&self, _: Duration) -> Result<Self::Connection, epp_client::Error> {
|
|
|
|
let mut builder = Builder::new();
|
|
|
|
|
|
|
|
let buf = xml("response/greeting.xml");
|
|
|
|
builder.read(&len_bytes(&buf)).read(buf.as_bytes());
|
|
|
|
|
|
|
|
let buf = xml("request/login.xml");
|
|
|
|
builder.write(&len_bytes(&buf)).write(buf.as_bytes());
|
|
|
|
|
|
|
|
let buf = xml("response/login.xml");
|
|
|
|
builder.read(&len_bytes(&buf)).read(buf.as_bytes());
|
|
|
|
|
|
|
|
let buf = xml("request/domain/check.xml");
|
|
|
|
builder.write(&len_bytes(&buf)).write(buf.as_bytes());
|
|
|
|
|
|
|
|
// We add a wait here. We're going to timeout below as a way of dropping the future.
|
|
|
|
builder.wait(Duration::from_millis(100));
|
|
|
|
|
|
|
|
let buf = xml("response/domain/check.xml");
|
|
|
|
builder.read(&len_bytes(&buf)).read(buf.as_bytes());
|
|
|
|
|
|
|
|
let buf = xml("request/domain/create.xml");
|
|
|
|
builder.write(&len_bytes(&buf)).write(buf.as_bytes());
|
|
|
|
|
|
|
|
let buf = xml("response/domain/create.xml");
|
|
|
|
builder.read(&len_bytes(&buf)).read(buf.as_bytes());
|
|
|
|
|
|
|
|
Ok(builder.build())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-17 16:20:24 +00:00
|
|
|
let (mut client, mut connection) =
|
2023-01-17 14:48:18 +00:00
|
|
|
connect_with_connector(FakeConnector, "test".into(), Duration::from_secs(5), None)
|
2023-01-17 16:20:24 +00:00
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
tokio::spawn(async move {
|
|
|
|
connection.run().await.unwrap();
|
|
|
|
trace!("connection future resolved successfully")
|
|
|
|
});
|
|
|
|
|
|
|
|
trace!("Trying to get greeting");
|
|
|
|
assert_eq!(
|
|
|
|
client.xml_greeting().await.unwrap(),
|
|
|
|
xml("response/greeting.xml")
|
|
|
|
);
|
2022-03-10 11:21:26 +00:00
|
|
|
let rsp = client
|
|
|
|
.transact(
|
|
|
|
&Login::new(
|
|
|
|
"username",
|
|
|
|
"password",
|
2022-09-06 21:51:48 +00:00
|
|
|
Some("new-password"),
|
2022-03-10 11:21:26 +00:00
|
|
|
Some(&["http://schema.ispapi.net/epp/xml/keyvalue-1.0"]),
|
|
|
|
),
|
|
|
|
CLTRID,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(rsp.result.code, ResultCode::CommandCompletedSuccessfully);
|
|
|
|
|
|
|
|
// Here, we add a 10ms timeout on the entire transaction. The mock stream
|
|
|
|
// specifies that the caller will have to wait for 100ms after sending
|
|
|
|
// the request before the response is returned. When `timeout()` returns
|
|
|
|
// `Err(Elapsed)`, the `RequestFuture` inside the `Timeout` future is dropped,
|
|
|
|
// leaving a half-finished request in the `EppConnection`.
|
|
|
|
timeout(
|
|
|
|
Duration::from_millis(10),
|
|
|
|
client.transact(
|
|
|
|
&DomainCheck {
|
|
|
|
domains: &["eppdev.com", "eppdev.net"],
|
|
|
|
},
|
|
|
|
CLTRID,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.unwrap_err();
|
|
|
|
|
|
|
|
let contacts = &[
|
|
|
|
DomainContact {
|
|
|
|
contact_type: "admin".into(),
|
|
|
|
id: "eppdev-contact-3".into(),
|
|
|
|
},
|
|
|
|
DomainContact {
|
|
|
|
contact_type: "tech".into(),
|
|
|
|
id: "eppdev-contact-3".into(),
|
|
|
|
},
|
|
|
|
DomainContact {
|
|
|
|
contact_type: "billing".into(),
|
|
|
|
id: "eppdev-contact-3".into(),
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
// Then, we start another request (of a different type). This should push through the
|
|
|
|
// remainder of the in-flight request before starting the new one, and succeed.
|
|
|
|
let create = DomainCreate::new(
|
|
|
|
"eppdev-1.com",
|
|
|
|
Period::years(1).unwrap(),
|
|
|
|
None,
|
|
|
|
Some("eppdev-contact-3"),
|
|
|
|
"epP4uthd#v",
|
|
|
|
Some(contacts),
|
|
|
|
);
|
|
|
|
|
|
|
|
let rsp = client.transact(&create, CLTRID).await.unwrap();
|
|
|
|
assert_eq!(rsp.result.code, ResultCode::CommandCompletedSuccessfully);
|
2023-01-17 16:20:24 +00:00
|
|
|
|
|
|
|
drop(client);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn drop_client() {
|
|
|
|
let _guard = log_to_stdout();
|
|
|
|
|
|
|
|
struct FakeConnector;
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
impl epp_client::client::Connector for FakeConnector {
|
|
|
|
type Connection = tokio_test::io::Mock;
|
|
|
|
|
|
|
|
async fn connect(&self, _: Duration) -> Result<Self::Connection, epp_client::Error> {
|
|
|
|
Ok(build_stream(&["response/greeting.xml"]).build())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let (client, mut connection) =
|
2023-01-17 14:48:18 +00:00
|
|
|
connect_with_connector(FakeConnector, "test".into(), Duration::from_secs(5), None)
|
2023-01-17 16:20:24 +00:00
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let handle = tokio::spawn(async move {
|
|
|
|
connection.run().await.unwrap();
|
|
|
|
trace!("connection future resolved successfully")
|
|
|
|
});
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
client.xml_greeting().await.unwrap(),
|
|
|
|
xml("response/greeting.xml")
|
|
|
|
);
|
|
|
|
|
|
|
|
drop(client);
|
|
|
|
assert!(handle.await.is_ok());
|
2022-03-10 11:21:26 +00:00
|
|
|
}
|
2023-01-17 14:48:18 +00:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn keepalive() {
|
|
|
|
let _guard = log_to_stdout();
|
|
|
|
|
|
|
|
struct FakeConnector;
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
impl epp_client::client::Connector for FakeConnector {
|
|
|
|
type Connection = tokio_test::io::Mock;
|
|
|
|
|
|
|
|
async fn connect(&self, _: Duration) -> Result<Self::Connection, epp_client::Error> {
|
|
|
|
Ok(build_stream(&[
|
|
|
|
"response/greeting.xml",
|
|
|
|
"request/login.xml",
|
|
|
|
"response/login.xml",
|
|
|
|
// The keepalive should kick in.
|
|
|
|
// We set the keepalive to 100ms and wait 250ms which should yield two hello requests
|
|
|
|
"request/hello.xml",
|
|
|
|
"response/greeting.xml",
|
|
|
|
"request/hello.xml",
|
|
|
|
"response/greeting.xml",
|
|
|
|
"request/domain/create.xml",
|
|
|
|
"response/domain/create.xml",
|
|
|
|
])
|
|
|
|
.build())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let (mut client, mut connection) = connect_with_connector(
|
|
|
|
FakeConnector,
|
|
|
|
"test".into(),
|
|
|
|
Duration::from_secs(5),
|
|
|
|
Some(Duration::from_millis(100)),
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
tokio::spawn(async move {
|
|
|
|
connection.run().await.unwrap();
|
|
|
|
trace!("connection future resolved successfully")
|
|
|
|
});
|
|
|
|
|
|
|
|
trace!("Trying to get greeting");
|
|
|
|
assert_eq!(
|
|
|
|
client.xml_greeting().await.unwrap(),
|
|
|
|
xml("response/greeting.xml")
|
|
|
|
);
|
|
|
|
|
|
|
|
let rsp = client
|
|
|
|
.transact(
|
|
|
|
&Login::new(
|
|
|
|
"username",
|
|
|
|
"password",
|
|
|
|
Some("new-password"),
|
|
|
|
Some(&["http://schema.ispapi.net/epp/xml/keyvalue-1.0"]),
|
|
|
|
),
|
|
|
|
CLTRID,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(rsp.result.code, ResultCode::CommandCompletedSuccessfully);
|
|
|
|
trace!("Waiting");
|
|
|
|
tokio::time::sleep(Duration::from_millis(250)).await;
|
|
|
|
|
|
|
|
let contacts = &[
|
|
|
|
DomainContact {
|
|
|
|
contact_type: "admin".into(),
|
|
|
|
id: "eppdev-contact-3".into(),
|
|
|
|
},
|
|
|
|
DomainContact {
|
|
|
|
contact_type: "tech".into(),
|
|
|
|
id: "eppdev-contact-3".into(),
|
|
|
|
},
|
|
|
|
DomainContact {
|
|
|
|
contact_type: "billing".into(),
|
|
|
|
id: "eppdev-contact-3".into(),
|
|
|
|
},
|
|
|
|
];
|
|
|
|
let create = DomainCreate::new(
|
|
|
|
"eppdev-1.com",
|
|
|
|
Period::years(1).unwrap(),
|
|
|
|
None,
|
|
|
|
Some("eppdev-contact-3"),
|
|
|
|
"epP4uthd#v",
|
|
|
|
Some(contacts),
|
|
|
|
);
|
|
|
|
trace!("Trying to create domains");
|
|
|
|
let rsp = client.transact(&create, CLTRID).await.unwrap();
|
|
|
|
assert_eq!(rsp.result.code, ResultCode::CommandCompletedSuccessfully);
|
|
|
|
}
|