Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/engine_eval/mappers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub fn environment_to_context(environment: Environment) -> EngineEvaluationConte
let mut ctx = EngineEvaluationContext {
environment: EnvironmentContext {
key: environment.api_key.clone(),
name: environment.api_key.clone(),
name: environment.name.clone(),
},
features: HashMap::new(),
segments: HashMap::new(),
Expand Down
1 change: 1 addition & 0 deletions src/environments/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod builders;
pub struct Environment {
pub id: u32,
pub api_key: String,
pub name: String,
pub project: projects::Project,
pub feature_states: Vec<features::FeatureState>,

Expand Down
311 changes: 311 additions & 0 deletions tests/mappers_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
use flagsmith_flag_engine::engine_eval::{
context::{ConditionOperator, ConditionValue, SegmentRuleType, SegmentSource},
environment_to_context,
};
use flagsmith_flag_engine::environments::Environment;

fn get_environment_fixture() -> &'static str {
r#"{
"api_key": "test_key",
"name": "Test Environment",
"project": {
"name": "Test project",
"organisation": {
"feature_analytics": false,
"name": "Test Org",
"id": 1,
"persist_trait_data": true,
"stop_serving_flags": false
},
"id": 1,
"hide_disabled_flags": false,
"segments": [
{
"id": 1,
"name": "Test segment",
"rules": [
{
"type": "ALL",
"rules": [
{
"type": "ALL",
"rules": [],
"conditions": [
{
"operator": "EQUAL",
"property_": "foo",
"value": "bar"
}
]
}
],
"conditions": []
}
],
"feature_states": []
}
]
},
"segment_overrides": [],
"id": 1,
"feature_states": [
{
"multivariate_feature_state_values": [],
"feature_state_value": "some-value",
"id": 1,
"featurestate_uuid": "00000000-0000-0000-0000-000000000000",
"feature": {
"name": "some_feature",
"type": "STANDARD",
"id": 1
},
"segment_id": null,
"enabled": true
},
{
"feature_state_value": "default_value",
"django_id": 2,
"featurestate_uuid": "11111111-1111-1111-1111-111111111111",
"feature": {
"name": "mv_feature_with_ids",
"type": "MULTIVARIATE",
"id": 2
},
"segment_id": null,
"enabled": true,
"multivariate_feature_state_values": [
{
"id": 100,
"multivariate_feature_option": {
"id": 10,
"value": "variant_a"
},
"mv_fs_value_uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"percentage_allocation": 30.0
},
{
"id": 200,
"multivariate_feature_option": {
"id": 20,
"value": "variant_b"
},
"mv_fs_value_uuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"percentage_allocation": 70.0
}
]
},
{
"feature_state_value": "fallback_value",
"django_id": 3,
"featurestate_uuid": "22222222-2222-2222-2222-222222222222",
"feature": {
"name": "mv_feature_without_ids",
"type": "MULTIVARIATE",
"id": 3
},
"segment_id": null,
"enabled": false,
"multivariate_feature_state_values": [
{
"multivariate_feature_option": {
"id": 40,
"value": "option_y"
},
"mv_fs_value_uuid": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
"percentage_allocation": 50.0
},
{
"multivariate_feature_option": {
"id": 30,
"value": "option_x"
},
"mv_fs_value_uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"percentage_allocation": 25.0
},
{
"multivariate_feature_option": {
"id": 50,
"value": "option_z"
},
"mv_fs_value_uuid": "zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz",
"percentage_allocation": 25.0
}
]
}
],
"identity_overrides": [
{
"identifier": "overridden-id",
"identity_uuid": "0f21cde8-63c5-4e50-baca-87897fa6cd01",
"created_date": "2019-08-27T14:53:45.698555Z",
"updated_at": "2023-07-14T16:12:00.000000",
"environment_api_key": "test_key",
"identity_features": [
{
"id": 1,
"feature": {
"id": 1,
"name": "some_feature",
"type": "STANDARD"
},
"featurestate_uuid": "1bddb9a5-7e59-42c6-9be9-625fa369749f",
"feature_state_value": "some-overridden-value",
"enabled": false,
"django_id": null,
"environment": 1,
"identity": null,
"feature_segment": null
}
]
}
]
}"#
}

#[test]
fn test_environment_to_context_produces_evaluation_context() {
let json = get_environment_fixture();
let environment: Environment = serde_json::from_str(json).expect("Failed to parse environment");

let context = environment_to_context(environment);

// Verify environment context
assert_eq!(context.environment.key, "test_key");
assert_eq!(context.environment.name, "Test Environment");
assert!(context.identity.is_none());
assert_eq!(context.segments.len(), 2);

// Verify API segment
assert!(context.segments.contains_key("1"));
let api_segment = context.segments.get("1").unwrap();
assert_eq!(api_segment.key, "1");
assert_eq!(api_segment.name, "Test segment");
assert_eq!(api_segment.rules.len(), 1);
assert!(api_segment.overrides.is_empty());
assert_eq!(api_segment.metadata.source, SegmentSource::Api);
assert_eq!(api_segment.metadata.segment_id, Some(1));

// Verify segment rule structure
assert_eq!(api_segment.rules[0].rule_type, SegmentRuleType::All);
assert!(api_segment.rules[0].conditions.is_empty());
assert_eq!(api_segment.rules[0].rules.len(), 1);

assert_eq!(
api_segment.rules[0].rules[0].rule_type,
SegmentRuleType::All
);
assert_eq!(api_segment.rules[0].rules[0].conditions.len(), 1);
assert!(api_segment.rules[0].rules[0].rules.is_empty());

assert_eq!(api_segment.rules[0].rules[0].conditions[0].property, "foo");
assert_eq!(
api_segment.rules[0].rules[0].conditions[0].operator,
ConditionOperator::Equal
);
assert_eq!(
api_segment.rules[0].rules[0].conditions[0]
.value
.as_string(),
"bar"
);

// Verify identity override segment exists
let override_segments: Vec<_> = context
.segments
.iter()
.filter(|(_, s)| s.metadata.source == SegmentSource::IdentityOverride)
.collect();
assert_eq!(override_segments.len(), 1);

let (_, override_segment) = override_segments[0];
assert_eq!(override_segment.key, "");
assert_eq!(override_segment.name, "identity_overrides");
assert_eq!(override_segment.rules.len(), 1);
assert_eq!(override_segment.overrides.len(), 1);

assert_eq!(override_segment.rules[0].rule_type, SegmentRuleType::All);
assert_eq!(override_segment.rules[0].conditions.len(), 1);
assert!(override_segment.rules[0].rules.is_empty());

assert_eq!(
override_segment.rules[0].conditions[0].property,
"$.identity.identifier"
);
assert_eq!(
override_segment.rules[0].conditions[0].operator,
ConditionOperator::In
);
match &override_segment.rules[0].conditions[0].value {
ConditionValue::Multiple(identifiers) => {
assert_eq!(identifiers, &vec!["overridden-id".to_string()]);
}
_ => panic!("Expected Multiple condition value"),
}

assert_eq!(override_segment.overrides[0].key, "");
assert_eq!(override_segment.overrides[0].name, "some_feature");
assert!(!override_segment.overrides[0].enabled);
assert_eq!(
override_segment.overrides[0].value.value,
"some-overridden-value"
);
assert_eq!(
override_segment.overrides[0].priority,
Some(f64::NEG_INFINITY)
);
assert!(override_segment.overrides[0].variants.is_empty());
assert_eq!(override_segment.overrides[0].metadata.feature_id, 1);

// Verify features
assert_eq!(context.features.len(), 3);

// Verify some_feature
assert!(context.features.contains_key("some_feature"));
let some_feature = context.features.get("some_feature").unwrap();
assert_eq!(some_feature.key, "00000000-0000-0000-0000-000000000000");
assert_eq!(some_feature.name, "some_feature");
assert!(some_feature.enabled);
assert_eq!(some_feature.value.value, "some-value");
assert!(some_feature.priority.is_none());
assert!(some_feature.variants.is_empty());
assert_eq!(some_feature.metadata.feature_id, 1);

// Verify multivariate feature with IDs
assert!(context.features.contains_key("mv_feature_with_ids"));
let mv_feature_with_ids = context.features.get("mv_feature_with_ids").unwrap();
assert_eq!(mv_feature_with_ids.key, "2");
assert_eq!(mv_feature_with_ids.name, "mv_feature_with_ids");
assert!(mv_feature_with_ids.enabled);
assert_eq!(mv_feature_with_ids.value.value, "default_value");
assert!(mv_feature_with_ids.priority.is_none());
assert_eq!(mv_feature_with_ids.variants.len(), 2);
assert_eq!(mv_feature_with_ids.metadata.feature_id, 2);

// Verify variants
assert_eq!(mv_feature_with_ids.variants[0].value.value, "variant_a");
assert_eq!(mv_feature_with_ids.variants[0].weight, 30.0);

assert_eq!(mv_feature_with_ids.variants[1].value.value, "variant_b");
assert_eq!(mv_feature_with_ids.variants[1].weight, 70.0);

// Verify multivariate feature without IDs
assert!(context.features.contains_key("mv_feature_without_ids"));
let mv_feature_without_ids = context.features.get("mv_feature_without_ids").unwrap();
assert_eq!(mv_feature_without_ids.key, "3");
assert_eq!(mv_feature_without_ids.name, "mv_feature_without_ids");
assert!(!mv_feature_without_ids.enabled);
assert_eq!(mv_feature_without_ids.value.value, "fallback_value");
assert!(mv_feature_without_ids.priority.is_none());
assert_eq!(mv_feature_without_ids.variants.len(), 3);
assert_eq!(mv_feature_without_ids.metadata.feature_id, 3);

// Verify variants preserve order from input
assert_eq!(mv_feature_without_ids.variants[0].value.value, "option_y");
assert_eq!(mv_feature_without_ids.variants[0].weight, 50.0);

assert_eq!(mv_feature_without_ids.variants[1].value.value, "option_x");
assert_eq!(mv_feature_without_ids.variants[1].weight, 25.0);

assert_eq!(mv_feature_without_ids.variants[2].value.value, "option_z");
assert_eq!(mv_feature_without_ids.variants[2].weight, 25.0);
}