//! Tests for `PasswordHash` encoding/decoding.
//!
//! Each test implements a different permutation of the possible combinations
//! of the string encoding, and ensures password hashes round trip under each
//! of the conditions.

use core::convert::{TryFrom, TryInto};
use password_hash::{Ident, ParamsString, PasswordHash, Salt};

const EXAMPLE_ALGORITHM: Ident = Ident::new("argon2d");
const EXAMPLE_SALT: &str = "saltsaltsaltsaltsalt";
const EXAMPLE_HASH: &[u8] = &[
    0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85,
    0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab,
];

/// Example parameters
fn example_params() -> ParamsString {
    let mut params = ParamsString::new();
    params.add_decimal("a", 1).unwrap();
    params.add_decimal("b", 2).unwrap();
    params.add_decimal("c", 3).unwrap();
    params
}

#[test]
fn algorithm_alone() {
    let ph = PasswordHash::new("$argon2d").unwrap();
    assert_eq!(ph.algorithm, EXAMPLE_ALGORITHM);

    let s = ph.to_string();
    assert_eq!(s, "$argon2d");

    let ph2 = PasswordHash::try_from(s.as_str()).unwrap();
    assert_eq!(ph, ph2);
}

#[test]
fn params() {
    let ph = PasswordHash {
        algorithm: EXAMPLE_ALGORITHM,
        version: None,
        params: example_params(),
        salt: None,
        hash: None,
    };

    let s = ph.to_string();
    assert_eq!(s, "$argon2d$a=1,b=2,c=3");

    let ph2 = PasswordHash::try_from(s.as_str()).unwrap();
    assert_eq!(ph, ph2);
}

#[test]
fn salt() {
    let ph = PasswordHash {
        algorithm: EXAMPLE_ALGORITHM,
        version: None,
        params: ParamsString::new(),
        salt: Some(Salt::new(EXAMPLE_SALT).unwrap()),
        hash: None,
    };

    let s = ph.to_string();
    assert_eq!(s, "$argon2d$saltsaltsaltsaltsalt");

    let ph2 = PasswordHash::try_from(s.as_str()).unwrap();
    assert_eq!(ph, ph2);
}

#[test]
fn one_param_and_salt() {
    let mut params = ParamsString::new();
    params.add_decimal("a", 1).unwrap();

    let ph = PasswordHash {
        algorithm: EXAMPLE_ALGORITHM,
        version: None,
        params,
        salt: Some(Salt::new(EXAMPLE_SALT).unwrap()),
        hash: None,
    };

    let s = ph.to_string();
    assert_eq!(s, "$argon2d$a=1$saltsaltsaltsaltsalt");

    let ph2 = PasswordHash::try_from(s.as_str()).unwrap();
    assert_eq!(ph, ph2);
}

#[test]
fn params_and_salt() {
    let ph = PasswordHash {
        algorithm: EXAMPLE_ALGORITHM,
        version: None,
        params: example_params(),
        salt: Some(Salt::new(EXAMPLE_SALT).unwrap()),
        hash: None,
    };

    let s = ph.to_string();
    assert_eq!(s, "$argon2d$a=1,b=2,c=3$saltsaltsaltsaltsalt");

    let ph2 = PasswordHash::try_from(s.as_str()).unwrap();
    assert_eq!(ph, ph2);
}

#[test]
fn salt_and_hash() {
    let ph = PasswordHash {
        algorithm: EXAMPLE_ALGORITHM,
        version: None,
        params: ParamsString::default(),
        salt: Some(Salt::new(EXAMPLE_SALT).unwrap()),
        hash: Some(EXAMPLE_HASH.try_into().unwrap()),
    };

    let s = ph.to_string();
    assert_eq!(
        s,
        "$argon2d$saltsaltsaltsaltsalt$hashhashhashhashhashhashhashhashhashhashhas"
    );

    let ph2 = PasswordHash::try_from(s.as_str()).unwrap();
    assert_eq!(ph, ph2);
}

#[test]
fn all_fields() {
    let ph = PasswordHash {
        algorithm: EXAMPLE_ALGORITHM,
        version: None,
        params: example_params(),
        salt: Some(Salt::new(EXAMPLE_SALT).unwrap()),
        hash: Some(EXAMPLE_HASH.try_into().unwrap()),
    };

    let s = ph.to_string();
    assert_eq!(
        s,
        "$argon2d$a=1,b=2,c=3$saltsaltsaltsaltsalt$hashhashhashhashhashhashhashhashhashhashhas"
    );

    let ph2 = PasswordHash::try_from(s.as_str()).unwrap();
    assert_eq!(ph, ph2);
}
