Add unit tests for fragment parsing

master
Edward Shen 2022-01-11 23:24:47 -08:00
parent 73b7b50ed4
commit e720007cbe
Signed by: edward
GPG Key ID: 19182661E818369F
2 changed files with 119 additions and 3 deletions

View File

@ -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

View File

@ -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()));
}
}