use crate::domain::{ClientResponse, Remoteip, Sitekey};
use crate::Error;
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct Captcha {
pub(crate) response: ClientResponse,
pub(crate) remoteip: Option<Remoteip>,
pub(crate) sitekey: Option<Sitekey>,
}
impl Captcha {
#[allow(dead_code)]
#[cfg_attr(
feature = "trace",
tracing::instrument(name = "Create new Captcha from a response string.", level = "debug")
)]
pub fn new(response: &str) -> Result<Self, Error> {
Ok(Captcha {
response: ClientResponse::parse(response.to_owned())?,
remoteip: None,
sitekey: None,
})
}
#[allow(dead_code)]
#[cfg_attr(
feature = "trace",
tracing::instrument(name = "Update remoteip field in Captcha.", level = "debug")
)]
pub fn set_remoteip(&mut self, remoteip: &str) -> Result<Self, Error> {
if remoteip.is_empty() {
self.remoteip = None;
} else {
self.remoteip = Some(Remoteip::parse(remoteip.to_owned())?);
}
Ok(self.clone())
}
#[allow(dead_code)]
#[cfg_attr(
feature = "trace",
tracing::instrument(name = "Update sitekey field in Captcha.", level = "debug")
)]
pub fn set_sitekey(&mut self, sitekey: &str) -> Result<Self, Error> {
if sitekey.is_empty() {
self.sitekey = None;
} else {
self.sitekey = Some(Sitekey::parse(sitekey.to_owned())?);
}
Ok(self.clone())
}
#[allow(dead_code)]
#[cfg_attr(
feature = "trace",
tracing::instrument(name = "Get response field.", level = "debug")
)]
pub fn response(self) -> ClientResponse {
self.response
}
#[allow(dead_code)]
#[cfg_attr(
feature = "trace",
tracing::instrument(name = "Get remoteip field.", level = "debug")
)]
pub fn remoteip(&self) -> Option<Remoteip> {
self.remoteip.clone()
}
#[allow(dead_code)]
#[cfg_attr(
feature = "trace",
tracing::instrument(name = "Get sitekey field.", level = "debug")
)]
pub fn sitekey(&self) -> Option<Sitekey> {
self.sitekey.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Code;
use claims::{assert_err, assert_none, assert_ok, assert_some};
use rand::distr::Alphanumeric;
use rand::{rng, RngExt};
fn random_response() -> String {
let mut rng = rng();
(&mut rng)
.sample_iter(Alphanumeric)
.take(100)
.map(char::from)
.collect()
}
fn dummy_captcha() -> Captcha {
Captcha::new(&random_response())
.unwrap()
.set_remoteip(&mockd::internet::ipv4_address())
.unwrap()
.set_sitekey(&mockd::unique::uuid_v4())
.unwrap()
}
#[test]
fn response_cannot_be_empty_or_blank() {
let empty = "";
assert_err!(Captcha::new(empty));
let blank = " ";
assert_err!(Captcha::new(blank));
}
#[test]
fn fail_if_remoteip_not_valid_v4_or_v6_address() {
let captcha = Captcha::new("response_string")
.unwrap()
.set_remoteip(&mockd::words::word());
assert_err!(&captcha);
if let Err(Error::Codes(hs)) = captcha {
assert!(hs.contains(&Code::InvalidUserIp));
}
}
#[test]
fn remoteip_is_optional() {
let captcha = Captcha::new("response_string")
.unwrap()
.set_remoteip(&mockd::internet::ipv4_address())
.unwrap();
assert_some!(captcha.remoteip);
}
#[test]
fn valid_user_id_is_accepted() {
assert_ok!(Captcha::new("response_string")
.unwrap()
.set_remoteip(&mockd::internet::ipv4_address()));
}
#[test]
fn fail_if_sitekey_not_valid_uuid() {
let captcha = Captcha::new("response_string")
.unwrap()
.set_sitekey(&mockd::words::word());
assert_err!(&captcha);
if let Err(Error::Codes(hs)) = captcha {
assert!(hs.contains(&Code::InvalidSiteKey));
}
}
#[test]
fn sitekey_is_optional() {
let captcha = Captcha::new("response_string")
.unwrap()
.set_sitekey(&mockd::unique::uuid_v4())
.unwrap();
assert_some!(captcha.sitekey);
}
#[test]
fn valid_sitekey_is_accepted() {
let captcha = Captcha::new("response_string")
.unwrap()
.set_sitekey(&mockd::unique::uuid_v4())
.unwrap();
assert_some!(captcha.sitekey());
}
#[test]
fn update_sitekey_with_empty_string_yields_none() {
let mut captcha = dummy_captcha();
assert_some!(captcha.sitekey());
captcha.set_sitekey("").unwrap();
assert_none!(captcha.sitekey());
}
#[test]
fn update_remoteip_with_empty_string_yields_none() {
let mut captcha = dummy_captcha();
assert_some!(captcha.remoteip());
captcha.set_remoteip("").unwrap();
assert_none!(captcha.remoteip());
}
fn get_captcha() -> (String, Captcha) {
let remoteip = mockd::internet::ipv4_address();
let response = random_response();
let captcha = Captcha::new(&response)
.unwrap()
.set_remoteip(&remoteip)
.unwrap()
.set_sitekey(&mockd::unique::uuid_v4())
.unwrap();
(response, captcha)
}
#[test]
fn test_hcaptcha_captcha_default_initialization() {
let (response, captcha) = get_captcha();
assert_eq!(response, captcha.response().to_string());
}
}