optimizely 0.5.0

An unofficial Rust SDK for Optimizely Feature Experimentation
// External imports
use std::error::Error;

//
use optimizely::AttributeValue;

// Relative imports of sub modules
use common::setup;
mod common;

macro_rules! assert_decision {
    ($ctx: ident, $flag_key: ident, $user_id: expr, $enabled: expr, $variation_key: expr) => {{
        // Create new user context
        let user_context = $ctx.client.create_user_context($user_id);

        // Make decision for user
        let decision = user_context.decide($flag_key);

        // Assert the decision is consistent with given values
        assert_eq!(decision.enabled(), $enabled);
        assert_eq!(decision.variation_key(), $variation_key);
    }};
}

#[test]
fn feature_rollout() -> Result<(), Box<dyn Error>> {
    let ctx = setup()?;
    let flag_key = "feature_rollout";

    // Test against decision generated by the Python SDK
    assert_decision!(ctx, flag_key, "user0", false, "off");
    assert_decision!(ctx, flag_key, "user1", false, "off");
    assert_decision!(ctx, flag_key, "user2", false, "off");
    assert_decision!(ctx, flag_key, "user3", false, "off");
    assert_decision!(ctx, flag_key, "user4", false, "off");
    assert_decision!(ctx, flag_key, "user5", false, "off");
    assert_decision!(ctx, flag_key, "user6", true, "on");
    assert_decision!(ctx, flag_key, "user7", true, "on");
    assert_decision!(ctx, flag_key, "user8", true, "on");
    assert_decision!(ctx, flag_key, "user9", false, "off");
    assert_decision!(ctx, flag_key, "user10", false, "off");
    assert_decision!(ctx, flag_key, "user11", false, "off");
    assert_decision!(ctx, flag_key, "user12", true, "on");
    assert_decision!(ctx, flag_key, "user13", false, "off");
    assert_decision!(ctx, flag_key, "user14", false, "off");
    assert_decision!(ctx, flag_key, "user15", false, "off");
    assert_decision!(ctx, flag_key, "user16", false, "off");
    assert_decision!(ctx, flag_key, "user17", false, "off");
    assert_decision!(ctx, flag_key, "user18", false, "off");
    assert_decision!(ctx, flag_key, "user19", false, "off");
    assert_decision!(ctx, flag_key, "user20", false, "off");
    assert_decision!(ctx, flag_key, "user21", false, "off");
    assert_decision!(ctx, flag_key, "user22", false, "off");
    assert_decision!(ctx, flag_key, "user23", true, "on");
    assert_decision!(ctx, flag_key, "user24", true, "on");
    assert_decision!(ctx, flag_key, "user25", true, "on");
    assert_decision!(ctx, flag_key, "user26", false, "off");
    assert_decision!(ctx, flag_key, "user27", false, "off");
    assert_decision!(ctx, flag_key, "user28", false, "off");
    assert_decision!(ctx, flag_key, "user29", false, "off");
    assert_decision!(ctx, flag_key, "user30", false, "off");
    assert_decision!(ctx, flag_key, "user31", true, "on");

    // Since this key is a rollout, no events should be dispatched
    assert_eq!(ctx.decision_counter.value(), 0);

    Ok(())
}

#[test]
fn a_b_test() -> Result<(), Box<dyn Error>> {
    let ctx = setup()?;
    let flag_key = "a_b_test";

    // Test against decision generated by the Python SDK
    assert_decision!(ctx, flag_key, "user0", true, "variation_1");
    assert_decision!(ctx, flag_key, "user1", true, "variation_3");
    assert_decision!(ctx, flag_key, "user2", true, "variation_3");
    assert_decision!(ctx, flag_key, "user3", true, "variation_3");
    assert_decision!(ctx, flag_key, "user4", true, "variation_1");
    assert_decision!(ctx, flag_key, "user5", true, "variation_2");
    assert_decision!(ctx, flag_key, "user6", true, "variation_1");
    assert_decision!(ctx, flag_key, "user7", true, "variation_2");
    assert_decision!(ctx, flag_key, "user8", true, "variation_3");
    assert_decision!(ctx, flag_key, "user9", true, "variation_2");
    assert_decision!(ctx, flag_key, "user10", true, "variation_1");
    assert_decision!(ctx, flag_key, "user11", true, "variation_1");
    assert_decision!(ctx, flag_key, "user12", true, "variation_3");
    assert_decision!(ctx, flag_key, "user13", true, "variation_2");
    assert_decision!(ctx, flag_key, "user14", true, "variation_3");
    assert_decision!(ctx, flag_key, "user15", true, "variation_3");
    assert_decision!(ctx, flag_key, "user16", true, "variation_1");
    assert_decision!(ctx, flag_key, "user17", true, "variation_2");
    assert_decision!(ctx, flag_key, "user18", true, "variation_3");
    assert_decision!(ctx, flag_key, "user19", true, "variation_3");
    assert_decision!(ctx, flag_key, "user20", true, "variation_2");
    assert_decision!(ctx, flag_key, "user21", true, "variation_1");
    assert_decision!(ctx, flag_key, "user22", true, "variation_2");
    assert_decision!(ctx, flag_key, "user23", true, "variation_3");
    assert_decision!(ctx, flag_key, "user24", true, "variation_2");
    assert_decision!(ctx, flag_key, "user25", true, "variation_3");
    assert_decision!(ctx, flag_key, "user26", true, "variation_1");
    assert_decision!(ctx, flag_key, "user27", true, "variation_1");
    assert_decision!(ctx, flag_key, "user28", true, "variation_2");
    assert_decision!(ctx, flag_key, "user29", true, "variation_3");
    assert_decision!(ctx, flag_key, "user30", true, "variation_3");
    assert_decision!(ctx, flag_key, "user31", true, "variation_1");

    // Each of those 32 users should dispatch an event
    assert_eq!(ctx.decision_counter.value(), 32);

    Ok(())
}

#[test]
fn invalid_flag() -> Result<(), Box<dyn Error>> {
    let ctx = setup()?;
    let flag_key = "this_flag_does_not_exist";

    // An invalid flag should always be disabled
    assert_decision!(ctx, flag_key, "user0", false, "off");
    assert_decision!(ctx, flag_key, "user1", false, "off");
    assert_decision!(ctx, flag_key, "user2", false, "off");
    assert_decision!(ctx, flag_key, "user3", false, "off");

    // Since this key does not exist, no events should be dispatched
    assert_eq!(ctx.decision_counter.value(), 0);

    Ok(())
}

#[test]
fn targeted_delivery() -> Result<(), Box<dyn Error>> {
    let ctx = setup()?;
    let flag_key = "targeted_delivery";

    // Create user context
    let mut user_context = ctx.client.create_user_context("user0");

    // Since no attribute is set, the user should see the default
    assert_eq!(user_context.decide(flag_key).variation_key(), "off");

    // Update user attribute, and assert new variation
    user_context.set_attribute("appVersion", AttributeValue::String("0.20.3".into()));
    user_context.set_attribute("currentPath", AttributeValue::String("/home".into()));
    user_context.set_attribute("numberOfProductsInCart", AttributeValue::Integer(0));
    user_context.set_attribute("isLoggedIn", AttributeValue::Boolean(true));
    assert_eq!(user_context.decide(flag_key).variation_key(), "off");

    // Update user attribute, and assert new variation
    user_context.set_attribute("appVersion", AttributeValue::String("1.3.2".into()));
    assert_eq!(user_context.decide(flag_key).variation_key(), "variation_for_audience_on_app_version_1_and_up");

    // Update user attribute, and assert new variation
    user_context.set_attribute("numberOfProductsInCart", AttributeValue::Integer(3));
    assert_eq!(
        user_context.decide(flag_key).variation_key(),
        "variation_for_audience_with_more_than_1_product_in_cart"
    );

    // Update user attribute, and assert new variation
    user_context.set_attribute("currentPath", AttributeValue::String("/checkout".into()));
    assert_eq!(user_context.decide(flag_key).variation_key(), "variation_for_audience_on_checkout");

    // Update user attribute, and assert new variation
    user_context.set_attribute("isLoggedIn", AttributeValue::Boolean(false));
    assert_eq!(user_context.decide(flag_key).variation_key(), "variation_for_audience_who_are_logged_out");

    Ok(())
}