Skip to content

Commit

Permalink
added tests to see etags based on relevant filter for token
Browse files Browse the repository at this point in the history
  • Loading branch information
chriswk committed Jan 15, 2024
1 parent 8e0d88d commit 2137ff8
Show file tree
Hide file tree
Showing 15 changed files with 264 additions and 26 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ debug/
*.ipr
*.iws
.vscode
mutants.out

.project
.classpath
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ license = false
eula = false

[dependencies]
actix-cors = "0.6.5"
actix-cors = "0.7.0"
actix-http = { version = "3.5.1", features = ["compress-zstd", "rustls-0_21"] }
actix-middleware-etag = "0.3.0"
actix-service = "2.0.2"
Expand Down
123 changes: 121 additions & 2 deletions server/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::{
auth::token_validator::TokenValidator,
cli::{CliArgs, EdgeArgs, EdgeMode, OfflineArgs},
error::EdgeError,
filters, hashing,
http::{feature_refresher::FeatureRefresher, unleash_client::UnleashClient},
types::{EdgeResult, EdgeToken, TokenType},
};
Expand Down Expand Up @@ -58,12 +59,14 @@ async fn hydrate_from_persistent_storage(
let refresh_targets = storage.load_refresh_targets().await.unwrap_or_default();
for token in tokens {
tracing::debug!("Hydrating tokens {token:?}");
token_cache.insert(token.token.clone(), token);
token_cache.insert(token.token.clone(), token.clone());
etag_cache.insert(token.clone(), EntityTag::new_weak("".to_string()));
}

for (key, features) in features {
tracing::debug!("Hydrating features for {key:?}");
features_cache.insert(key.clone(), features.clone());
update_etags(etag_cache.clone(), &key, &features);
let mut engine_state = EngineState::default();
engine_state.take_state(features);
engine_cache.insert(key, engine_state);
Expand All @@ -77,6 +80,20 @@ async fn hydrate_from_persistent_storage(
}
}

fn update_etags(
etag_cache: Arc<DashMap<EdgeToken, EntityTag>>,
key: &str,
features: &ClientFeatures,
) {
let etag = crate::hashing::client_features_to_etag(features);
let token = EdgeToken::from_str(key).unwrap();
etag_cache.iter_mut().for_each(|mut t| {
if token.subsumes(t.key()) || &token == t.key() {
*t.value_mut() = etag.clone();
}
})
}

pub(crate) fn build_offline_mode(
client_features: ClientFeatures,
tokens: Vec<String>,
Expand All @@ -97,6 +114,11 @@ pub(crate) fn build_offline_mode(
engine_cache.clone(),
client_features.clone(),
);
let filtered = filters::filter_features_for_token(&client_features, &edge_token);
etag_cache.insert(
edge_token.clone(),
hashing::client_features_to_etag(&filtered),
);
}
Ok((token_cache, features_cache, engine_cache, etag_cache))
}
Expand Down Expand Up @@ -139,7 +161,6 @@ async fn build_edge(args: &EdgeArgs) -> EdgeResult<EdgeInfo> {

let persistence = get_data_source(args).await;

let etag_cache = Arc::new(DashMap::default());
let unleash_client = Url::parse(&args.upstream_url.clone())
.map(|url| {
UnleashClient::from_url(
Expand Down Expand Up @@ -212,3 +233,101 @@ pub async fn build_caches_and_refreshers(args: CliArgs) -> EdgeResult<EdgeInfo>
_ => unreachable!(),
}
}

#[cfg(test)]
mod tests {
use unleash_types::client_features::{ClientFeature, Strategy};

use super::*;

#[test]
fn building_an_initial_state_from_backup_also_adds_etags_for_features() {
let client_features = ClientFeatures {
version: 2,
features: vec![
ClientFeature {
name: "first_feature".into(),
feature_type: Some("Release".into()),
description: None,
created_at: None,
last_seen_at: None,
enabled: true,
stale: Some(false),
impression_data: Some(false),
project: Some("default".into()),
strategies: Some(vec![Strategy {
name: "gradualRollout".into(),
sort_order: Some(1),
segments: None,
constraints: None,
parameters: None,
variants: None,
}]),
variants: None,
dependencies: None,
},
ClientFeature {
name: "second_feature".into(),
feature_type: Some("Release".into()),
description: None,
created_at: None,
last_seen_at: None,
enabled: true,
stale: Some(false),
impression_data: Some(false),
project: Some("eg".into()),
strategies: Some(vec![Strategy {
name: "gradualRollout".into(),
sort_order: Some(1),
segments: None,
constraints: None,
parameters: None,
variants: None,
}]),
variants: None,
dependencies: None,
},
ClientFeature {
name: "third_feature".into(),
feature_type: Some("Release".into()),
description: None,
created_at: None,
last_seen_at: None,
enabled: true,
stale: Some(false),
impression_data: Some(false),
project: Some("default".into()),
strategies: Some(vec![Strategy {
name: "gradualRollout".into(),
sort_order: Some(1),
segments: None,
constraints: None,
parameters: None,
variants: None,
}]),
variants: None,
dependencies: None,
},
],
segments: None,
query: None,
};
let wildcard_token = EdgeToken::from_str("*:development.somerandomstring").unwrap();
let subsumed_token = EdgeToken::from_str("eg:development.someotherrandomstring").unwrap();
let (_, _, _, etag_cache) = build_offline_mode(
client_features,
vec![wildcard_token.token.clone(), subsumed_token.token.clone()],
)
.unwrap();
assert!(etag_cache.contains_key(&wildcard_token));
assert!(etag_cache.contains_key(&subsumed_token));
assert_eq!(
etag_cache.get(&wildcard_token).unwrap().value(),
&EntityTag::new_weak("248-ISJs9qZR-gpTrJaMsTeZ8g==".into())
);
assert_eq!(
etag_cache.get(&subsumed_token).unwrap().value(),
&EntityTag::new_weak("da-ufCoeTJdSw9a7U9Av9k1qQ==".into())
);
}
}
11 changes: 11 additions & 0 deletions server/src/client_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ mod tests {
use crate::middleware;
use crate::tests::{features_from_disk, upstream_server};
use actix_http::Request;
use actix_web::http::header::EntityTag;
use actix_web::{
http::header::ContentType,
test,
Expand Down Expand Up @@ -863,10 +864,12 @@ mod tests {
Arc::new(DashMap::default());
let upstream_token_cache: Arc<DashMap<String, EdgeToken>> = Arc::new(DashMap::default());
let upstream_engine_cache: Arc<DashMap<String, EngineState>> = Arc::new(DashMap::default());
let upstream_etag_cache: Arc<DashMap<EdgeToken, EntityTag>> = Arc::new(DashMap::default());
let server = upstream_server(
upstream_token_cache.clone(),
upstream_features_cache.clone(),
upstream_engine_cache.clone(),
upstream_etag_cache.clone(),
)
.await;
let upstream_features = features_from_disk("../examples/hostedexample.json");
Expand All @@ -882,11 +885,13 @@ mod tests {
let features_cache: Arc<DashMap<String, ClientFeatures>> = Arc::new(DashMap::default());
let token_cache: Arc<DashMap<String, EdgeToken>> = Arc::new(DashMap::default());
let engine_cache: Arc<DashMap<String, EngineState>> = Arc::new(DashMap::default());
let etag_cache: Arc<DashMap<EdgeToken, EntityTag>> = Arc::new(DashMap::default());
let feature_refresher = Arc::new(FeatureRefresher {
unleash_client: unleash_client.clone(),
tokens_to_refresh: Arc::new(Default::default()),
features_cache: features_cache.clone(),
engine_cache: engine_cache.clone(),
etag_cache: etag_cache.clone(),
refresh_interval: Duration::seconds(6000),
persistence: None,
});
Expand Down Expand Up @@ -986,10 +991,13 @@ mod tests {
Arc::new(DashMap::default());
let upstream_token_cache: Arc<DashMap<String, EdgeToken>> = Arc::new(DashMap::default());
let upstream_engine_cache: Arc<DashMap<String, EngineState>> = Arc::new(DashMap::default());
let upstream_etag_cache: Arc<DashMap<EdgeToken, EntityTag>> = Arc::new(DashMap::default());

let server = upstream_server(
upstream_token_cache.clone(),
upstream_features_cache.clone(),
upstream_engine_cache.clone(),
upstream_etag_cache.clone(),
)
.await;
let upstream_features = features_from_disk("../examples/hostedexample.json");
Expand All @@ -1006,10 +1014,13 @@ mod tests {
let features_cache: Arc<DashMap<String, ClientFeatures>> = Arc::new(DashMap::default());
let token_cache: Arc<DashMap<String, EdgeToken>> = Arc::new(DashMap::default());
let engine_cache: Arc<DashMap<String, EngineState>> = Arc::new(DashMap::default());
let etag_cache = Arc::new(DashMap::default());

let feature_refresher = Arc::new(FeatureRefresher::new(
unleash_client.clone(),
features_cache.clone(),
engine_cache.clone(),
etag_cache.clone(),
Duration::seconds(6000),
None,
));
Expand Down
14 changes: 14 additions & 0 deletions server/src/filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ fn filter_features(
.collect::<Vec<ClientFeature>>()
}

pub fn filter_features_for_token(features: &ClientFeatures, token: &EdgeToken) -> ClientFeatures {
let f = FeatureFilterSet::from(project_filter(&token));

Check warning

Code scanning / clippy

this expression creates a reference which is immediately dereferenced by the compiler Warning

this expression creates a reference which is immediately dereferenced by the compiler
let filtered = features
.features
.iter()
.filter(|feature| f.apply(feature))
.cloned()
.collect::<Vec<ClientFeature>>();
ClientFeatures {
features: filtered,
..features.clone()
}
}

pub(crate) fn filter_client_features(
feature_cache: &Ref<'_, String, ClientFeatures>,
filters: &FeatureFilterSet,
Expand Down
Loading

0 comments on commit 2137ff8

Please sign in to comment.