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::XChaCha20Poly1305;
|
||||||
use chacha20poly1305::XNonce;
|
use chacha20poly1305::XNonce;
|
||||||
use rand::{CryptoRng, Rng};
|
use rand::{CryptoRng, Rng};
|
||||||
use secrecy::{ExposeSecret, Secret, SecretVec, Zeroize};
|
use secrecy::{DebugSecret, ExposeSecret, Secret, SecretVec, Zeroize};
|
||||||
use typenum::Unsigned;
|
use typenum::Unsigned;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
@ -43,7 +43,7 @@ pub enum Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This struct intentionally prevents implement Clone or Copy
|
// This struct intentionally prevents implement Clone or Copy
|
||||||
#[derive(Default)]
|
#[derive(Default, PartialEq, Eq)]
|
||||||
pub struct Key(chacha20poly1305::Key);
|
pub struct Key(chacha20poly1305::Key);
|
||||||
|
|
||||||
impl Key {
|
impl Key {
|
||||||
|
@ -55,6 +55,8 @@ impl Key {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DebugSecret for Key {}
|
||||||
|
|
||||||
impl AsRef<chacha20poly1305::Key> for Key {
|
impl AsRef<chacha20poly1305::Key> for Key {
|
||||||
fn as_ref(&self) -> &chacha20poly1305::Key {
|
fn as_ref(&self) -> &chacha20poly1305::Key {
|
||||||
&self.0
|
&self.0
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
|
|
||||||
|
use std::convert::Infallible;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
@ -48,12 +49,28 @@ pub struct ParsedUrl {
|
||||||
pub needs_password: bool,
|
pub needs_password: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, Debug)]
|
||||||
pub struct PartialParsedUrl {
|
pub struct PartialParsedUrl {
|
||||||
pub decryption_key: Option<Secret<Key>>,
|
pub decryption_key: Option<Secret<Key>>,
|
||||||
pub needs_password: bool,
|
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 {
|
impl From<&str> for PartialParsedUrl {
|
||||||
fn from(fragment: &str) -> Self {
|
fn from(fragment: &str) -> Self {
|
||||||
// Short circuit if the fragment only contains the key.
|
// 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)]
|
#[derive(Debug, Error)]
|
||||||
pub enum ParseUrlError {
|
pub enum ParseUrlError {
|
||||||
#[error("The provided url was bad")]
|
#[error("The provided url was bad")]
|
||||||
|
@ -274,3 +299,92 @@ impl Default for Expiration {
|
||||||
Self::UnixTime(Utc::now() + Duration::days(1))
|
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