password-as-service 0.1.4

a very poorly considered http/rest service
Documentation
use crate::*;
use std::{collections::HashMap, io::Result};

use model::{Group, GroupQueryBuilder, User, UserQueryBuilder};

/// Mock a resource with an in-memory store that never throws an error.
pub struct MockResource<T>(Vec<T>);
/// Mock a resource by always throwing an error.
pub struct MockBadResource;

#[derive(Copy, Clone, Debug)]
/// An arbitrary IO error.
pub struct MockError;

impl<T: UniqueID> Resource<T> for MockBadResource {
    fn load(&self) -> Result<ArcMap<T>> {
        Err(std::io::Error::new(std::io::ErrorKind::Other, MockError))
    }
}

impl<T> Resource<T> for MockResource<T>
where
    T: Clone + UniqueID,
{
    fn load(&self) -> Result<std::sync::Arc<HashMap<u64, T>>> {
        Ok(std::sync::Arc::new(
            self.0.iter().map(|t| (t.id(), t.clone())).collect(),
        ))
    }
}

impl<T> Resource<T> for &MockResource<T>
where
    T: Clone + UniqueID,
{
    fn load(&self) -> Result<std::sync::Arc<HashMap<u64, T>>> {
        Ok(std::sync::Arc::new(
            self.0.iter().map(|t| (t.id(), t.clone())).collect(),
        ))
    }
}

impl<T: UniqueID> Resource<T> for &MockBadResource {
    fn load(&self) -> Result<std::sync::Arc<HashMap<u64, T>>> {
        Err(std::io::Error::new(std::io::ErrorKind::Other, MockError))
    }
}

fn users() -> Vec<User> {
    vec![root(), dwoodlin(), same_username_as_dwoodlin()]
}

fn root() -> User {
    User {
        name: "root".to_string(),
        uid: 0,
        gid: 0,
        comment: "root".to_string(),
        home: "/root".to_string(),
        shell: "/b
    in/bash"
            .to_string(),
    }
}
fn dwoodlin() -> User {
    User {
        name: "dwoodlins".to_string(),
        uid: 1001,
        gid: 1001,
        comment: "".to_string(),
        home: "/bin/false".to_string(),
        shell: "/bin/false".to_string(),
    }
}

fn same_username_as_dwoodlin() -> User {
    User {
        name: "dwoodlins".to_string(),
        uid: 10,
        gid: 10,
        comment: "hi".to_string(),
        home: "/bin/true".to_string(),
        shell: "/bin/true".to_string(),
    }
}

fn sorted_by_id<T: UniqueID>(a: impl IntoIterator<Item = T>) -> Vec<T> {
    let mut a: Vec<T> = a.into_iter().collect();
    a.sort_by_key(UniqueID::id);
    a
}

fn admins() -> Group {
    Group {
        name: "admin".to_string().to_owned(),
        gid: 1,
        members: vec![dwoodlin().name.to_string(), root().name.to_string()],
    }
}
#[test]
fn get_group() {
    let groups = MockResource(vec![admins()]);
    assert!(api::get_group(&groups, 1).unwrap() == admins());
}

#[test]
fn get_groups_for_user() {
    struct Test {
        groups: MockResource<Group>,
        uid: u64,
        want: Vec<Group>,
        msg: &'static str,
    }

    let tests: Vec<Test> = vec![
        Test {
            groups: MockResource(vec![admins()]),
            want: vec![admins()],
            uid: dwoodlin().uid,
            msg: "only overlapping groups",
        },
        Test {
            groups: MockResource(vec![]),
            want: vec![],
            uid: dwoodlin().uid,
            msg: "empty groups",
        },
    ];
    let users = MockResource(users().to_vec());
    for Test { groups, uid, want, msg } in tests {
        let got = sorted_by_id(api::get_groups_for_user(&groups, &users, uid).unwrap());
        let want = sorted_by_id(want);
        assert_eq!(got, want, "{}", msg);
    }

    assert!(api::get_groups_for_user(&MockBadResource, users, 0).is_none())
}

#[test]
#[ignore]
fn known_bad_test_for_get_groups_for_user() {
    assert_eq!(
        api::get_groups_for_user(
            MockResource(vec![admins()]),
            &MockResource(users().to_vec()),
            same_username_as_dwoodlin().uid
        )
        .unwrap(),
        vec![],
        "should have no overlap, but shared username leads to bad result; not properly unique"
    )
}
#[test]
fn get_matching_users() {
    struct Test {
        query: UserQueryBuilder,
        users: Vec<User>,
        want: Vec<User>,
        msg: &'static str,
    }

    let tests: Vec<Test> = vec![
        Test {
            query: UserQueryBuilder::new().with_name(&dwoodlin().name),
            users: users(),
            want: vec![dwoodlin(), same_username_as_dwoodlin()],
            msg: "get multiple matches",
        },
        Test {
            query: UserQueryBuilder::new().with_uid(10000),
            users: users(),
            want: vec![],
            msg: "no matches",
        },
    ];
    for Test {
        query,
        users,
        want,
        msg,
    } in tests
    {
        let resource = &MockResource(users.to_vec());
        let got = api::get_matching_users(query.build().unwrap(), resource).unwrap();
        assert_eq!(sorted_by_id(got), sorted_by_id(want.to_vec()), "{}", msg)
    }
}
#[test]
fn get_all_groups() {
    assert_eq!(
        api::get_all_groups(&MockResource(vec![admins()])).unwrap(),
        vec![admins()]
    );
}

#[test]
fn get_matching_groups() {
    struct Test {
        groups: Vec<Group>,
        query: GroupQueryBuilder,
        want: Vec<Group>,
    }
    let tests: Vec<Test> = vec![Test {
        groups: vec![admins()],
        query: GroupQueryBuilder::new().with_gid(admins().gid),
        want: vec![admins()],
    }];
    for Test { groups, query, want } in tests {
        let groups = MockResource(groups.to_vec());
        let query = query.build().unwrap();
        assert_eq!(api::get_matching_groups(query, groups).unwrap(), want);
    }
}
#[test]
fn test_is_match() {
    for query in vec![
        UserQueryBuilder::new().with_name(&root().name),
        UserQueryBuilder::new().with_comment(&root().comment),
        UserQueryBuilder::new().with_uid(root().uid),
        UserQueryBuilder::new().with_gid(root().gid),
        UserQueryBuilder::new().with_home(&root().home),
        UserQueryBuilder::new().with_shell(&root().shell),
    ] {
        assert!(query.build().unwrap().is_match(&root()))
    }

    for query in vec![
        UserQueryBuilder::new().with_name(&dwoodlin().name),
        UserQueryBuilder::new().with_comment(&dwoodlin().comment),
        UserQueryBuilder::new().with_uid(dwoodlin().uid),
        UserQueryBuilder::new().with_gid(dwoodlin().gid),
        UserQueryBuilder::new().with_home(&dwoodlin().home),
        UserQueryBuilder::new().with_shell(&dwoodlin().shell),
    ] {
        assert!(!query.build().unwrap().is_match(&root()))
    }

    let exact_match = UserQueryBuilder {
        name: Some(root().name.to_string()),
        comment: Some(root().comment.to_string()),
        uid: Some(root().uid),
        gid: Some(root().gid),
        home: Some(root().home.to_string()),
        shell: Some(root().shell.to_string()),
    };
    assert!(exact_match.clone().build().unwrap().is_match(&root()));

    let one_criteria_fails = exact_match.with_comment(&dwoodlin().comment);
    assert!(!one_criteria_fails.build().unwrap().is_match(&root()))
}

impl std::fmt::Display for MockError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "mock error")
    }
}
impl std::error::Error for MockError {}