Skip to content

Commit

Permalink
frontend: Make features code more modular
Browse files Browse the repository at this point in the history
By making it more modular it is easier to add
unit tests fro current behavior.
  • Loading branch information
almusil authored and Joshua Nelson committed Nov 13, 2020
1 parent 0a79e48 commit 6a81b64
Showing 1 changed file with 154 additions and 22 deletions.
176 changes: 154 additions & 22 deletions src/web/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use router::Router;
use serde::Serialize;
use std::collections::{HashMap, VecDeque};

const DEFAULT_NAME: &str = "default";

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
struct FeaturesPage {
metadata: MetaData,
Expand Down Expand Up @@ -38,28 +40,14 @@ pub fn build_features_handler(req: &mut Request) -> IronResult<Response> {

let row = cexpect!(req, rows.get(0));

let mut features = None;
let mut default_len = 0;
let features = row
.get::<'_, usize, Option<Vec<Feature>>>(0)
.map(|raw| {
raw.into_iter()
.filter(|feature| !feature.is_private())
.map(|feature| (feature.name.clone(), feature))
.collect::<HashMap<String, Feature>>()
})
.map(|mut feature_map| {
let mut features = get_tree_structure_from_default(&mut feature_map);
let mut remaining = feature_map
.into_iter()
.map(|(_, feature)| feature)
.collect::<Vec<Feature>>();
remaining.sort_by_key(|feature| feature.subfeatures.len());

default_len = features.len();

features.extend(remaining.into_iter().rev());
features
});

if let Some(raw) = row.get(0) {
let result = order_features_and_count_default_len(raw);
features = Some(result.0);
default_len = result.1;
}

FeaturesPage {
metadata: cexpect!(req, MetaData::from_crate(&mut conn, &name, &version)),
Expand All @@ -69,11 +57,26 @@ pub fn build_features_handler(req: &mut Request) -> IronResult<Response> {
.into_response(req)
}

fn order_features_and_count_default_len(raw: Vec<Feature>) -> (Vec<Feature>, usize) {
let mut feature_map = get_feature_map(raw);
let mut features = get_tree_structure_from_default(&mut feature_map);
let mut remaining: Vec<_> = feature_map
.into_iter()
.map(|(_, feature)| feature)
.collect();
remaining.sort_by_key(|feature| feature.subfeatures.len());

let default_len = features.len();

features.extend(remaining.into_iter().rev());
(features, default_len)
}

fn get_tree_structure_from_default(feature_map: &mut HashMap<String, Feature>) -> Vec<Feature> {
let mut features = Vec::new();
let mut queue: VecDeque<String> = VecDeque::new();

queue.push_back("default".into());
queue.push_back(DEFAULT_NAME.into());
while !queue.is_empty() {
let name = queue.pop_front().unwrap();
if let Some(feature) = feature_map.remove(&name) {
Expand All @@ -86,3 +89,132 @@ fn get_tree_structure_from_default(feature_map: &mut HashMap<String, Feature>) -
}
features
}

fn get_feature_map(raw: Vec<Feature>) -> HashMap<String, Feature> {
raw.into_iter()
.filter(|feature| !feature.is_private())
.map(|feature| (feature.name.clone(), feature))
.collect()
}

#[cfg(test)]
mod tests {
use crate::db::types::Feature;
use crate::web::features::{
get_feature_map, get_tree_structure_from_default, order_features_and_count_default_len,
DEFAULT_NAME,
};

#[test]
fn test_feature_map_filters_private() {
let private1 = Feature::new("_private1".into(), vec!["feature1".into()]);
let feature2 = Feature::new("feature2".into(), Vec::new());

let raw = vec![private1.clone(), feature2.clone()];
let feature_map = get_feature_map(raw);

assert_eq!(feature_map.len(), 1);
assert!(feature_map.contains_key(&feature2.name));
assert!(!feature_map.contains_key(&private1.name));
}

#[test]
fn test_default_tree_structure_with_nested_default() {
let default = Feature::new(DEFAULT_NAME.into(), vec!["feature1".into()]);
let non_default = Feature::new("non-default".into(), Vec::new());
let feature1 = Feature::new(
"feature1".into(),
vec!["feature2".into(), "feature3".into()],
);
let feature2 = Feature::new("feature2".into(), Vec::new());
let feature3 = Feature::new("feature3".into(), Vec::new());

let raw = vec![
default.clone(),
non_default.clone(),
feature3.clone(),
feature2.clone(),
feature1.clone(),
];
let mut feature_map = get_feature_map(raw);
let default_tree = get_tree_structure_from_default(&mut feature_map);

assert_eq!(feature_map.len(), 1);
assert_eq!(default_tree.len(), 4);
assert!(feature_map.contains_key(&non_default.name));
assert!(!feature_map.contains_key(&default.name));
assert_eq!(default_tree[0], default);
assert_eq!(default_tree[1], feature1);
assert_eq!(default_tree[2], feature2);
assert_eq!(default_tree[3], feature3);
}

#[test]
fn test_default_tree_structure_without_default() {
let feature1 = Feature::new(
"feature1".into(),
vec!["feature2".into(), "feature3".into()],
);
let feature2 = Feature::new("feature2".into(), Vec::new());
let feature3 = Feature::new("feature3".into(), Vec::new());

let raw = vec![feature3.clone(), feature2.clone(), feature1.clone()];
let mut feature_map = get_feature_map(raw);
let default_tree = get_tree_structure_from_default(&mut feature_map);

assert_eq!(feature_map.len(), 3);
assert_eq!(default_tree.len(), 0);
assert!(feature_map.contains_key(&feature1.name));
assert!(feature_map.contains_key(&feature2.name));
assert!(feature_map.contains_key(&feature3.name));
}

#[test]
fn test_default_tree_structure_single_default() {
let default = Feature::new(DEFAULT_NAME.into(), Vec::new());
let non_default = Feature::new("non-default".into(), Vec::new());

let raw = vec![default.clone(), non_default.clone()];
let mut feature_map = get_feature_map(raw);
let default_tree = get_tree_structure_from_default(&mut feature_map);

assert_eq!(feature_map.len(), 1);
assert_eq!(default_tree.len(), 1);
assert!(feature_map.contains_key(&non_default.name));
assert!(!feature_map.contains_key(&default.name));
assert_eq!(default_tree[0], default);
}

#[test]
fn test_order_features_and_get_len_without_default() {
let feature1 = Feature::new(
"feature1".into(),
vec!["feature10".into(), "feature11".into()],
);
let feature2 = Feature::new("feature2".into(), vec!["feature20".into()]);
let feature3 = Feature::new("feature3".into(), Vec::new());

let raw = vec![feature3.clone(), feature2.clone(), feature1.clone()];
let (features, default_len) = order_features_and_count_default_len(raw);

assert_eq!(features.len(), 3);
assert_eq!(default_len, 0);
assert_eq!(features[0], feature1);
assert_eq!(features[1], feature2);
assert_eq!(features[2], feature3);
}

#[test]
fn test_order_features_and_get_len_single_default() {
let default = Feature::new(DEFAULT_NAME.into(), Vec::new());
let non_default = Feature::new("non-default".into(), Vec::new());

let raw = vec![default.clone(), non_default.clone()];
let (features, default_len) = order_features_and_count_default_len(raw);

assert_eq!(features.len(), 2);
assert_eq!(default_len, 1);
assert_eq!(features[0], default);
assert_eq!(features[1], non_default);
}
}

0 comments on commit 6a81b64

Please sign in to comment.