Files
upstream-mirror da934b693c
Some checks failed
Build / ubuntu-latest (push) Failing after 5s
Build / Android (push) Failing after 2s
Build / Code coverage (push) Failing after 2s
Lint / format (push) Failing after 3s
Lint / clippy (push) Failing after 3s
Build / macOS-latest (push) Has been cancelled
Build / windows-latest (push) Has been cancelled
snapshot of https://github.com/ActivityWatch/aw-server-rust.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 00:19:17 +05:00

779 lines
28 KiB
Rust

extern crate chrono;
extern crate serde_json;
extern crate aw_query;
// TODO: Move me to an appropriate place
#[macro_export]
macro_rules! json_map {
{ $( $key:literal : $value:expr),* } => {{
use serde_json::Value;
use serde_json::map::Map;
#[allow(unused_mut)]
let mut map : Map<String, Value> = Map::new();
$(
map.insert( $key.to_string(), json!($value) );
)*
map
}};
}
#[cfg(test)]
mod query_tests {
use chrono::Duration;
use serde_json::json;
use std::convert::TryFrom;
use aw_query::DataType;
use aw_query::QueryError;
use aw_datastore::Datastore;
use aw_models::Bucket;
use aw_models::BucketMetadata;
use aw_models::Event;
use aw_models::TimeInterval;
static TIME_INTERVAL: &str = "1980-01-01T00:00:00Z/2080-01-02T00:00:00Z";
static BUCKET_ID: &str = "testid";
fn setup_datastore_empty() -> Datastore {
Datastore::new_in_memory(false)
}
fn setup_datastore_with_bucket() -> Datastore {
let ds = setup_datastore_empty();
// Create bucket
let bucket = Bucket {
bid: None,
id: BUCKET_ID.to_string(),
_type: "testtype".to_string(),
client: "testclient".to_string(),
hostname: "testhost".to_string(),
created: Some(chrono::Utc::now()),
data: json_map! {},
metadata: BucketMetadata::default(),
events: None,
last_updated: None,
};
ds.create_bucket(&bucket).unwrap();
ds
}
fn setup_datastore_populated() -> Datastore {
let ds = setup_datastore_with_bucket();
// Insert events
let e1 = Event {
id: None,
timestamp: chrono::Utc::now(),
duration: Duration::seconds(0),
data: json_map! {"key": json!("value")},
};
let mut e2 = e1.clone();
e2.timestamp = chrono::Utc::now();
let mut e_replace = e2.clone();
e_replace.data = json_map! {"key": json!("value2")};
e_replace.duration = Duration::seconds(2);
let event_list = vec![e1, e2];
ds.insert_events(BUCKET_ID, &event_list).unwrap();
ds
}
macro_rules! assert_err_type {
($v:expr, $p:pat) => {
match $v {
Ok(_) => panic!("Expected an error, got {:?}", $v),
Err(e) => match e {
$p => (),
_ => panic!("Expected an error of another type, got {:?}", e),
},
}
};
}
#[test]
fn test_bool() {
let ds = setup_datastore_empty();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
let code = String::from("True;False;a=True;return True;");
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Bool(b) => assert!(b),
ref data => panic!("Wrong datatype, {data:?}"),
};
}
#[test]
fn test_number() {
let ds = setup_datastore_empty();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
let code = String::from("1;1.;return 1.1;");
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Number(n) => assert_eq!(n, 1.1),
ref data => panic!("Wrong datatype, {data:?}"),
};
}
#[test]
fn test_equals() {
let ds = setup_datastore_empty();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
// number comparison true
let code = String::from("return 1==1;");
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Bool(b) => assert!(b),
ref data => panic!("Wrong datatype, {data:?}"),
};
// number comparison false
let code = String::from("return 2==1;");
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Bool(b) => assert!(!b),
ref data => panic!("Wrong datatype, {data:?}"),
};
// string comparison true
let code = String::from(r#"return "a"=="a";"#);
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Bool(b) => assert!(b),
ref data => panic!("Wrong datatype, {data:?}"),
};
// string comparison false
let code = String::from(r#"return "a"=="b";"#);
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Bool(b) => assert!(!b),
ref data => panic!("Wrong datatype, {data:?}"),
};
// bool comparison true
let code = String::from("return True==True;");
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Bool(b) => assert!(b),
ref data => panic!("Wrong datatype, {data:?}"),
};
// bool comparison false
let code = String::from("return False==True;");
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Bool(b) => assert!(!b),
ref data => panic!("Wrong datatype, {data:?}"),
};
// different types comparison (should raise an error)
let code = String::from("return True==1;");
let res = aw_query::query(&code, &interval, &ds);
assert_err_type!(res, QueryError::InvalidType(_));
// None comparison, consistent with the PartialEq impl on DataType.
// print() returns None, so this compares two None values.
let code = String::from(r#"a=print("a"); b=print("b"); return a==b;"#);
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Bool(b) => assert!(b),
ref data => panic!("Wrong datatype, {data:?}"),
};
}
#[test]
fn test_return() {
let ds = setup_datastore_empty();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
let code = String::from("return 1;");
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Number(n) => assert_eq!(n, 1.0),
ref data => panic!("Wrong datatype, {data:?}"),
};
// Test with old RETURN method
let code = String::from("RETURN=1;");
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Number(n) => assert_eq!(n, 1.0),
ref data => panic!("Wrong datatype, {data:?}"),
};
let code = String::from("return 1+1;");
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Number(n) => assert_eq!(n, 2.0),
ref data => panic!("Wrong datatype, {data:?}"),
};
}
#[test]
fn test_if() {
let ds = setup_datastore_empty();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
// Test hardcoded True
let code = String::from(
"
n=1;
if True { n=2; }
return n;",
);
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Number(n) => assert_eq!(n, 2.0),
ref data => panic!("Wrong datatype, {data:?}"),
};
// Test hardcoded False
let code = String::from(
"
n=1;
if False { n=2; }
return n;",
);
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Number(n) => assert_eq!(n, 1.0),
ref data => panic!("Wrong datatype, {data:?}"),
};
// Test expression True
let code = String::from(
"
a=True; n=1;
if a { n=2; }
return n;",
);
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Number(n) => assert_eq!(n, 2.0),
ref data => panic!("Wrong datatype, {data:?}"),
};
// Test expression False
let code = String::from(
"
a=False; n=1;
if a { n=2; }
return n;",
);
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Number(n) => assert_eq!(n, 1.0),
ref data => panic!("Wrong datatype, {data:?}"),
};
// Test if else
let code = String::from(
"
a=False; n=1;
if a { }
else { n=3; }
return n;",
);
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Number(n) => assert_eq!(n, 3.0),
ref data => panic!("Wrong datatype, {data:?}"),
};
// Test if else if
let code = String::from(
"
a=False; b=True; n=1;
if a { n=2; }
elif b { n=3; }
return n;",
);
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Number(n) => assert_eq!(n, 3.0),
ref data => panic!("Wrong datatype, {data:?}"),
};
// Test if else if else
let code = String::from(
"
a=False; b=True; n=1;
if a { n=2; }
elif a { n=3; }
else { n=4; }
return n;",
);
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Number(n) => assert_eq!(n, 4.0),
ref data => panic!("Wrong datatype, {data:?}"),
};
// Test if inside if
let code = String::from(
"
a=True; n=1;
if a { if a { n = 2; } }
return n;",
);
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::Number(n) => assert_eq!(n, 2.0),
ref data => panic!("Wrong datatype, {data:?}"),
};
}
#[test]
fn test_function() {
let ds = setup_datastore_empty();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
let code = String::from("return print(1);");
aw_query::query(&code, &interval, &ds).unwrap();
let code = String::from("return print(1, 2);");
aw_query::query(&code, &interval, &ds).unwrap();
let code = String::from("return no_such_function(1);");
match aw_query::query(&code, &interval, &ds) {
Ok(ok) => panic!("Expected QueryError, got {ok:?}"),
Err(e) => match e {
QueryError::VariableNotDefined(qe) => assert_eq!(qe, "no_such_function"),
qe => panic!("Expected QueryError::VariableNotDefined, got {qe:?}"),
},
}
let code = String::from("invalid_type=1; return invalid_type(1);");
match aw_query::query(&code, &interval, &ds) {
Ok(ok) => panic!("Expected QueryError, got {ok:?}"),
Err(e) => match e {
QueryError::InvalidType(qe) => assert_eq!(qe, "invalid_type"),
qe => panic!("Expected QueryError::VariableNotDefined, got {qe:?}"),
},
}
}
#[test]
fn test_all_functions() {
let ds = setup_datastore_populated();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
let code = String::from("return query_bucket(\"testid\");");
aw_query::query(&code, &interval, &ds).unwrap();
let code = format!(
r#"
events = query_bucket(find_bucket("{}", "testhost"));
events = flood(events);
events = sort_by_duration(events);
events = limit_events(events, 10000);
events = sort_by_timestamp(events);
events = concat(events, query_bucket("{}"));
events = categorize(events, [[["test"], {{ "type": "regex", "regex": "value$" }}], [["test", "testing"], {{ "type": "regex", "regex": "value$" }}]]);
events = tag(events, [["testtag", {{ "type": "regex", "regex": "test$" }}], ["another testtag", {{ "type": "regex", "regex": "test-pat$" }}]]);
total_duration = sum_durations(events);
bucketnames = query_bucket_names();
print("test", "test2");
url_events = split_url_events (events);
filtered_events = filter_period_intersect(events, events);
filtered_events = filter_keyvals(events, "$category", [["Uncategorized"]]);
filtered_events = filter_keyvals_regex(events, "key", "regex");
chunked_events = chunk_events_by_key(events, "key");
merged_events = merge_events_by_keys(events, ["key"]);
return merged_events;"#,
"testid", "testid"
);
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::List(l) => l,
ref data => panic!("Wrong datatype, {data:?}"),
};
// TODO: assert_eq result
}
#[test]
fn test_categorize() {
let ds = setup_datastore_populated();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
let code = format!(
r#"
events = query_bucket("{}");
events = categorize(events, [[["Test", "Subtest"], {{ "type": "regex", "regex": "^value$" }}]]);
return events;"#,
"testid"
);
let result: DataType = aw_query::query(&code, &interval, &ds).unwrap();
let events: Vec<Event> = Vec::try_from(&result).unwrap();
let event = events.first().unwrap();
let cats = event.data.get("$category").unwrap();
assert_eq!(cats, &serde_json::json!(vec!["Test", "Subtest"]));
}
#[test]
fn test_categorize_priority_overrides_depth() {
let ds = setup_datastore_populated();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
// Without priority, the deeper rule wins (existing contract).
let code = format!(
r#"
events = query_bucket("{}");
events = categorize(events, [[["A"], {{ "type": "regex", "regex": "^value$" }}], [["B", "B1"], {{ "type": "regex", "regex": "^value$" }}]]);
return events;"#,
"testid"
);
let result: DataType = aw_query::query(&code, &interval, &ds).unwrap();
let events: Vec<Event> = Vec::try_from(&result).unwrap();
assert_eq!(
events.first().unwrap().data.get("$category").unwrap(),
&serde_json::json!(vec!["B", "B1"])
);
// With an explicit priority on A, organizational depth no longer wins.
let code = format!(
r#"
events = query_bucket("{}");
events = categorize(events, [[["A"], {{ "type": "regex", "regex": "^value$", "priority": 25 }}], [["B", "B1"], {{ "type": "regex", "regex": "^value$" }}]]);
return events;"#,
"testid"
);
let result: DataType = aw_query::query(&code, &interval, &ds).unwrap();
let events: Vec<Event> = Vec::try_from(&result).unwrap();
assert_eq!(
events.first().unwrap().data.get("$category").unwrap(),
&serde_json::json!(vec!["A"])
);
// `weight` is accepted as an alias for `priority`.
let code = format!(
r#"
events = query_bucket("{}");
events = categorize(events, [[["A"], {{ "type": "regex", "regex": "^value$", "weight": 25 }}], [["B", "B1"], {{ "type": "regex", "regex": "^value$" }}]]);
return events;"#,
"testid"
);
let result: DataType = aw_query::query(&code, &interval, &ds).unwrap();
let events: Vec<Event> = Vec::try_from(&result).unwrap();
assert_eq!(
events.first().unwrap().data.get("$category").unwrap(),
&serde_json::json!(vec!["A"])
);
}
#[test]
fn test_tag() {
let ds = setup_datastore_populated();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
let code = format!(
r#"
events = query_bucket("{}");
events = tag(events, [["testtag", {{ "type": "regex", "regex": "value$" }}], ["another testtag", {{ "type": "regex", "regex": "value$" }}]]);
return events;"#,
"testid"
);
let result: DataType = aw_query::query(&code, &interval, &ds).unwrap();
let events: Vec<Event> = Vec::try_from(&result).unwrap();
let event = events.first().unwrap();
let tags = event.data.get("$tags").unwrap().as_array().unwrap();
assert_eq!(tags.len(), 2);
}
#[test]
fn test_tag_select_keys() {
let ds = setup_datastore_with_bucket();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
let event = Event {
id: None,
timestamp: chrono::Utc::now(),
duration: Duration::seconds(0),
data: json_map! {
"app": json!("terminal"),
"title": json!("just a test"),
"pid": json!(123)
},
};
ds.insert_events(BUCKET_ID, &[event]).unwrap();
let code = format!(
r#"
events = query_bucket("{}");
events = tag(events, [["title-match", {{ "type": "regex", "regex": "test$", "select_keys": ["title"] }}], ["app-match", {{ "type": "regex", "regex": "test$", "select_keys": ["app"] }}], ["pid-match", {{ "type": "regex", "regex": "123", "select_keys": ["pid"] }}], ["missing-match", {{ "type": "regex", "regex": "test", "select_keys": ["missing"] }}]]);
return events;"#,
BUCKET_ID
);
let result: DataType = aw_query::query(&code, &interval, &ds).unwrap();
let events: Vec<Event> = Vec::try_from(&result).unwrap();
let event = events.first().unwrap();
let tags = event.data.get("$tags").unwrap();
assert_eq!(tags, &serde_json::json!(vec!["title-match"]));
}
#[test]
fn test_rule_parsing() {
let ds = setup_datastore_populated();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
// Test rule where rule is not a dict
let code = r#"
events = [];
events = tag(events, ["test", false]);
return events;"#;
let res = aw_query::query(code, &interval, &ds);
assert_err_type!(res, QueryError::InvalidFunctionParameters(_));
// Test rule without type
let code = r#"
events = [];
events = tag(events, [["testtag", { }]]);
return events;"#;
let res = aw_query::query(code, &interval, &ds);
assert_err_type!(res, QueryError::InvalidFunctionParameters(_));
// Test invalid rule type
let code = r#"
events = [];
events = tag(events, [["testtag", { "type": false }]]);
return events;"#;
let res = aw_query::query(code, &interval, &ds);
assert_err_type!(res, QueryError::InvalidFunctionParameters(_));
// Test invalid rule name
let code = r#"
events = [];
events = tag(events, [["testtag", { "type": "rgex" }]]);
return events;"#;
let res = aw_query::query(code, &interval, &ds);
assert_err_type!(res, QueryError::InvalidFunctionParameters(_));
// Test "none" rule type
let code = r#"
events = [];
events = tag(events, [["testtag", { "type": "none" }]]);
return events;"#;
aw_query::query(code, &interval, &ds).unwrap();
// Test regex rule where regex field has wrong type
let code = r#"
events = [];
events = tag(events, [["testtag", { "type": "regex", "regex": true }]]);
return events;"#;
let res = aw_query::query(code, &interval, &ds);
assert_err_type!(res, QueryError::InvalidFunctionParameters(_));
// Test regex rule where regex field is not set
let code = r#"
events = [];
events = tag(events, [["testtag", { "type": "regex" }]]);
return events;"#;
let res = aw_query::query(code, &interval, &ds);
assert_err_type!(res, QueryError::InvalidFunctionParameters(_));
// Test regex rule with ignore_case field
let code = r#"
events = [];
events = tag(events, [["testtag", { "type": "regex", "regex": "test", "ignore_case": false }]]);
return events;"#;
aw_query::query(code, &interval, &ds).unwrap();
// Test regex rule with valid select_keys field
let code = r#"
events = [];
events = tag(events, [["testtag", { "type": "regex", "regex": "test", "select_keys": ["key"] }]]);
return events;"#;
aw_query::query(code, &interval, &ds).unwrap();
// Test regex rule where ignore_case field is of invalid type
let code = r#"
events = [];
events = tag(events, [["testtag", { "type": "regex", "regex": "test", "ignore_case": "" }]]);
return events;"#;
let res = aw_query::query(code, &interval, &ds);
assert_err_type!(res, QueryError::InvalidFunctionParameters(_));
// Test regex rule where select_keys field is of invalid type
let code = r#"
events = [];
events = tag(events, [["testtag", { "type": "regex", "regex": "test", "select_keys": "key" }]]);
return events;"#;
let res = aw_query::query(code, &interval, &ds);
assert_err_type!(res, QueryError::InvalidFunctionParameters(_));
// Test regex rule where select_keys contains non-string values
let code = r#"
events = [];
events = tag(events, [["testtag", { "type": "regex", "regex": "test", "select_keys": ["key", false] }]]);
return events;"#;
let res = aw_query::query(code, &interval, &ds);
assert_err_type!(res, QueryError::InvalidFunctionParameters(_));
// Test regex rule where uncompilable regex is supplied
let code = r#"
events = [];
events = tag(events, [["testtag", { "type": "regex", "regex": "!#¤%&/(=" }]]);
return events;"#;
let res = aw_query::query(code, &interval, &ds);
assert_err_type!(res, QueryError::RegexCompileError(_));
// Test categorize rule with non-integer priority
let code = r#"
events = [];
events = categorize(events, [[["test"], { "type": "regex", "regex": "test", "priority": 1.5 }]]);
return events;"#;
let res = aw_query::query(code, &interval, &ds);
assert_err_type!(res, QueryError::InvalidFunctionParameters(_));
// Test categorize rule with non-numeric priority
let code = r#"
events = [];
events = categorize(events, [[["test"], { "type": "regex", "regex": "test", "priority": "high" }]]);
return events;"#;
let res = aw_query::query(code, &interval, &ds);
assert_err_type!(res, QueryError::InvalidFunctionParameters(_));
}
#[test]
fn test_string() {
let ds = setup_datastore_empty();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
let code = String::from("return \"test \\\" with escaped quote\";");
match aw_query::query(&code, &interval, &ds).unwrap() {
aw_query::DataType::String(s) => assert_eq!(s, "test \" with escaped quote"),
_ => panic!("Wrong datatype"),
}
}
#[test]
fn test_list() {
let ds = setup_datastore_empty();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
let code = String::from("return [];");
aw_query::query(&code, &interval, &ds).unwrap();
let code = String::from("return [1];");
aw_query::query(&code, &interval, &ds).unwrap();
let code = String::from("return [1+1];");
aw_query::query(&code, &interval, &ds).unwrap();
let code = String::from("return [1,1];");
aw_query::query(&code, &interval, &ds).unwrap();
let code = String::from("return [1,1+2];");
aw_query::query(&code, &interval, &ds).unwrap();
let code = String::from("return [1,1+1,1+2+3,4/3,[1+2]];");
aw_query::query(&code, &interval, &ds).unwrap();
}
#[test]
fn test_comment() {
let ds = setup_datastore_empty();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
let code = String::from("return 1;# testing 123");
aw_query::query(&code, &interval, &ds).unwrap();
}
#[test]
fn test_dict() {
let ds = setup_datastore_empty();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
let code = String::from("return {};");
aw_query::query(&code, &interval, &ds).unwrap();
let code = String::from("return {\"test\": 2};");
aw_query::query(&code, &interval, &ds).unwrap();
let code = String::from("return {\"test\": 2, \"test2\": \"teststr\"};");
aw_query::query(&code, &interval, &ds).unwrap();
let code = String::from("return {\"test\": {\"test\": \"test\"}};");
aw_query::query(&code, &interval, &ds).unwrap();
}
#[test]
fn test_concat() {
let ds = setup_datastore_empty();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
// Append lists
let code = String::from("return [1]+[2];");
let res = aw_query::query(&code, &interval, &ds).unwrap();
let v = vec![DataType::Number(1.0), DataType::Number(2.0)];
assert_eq!(res, DataType::List(v));
// Append strings
let code = String::from(r#"return "a"+"b";"#);
let res = aw_query::query(&code, &interval, &ds).unwrap();
assert_eq!(res, DataType::String("ab".to_string()));
}
#[test]
fn test_contains() {
let ds = setup_datastore_empty();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
// test list true
let code = String::from(r#"a = ["b", "a"]; return contains(a, "a");"#);
let res = aw_query::query(&code, &interval, &ds).unwrap();
assert_eq!(res, DataType::Bool(true));
// test list false
let code = String::from(r#"a = ["b", "a"]; return contains(a, "c");"#);
let res = aw_query::query(&code, &interval, &ds).unwrap();
assert_eq!(res, DataType::Bool(false));
// test dict true
let code = String::from(r#"a = {"a": 1}; return contains(a, "a");"#);
let res = aw_query::query(&code, &interval, &ds).unwrap();
assert_eq!(res, DataType::Bool(true));
// test dict false
let code = String::from(r#"a = {"b": 1}; return contains(a, "a");"#);
let res = aw_query::query(&code, &interval, &ds).unwrap();
assert_eq!(res, DataType::Bool(false));
}
#[test]
fn test_math() {
let ds = setup_datastore_empty();
let interval = TimeInterval::new_from_string(TIME_INTERVAL).unwrap();
let code = String::from("return 1+1;");
match aw_query::query(&code, &interval, &ds).unwrap() {
DataType::Number(n) => assert_eq!(n, 2.0),
num => panic!("Expected number, got {num:?}"),
};
let code = String::from("return 1-1;");
match aw_query::query(&code, &interval, &ds).unwrap() {
DataType::Number(n) => assert_eq!(n, 0.0),
num => panic!("Expected number, got {num:?}"),
};
let code = String::from("return 3*5;");
match aw_query::query(&code, &interval, &ds).unwrap() {
DataType::Number(n) => assert_eq!(n, 15.0),
num => panic!("Expected number, got {num:?}"),
};
let code = String::from("return 4/2;");
match aw_query::query(&code, &interval, &ds).unwrap() {
DataType::Number(n) => assert_eq!(n, 2.0),
num => panic!("Expected number, got {num:?}"),
};
let code = String::from("return 1/0;");
let res = aw_query::query(&code, &interval, &ds);
assert_err_type!(res, QueryError::MathError(_));
let code = String::from("return 2.5%1;");
match aw_query::query(&code, &interval, &ds).unwrap() {
DataType::Number(n) => assert_eq!(n, 0.5),
num => panic!("Expected number, got {num:?}"),
};
let code = String::from("return 1+1+0+1;");
match aw_query::query(&code, &interval, &ds).unwrap() {
DataType::Number(n) => assert_eq!(n, 3.0),
num => panic!("Expected number, got {num:?}"),
};
}
}