Skip to content

Commit

Permalink
components: add test command (#202)
Browse files Browse the repository at this point in the history
  • Loading branch information
CLEMENTINATOR authored Feb 5, 2025
1 parent e27b4cf commit 709dfe7
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ miette = { workspace = true, features = ["fancy"] }
openssl.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_yml.workspace = true
serde_json.workspace = true
tracing.workspace = true
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
toml.workspace = true
Expand Down
4 changes: 3 additions & 1 deletion crates/cli/src/commands/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ setup_commands! {
/// List currently pulled components
List(list),
/// Check if a component implements the required interfaces
Check(check)
Check(check),
/// Test a component
Test(test)
}

pub type Options = Command;
Expand Down
173 changes: 173 additions & 0 deletions crates/cli/src/commands/components/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
use edgee_components_runtime::config::{ComponentsConfiguration, DataCollectionComponents};
use edgee_components_runtime::context::ComponentsContext;
use std::collections::HashMap;

use edgee_components_runtime::data_collection::payload::{Event, EventType};

#[derive(Debug, clap::Parser)]
pub struct Options {
/// Comma-separated key=value pairs for settings
#[arg(long="settings", value_parser = parse_settings)]
pub settings: Option<HashMap<String, String>>,

/// Test to run
#[arg(long = "event-type", value_parser = ["page", "track", "user"])]
pub event_type: Option<String>,

// Display input
#[arg(long = "display-input", default_value = "false")]
pub display_input: bool,
}

fn parse_settings(settings_str: &str) -> Result<HashMap<String, String>, String> {
let mut settings_map = HashMap::new();

for setting in settings_str.split(',') {
let parts: Vec<&str> = setting.split('=').collect();
if parts.len() == 2 {
settings_map.insert(parts[0].to_string(), parts[1].to_string());
} else {
return Err(format!("Invalid setting: {}\nPlease use a comma-separated list of settings such as `-s 'key1=value,key2=value2'`", setting));
}
}

Ok(settings_map)
}

async fn test_data_collection_component(component_path: &str, opts: Options) -> anyhow::Result<()> {
if !std::path::Path::new(component_path).exists() {
return Err(anyhow::anyhow!("Output path not found in manifest file.",));
}

let config = ComponentsConfiguration {
data_collection: vec![DataCollectionComponents {
id: component_path.to_string(),
file: component_path.to_string(),
..Default::default()
}],
..Default::default()
};

let context = ComponentsContext::new(&config)
.map_err(|_e| anyhow::anyhow!("Something went wrong when trying to load the Wasm file. Please re-build and try again."))?;

let mut store = context.empty_store();

let instance = context
.get_data_collection_instance(component_path, &mut store)
.await?;
let component = instance.edgee_protocols_data_collection();

// events generated with demo.edgee.app
let page_event_json = r#"[{"uuid":"37009b9b-a572-4615-87c1-09e257331ecb","timestamp":"2025-02-03T15:46:39.283317613Z","type":"page","data":{"keywords":["demo","tag manager"],"title":"Page with Edgee components","url":"https://demo.edgee.app/analytics-with-edgee.html","path":"/analytics-with-edgee.html","referrer":"https://demo.edgee.dev/analytics-with-js.html"},"context":{"page":{"keywords":["demo","tag manager"],"title":"Page with Edgee components","url":"https://demo.edgee.app/analytics-with-edgee.html","path":"/analytics-with-edgee.html","referrer":"https://demo.edgee.dev/analytics-with-js.html"},"user":{"edgee_id":"6bb171d5-2284-41ee-9889-91af03b71dc5"},"client":{"ip":"127.0.0.1","locale":"en-us","accept_language":"en-US,en;q=0.9","timezone":"Europe/Paris","user_agent":"Mozilla/5.0 (X11; Linux x86_64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36","user_agent_version_list":"Not A(Brand;8|Chromium;132","user_agent_mobile":"0","os_name":"Linux","user_agent_architecture":"x86","user_agent_bitness":"64","user_agent_full_version_list":"Not A(Brand;8.0.0.0|Chromium;132.0.6834.159","user_agent_model":"","os_version":"6.12.11","screen_width":1920,"screen_height":1280,"screen_density":1.5},"session":{"session_id":"1738597536","session_count":1,"session_start":false,"first_seen":"2025-02-03T15:45:36.569004889Z","last_seen":"2025-02-03T15:46:39.278740029Z"}},"from":"edge"}]"#;
let track_event_json = r#" [{"uuid":"4cffe10b-b5fd-429e-96d2-471f0575005f","timestamp":"2025-02-03T16:06:32.809486270Z","type":"track","data":{"name":"button_click","properties":{"registered":false,"size":10,"color":"blue","category":"shoes","label":"Blue Sneakers"}},"context":{"page":{"keywords":["demo","tag manager"],"title":"Page with Edgee components","url":"https://demo.edgee.app/analytics-with-edgee.html","path":"/analytics-with-edgee.html","referrer":"https://demo.edgee.dev/"},"user":{"user_id":"123456","anonymous_id":"anon-123","edgee_id":"69659401-40cf-4ac8-8ffc-630a10fe06dc","properties":{"verified":true,"age":42,"email":"me@example.com","name":"John Doe"}},"client":{"ip":"127.0.0.1","locale":"en-us","accept_language":"en-US,en;q=0.5","timezone":"Europe/Paris","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0","screen_width":1440,"screen_height":960,"screen_density":2.0},"session":{"session_id":"1738598699","session_count":7,"session_start":false,"first_seen":"2024-12-12T16:30:03.693248190Z","last_seen":"2025-02-03T16:06:32.808844970Z"}},"from":"client","consent":"granted"}]"#;
let user_event_json = r#"[{"uuid":"eb0f001a-cd2b-42c4-9c71-7b1c2bcda445","timestamp":"2025-02-03T16:07:04.878715197Z","type":"user","data":{"user_id":"123456","anonymous_id":"anon-123","edgee_id":"69659401-40cf-4ac8-8ffc-630a10fe06dc","properties":{"age":42,"verified":true,"name":"John Doe","email":"me@example.com"}},"context":{"page":{"keywords":["demo","tag manager"],"title":"Page with Edgee components","url":"https://demo.edgee.app/analytics-with-edgee.html","path":"/analytics-with-edgee.html","referrer":"https://demo.edgee.dev/"},"user":{"user_id":"123456","anonymous_id":"anon-123","edgee_id":"69659401-40cf-4ac8-8ffc-630a10fe06dc","properties":{"email":"me@example.com","age":42,"name":"John Doe","verified":true}},"client":{"ip":"127.0.0.1","locale":"en-us","accept_language":"en-US,en;q=0.5","timezone":"Europe/Paris","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0","screen_width":1440,"screen_height":960,"screen_density":2.0},"session":{"session_id":"1738598699","session_count":7,"session_start":false,"first_seen":"2024-12-12T16:30:03.693248190Z","last_seen":"2025-02-03T16:07:04.878137016Z"}},"from":"client","consent":"granted"}]"#;

// setting management
let mut settings_map = HashMap::new();

// insert user provided settings
if let Some(parsed_settings) = opts.settings {
for (key, value) in parsed_settings {
settings_map.insert(key, value);
}
}
// TODO generate settings from the missing settings in the component manifest
let settings = settings_map.clone().into_iter().collect();

// select events to run
let mut events = vec![];
match opts.event_type {
None => {
events.push(serde_json::from_str::<Vec<Event>>(page_event_json).unwrap()[0].clone());
events.push(serde_json::from_str::<Vec<Event>>(track_event_json).unwrap()[0].clone());
events.push(serde_json::from_str::<Vec<Event>>(user_event_json).unwrap()[0].clone());
}
Some(event_type) => match event_type.as_str() {
"page" => {
events
.push(serde_json::from_str::<Vec<Event>>(page_event_json).unwrap()[0].clone());
}
"track" => {
events
.push(serde_json::from_str::<Vec<Event>>(track_event_json).unwrap()[0].clone());
}
"user" => {
events
.push(serde_json::from_str::<Vec<Event>>(user_event_json).unwrap()[0].clone());
}
_ => {
return Err(anyhow::anyhow!("Invalid event type"));
}
},
}

if opts.display_input {
println!("Settings: {:#?}", settings_map);
}
for event in events {
println!("---------------------------------------------------");
let request = match event.event_type {
EventType::Page => {
println!("Calling page");
component
.call_page(&mut store, &event.clone().into(), &settings)
.await
}
EventType::Track => {
println!("Calling track");
component
.call_track(&mut store, &event.clone().into(), &settings)
.await
}
EventType::User => {
println!("Calling user");
component
.call_user(&mut store, &event.clone().into(), &settings)
.await
}
};

let request = match request {
Ok(Ok(request)) => request,
Err(e) => return Err(anyhow::anyhow!("Failed to call component: {}", e)),
_ => unreachable!(),
};

if opts.display_input {
println!("Input Event: {}", serde_json::to_string_pretty(&event)?);
}

println!("EdgeeRequest object:");
println!("Method: {:#?}", request.method);
println!("URL: {:#?}", request.url);
println!("Headers: {:#?}", request.headers);
if let Ok(pretty_json) = serde_json::from_str::<serde_json::Value>(&request.body) {
println!("Body: {}", serde_json::to_string_pretty(&pretty_json)?);
} else {
println!("Body: {:#?}", request.body);
}
}

Ok(())
}
pub async fn run(opts: Options) -> anyhow::Result<()> {
use crate::components::manifest::{self, Manifest};

let manifest_path =
manifest::find_manifest_path().ok_or_else(|| anyhow::anyhow!("Manifest not found"))?;

let manifest = Manifest::load(&manifest_path)?;
let component_path = manifest
.package
.build
.output_path
.into_os_string()
.into_string()
.map_err(|_| anyhow::anyhow!("Invalid path"))?;

// TODO: dont assume that it is a data collection component, add type in manifest
test_data_collection_component(&component_path, opts).await?;

Ok(())
}

0 comments on commit 709dfe7

Please sign in to comment.