#![allow(clippy::result_large_err)]
use std::collections::HashMap;
use qlexpress::exception::error_codes;
use qlexpress::ql_options::QLOptions;
use qlexpress::Express4Runner;
fn opts() -> QLOptions {
QLOptions::builder().build()
}
fn err_code(script: &str) -> String {
let runner = Express4Runner::new();
let e = runner
.execute(script, HashMap::new(), &opts())
.expect_err("should error");
e.error_code().to_string()
}
fn ok(script: &str) {
let runner = Express4Runner::new();
runner
.execute(script, HashMap::new(), &opts())
.expect("should ok");
}
#[test]
fn arithmetic_error_division_by_zero() {
assert_eq!(err_code("1 / 0"), error_codes::INVALID_ARITHMETIC);
}
#[test]
fn arithmetic_error_modulo_by_zero() {
let e = err_code("1 % 0");
assert!(
e == error_codes::INVALID_ARITHMETIC || e == error_codes::EXECUTE_OPERATOR_EXCEPTION,
"expected arithmetic error, got {e}"
);
}
#[test]
fn invalid_binary_operands() {
let code = err_code("undefinedVar + 1");
assert!(code == error_codes::INVALID_BINARY_OPERAND);
}
#[test]
fn syntax_error_unterminated_string() {
assert_eq!(err_code("'hello"), error_codes::SYNTAX_ERROR);
}
#[test]
fn syntax_error_unterminated_string_double() {
assert_eq!(err_code("\"hello"), error_codes::SYNTAX_ERROR);
}
#[test]
fn syntax_error_import_not_at_beginning() {
assert_eq!(err_code("a = 1\nimport b.c"), error_codes::SYNTAX_ERROR);
}
#[test]
fn null_field_access() {
assert_eq!(
err_code("m = null\nm.field"),
error_codes::NULL_FIELD_ACCESS
);
}
#[test]
fn index_out_of_bound() {
assert_eq!(err_code("[1, 2, 3][5]"), error_codes::INDEX_OUT_BOUND);
}
#[test]
fn invalid_index_type() {
assert_eq!(err_code("[1, 2, 3]['a']"), error_codes::INVALID_INDEX);
}
#[test]
fn method_not_found() {
assert_eq!(
err_code("'hello'.nonExistentMethod()"),
error_codes::METHOD_NOT_FOUND
);
}
#[test]
fn class_not_found() {
let runner = Express4Runner::new();
let r = runner.execute("NonExistentClass", HashMap::new(), &opts());
match r {
Ok(v) => {
let _ = v;
} Err(e) => assert_eq!(e.error_code(), error_codes::CLASS_NOT_FOUND),
}
}
#[test]
fn script_timeout() {
use qlexpress::init_options::InitOptions;
let runner = Express4Runner::with_init_options(
InitOptions::builder()
.security_strategy(
qlexpress::security::ql_security_strategy::QLSecurityStrategy::open(),
)
.build(),
);
let opts = QLOptions::builder().timeout_millis(50).build();
let e = runner
.execute(
"int i = 0;\nwhile (true) { i = i + 1; }",
HashMap::new(),
&opts,
)
.expect_err("should timeout");
assert_eq!(e.error_code(), error_codes::SCRIPT_TIME_OUT);
}
#[test]
fn while_condition_non_bool() {
assert_eq!(
err_code("while (1) {}"),
error_codes::WHILE_CONDITION_BOOL_REQUIRED
);
}
#[test]
fn no_suitable_constructor() {
let runner = Express4Runner::new();
let r = runner.execute("new NonExistent(1)", HashMap::new(), &opts());
match r {
Ok(v) => {
let _ = v;
}
Err(e) => {
let code = e.error_code();
assert!(
code == error_codes::NO_SUITABLE_CONSTRUCTOR
|| code == error_codes::CLASS_NOT_FOUND,
"expected constructor or class error, got {code}"
);
}
}
}
#[test]
fn operator_not_allowed() {
use qlexpress::check_options::CheckOptions;
use qlexpress::operator::operator_check_strategy::OperatorCheckStrategy;
let runner = Express4Runner::new();
let opts = CheckOptions::builder()
.operator_check_strategy(OperatorCheckStrategy::Blacklist(
["="].into_iter().map(String::from).collect(),
))
.build();
let e = runner.check("a = 1", &opts).expect_err("should err");
assert_eq!(e.error_code(), error_codes::OPERATOR_NOT_ALLOWED);
}
#[test]
fn basic_arithmetic_ok() {
ok("1 + 2 * 3");
ok("10 % 3");
ok("(1 + 2) * 3");
}
#[test]
fn basic_comparison_ok() {
ok("1 < 2");
ok("1 == 1");
ok("1 != 2");
ok("true && false");
}
#[test]
fn control_flow_ok() {
ok("if (1 == 1) { 1 } else { 2 }");
ok("int i = 0;\nwhile (i < 3) { i = i + 1; }\ni");
}