Add unit tests for fragment parsing
This commit is contained in:
parent
73b7b50ed4
commit
e720007cbe
2 changed files with 119 additions and 3 deletions
|
@ -27,7 +27,7 @@ use chacha20poly1305::aead::{AeadInPlace, NewAead};
|
|||
use chacha20poly1305::XChaCha20Poly1305;
|
||||
use chacha20poly1305::XNonce;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use secrecy::{ExposeSecret, Secret, SecretVec, Zeroize};
|
||||
use secrecy::{DebugSecret, ExposeSecret, Secret, SecretVec, Zeroize};
|
||||
use typenum::Unsigned;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
@ -43,7 +43,7 @@ pub enum Error {
|
|||
}
|
||||
|
||||
// This struct intentionally prevents implement Clone or Copy
|
||||
#[derive(Default)]
|
||||
#[derive(Default, PartialEq, Eq)]
|
||||
pub struct Key(chacha20poly1305::Key);
|
||||
|
||||
impl Key {
|
||||
|
@ -55,6 +55,8 @@ impl Key {
|
|||
}
|
||||
}
|
||||
|
||||
impl DebugSecret for Key {}
|
||||
|
||||
impl AsRef<chacha20poly1305::Key> for Key {
|
||||
fn as_ref(&self) -> &chacha20poly1305::Key {
|
||||
&self.0
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
use std::convert::Infallible;
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
|
||||
|
@ -48,12 +49,28 @@ pub struct ParsedUrl {
|
|||
pub needs_password: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Debug)]
|
||||
pub struct PartialParsedUrl {
|
||||
pub decryption_key: Option<Secret<Key>>,
|
||||
pub needs_password: bool,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl PartialEq for PartialParsedUrl {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
use secrecy::ExposeSecret;
|
||||
let decryption_key_matches = {
|
||||
match (self.decryption_key.as_ref(), other.decryption_key.as_ref()) {
|
||||
(Some(key), Some(other)) => key.expose_secret() == other.expose_secret(),
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
};
|
||||
|
||||
decryption_key_matches && self.needs_password == other.needs_password
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for PartialParsedUrl {
|
||||
fn from(fragment: &str) -> Self {
|
||||
// Short circuit if the fragment only contains the key.
|
||||
|
@ -100,6 +117,14 @@ impl From<&str> for PartialParsedUrl {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromStr for PartialParsedUrl {
|
||||
type Err = Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ParseUrlError {
|
||||
#[error("The provided url was bad")]
|
||||
|
@ -274,3 +299,92 @@ impl Default for Expiration {
|
|||
Self::UnixTime(Utc::now() + Duration::days(1))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod partial_parsed_url_parsing {
|
||||
use secrecy::Secret;
|
||||
|
||||
use crate::base64;
|
||||
use crate::crypto::Key;
|
||||
use crate::PartialParsedUrl;
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
assert_eq!("".parse(), Ok(PartialParsedUrl::default()));
|
||||
}
|
||||
|
||||
const DECRYPTION_KEY_STRING: &str = "ddLod7sGy_EjFDjWqZoH4i5n_XU8bIpEuEo3-pjfAIE=";
|
||||
|
||||
fn decryption_key() -> Option<Secret<Key>> {
|
||||
Key::new_secret(base64::decode(DECRYPTION_KEY_STRING).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clean_no_password() {
|
||||
assert_eq!(
|
||||
DECRYPTION_KEY_STRING.parse(),
|
||||
Ok(PartialParsedUrl {
|
||||
decryption_key: decryption_key(),
|
||||
needs_password: false
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_password() {
|
||||
let input = "key:ddLod7sGy_EjFDjWqZoH4i5n_XU8bIpEuEo3-pjfAIE=";
|
||||
assert_eq!(
|
||||
input.parse(),
|
||||
Ok(PartialParsedUrl {
|
||||
decryption_key: decryption_key(),
|
||||
needs_password: false
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_password() {
|
||||
let input = "key:ddLod7sGy_EjFDjWqZoH4i5n_XU8bIpEuEo3-pjfAIE=!pw";
|
||||
assert_eq!(
|
||||
input.parse(),
|
||||
Ok(PartialParsedUrl {
|
||||
decryption_key: decryption_key(),
|
||||
needs_password: true
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn order_does_not_matter() {
|
||||
let input = "pw!key:ddLod7sGy_EjFDjWqZoH4i5n_XU8bIpEuEo3-pjfAIE=";
|
||||
assert_eq!(
|
||||
input.parse(),
|
||||
Ok(PartialParsedUrl {
|
||||
decryption_key: decryption_key(),
|
||||
needs_password: true
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_key_pair_gracefully_fails() {
|
||||
let input = "!!!key:ddLod7sGy_EjFDjWqZoH4i5n_XU8bIpEuEo3-pjfAIE=!!!";
|
||||
assert_eq!(
|
||||
input.parse(),
|
||||
Ok(PartialParsedUrl {
|
||||
decryption_key: decryption_key(),
|
||||
needs_password: false
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_decryption_key_gracefully_fails() {
|
||||
assert_eq!("invalid key".parse(), Ok(PartialParsedUrl::default()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_fields_are_ignored() {
|
||||
assert_eq!("!!a!!b!!c".parse(), Ok(PartialParsedUrl::default()));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue