Skip to content

Commit b85f7a4

Browse files
committed
Tests
1 parent 3ac3edc commit b85f7a4

File tree

2 files changed

+1304
-206
lines changed

2 files changed

+1304
-206
lines changed

primitives/src/targeting/eval.rs

Lines changed: 60 additions & 206 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ pub type Map = serde_json::value::Map<String, SerdeValue>;
1212

1313
use super::{Input, Output};
1414

15+
#[cfg(test)]
16+
#[path = "eval_test.rs"]
17+
mod test;
18+
1519
#[derive(Debug, Eq, PartialEq)]
1620
pub enum Error {
1721
TypeError,
@@ -174,6 +178,50 @@ impl From<Value> for Rule {
174178
}
175179

176180
impl Function {
181+
pub fn new_muldiv(
182+
value: impl Into<Rule>,
183+
multiplier: impl Into<Rule>,
184+
divisor: impl Into<Rule>,
185+
) -> Self {
186+
Self::MulDiv(
187+
Box::new(value.into()),
188+
Box::new(multiplier.into()),
189+
Box::new(divisor.into()),
190+
)
191+
}
192+
pub fn new_div(lhs: impl Into<Rule>, rhs: impl Into<Rule>) -> Self {
193+
Self::Div(Box::new(lhs.into()), Box::new(rhs.into()))
194+
}
195+
pub fn new_mul(lhs: impl Into<Rule>, rhs: impl Into<Rule>) -> Self {
196+
Self::Mul(Box::new(lhs.into()), Box::new(rhs.into()))
197+
}
198+
pub fn new_add(lhs: impl Into<Rule>, rhs: impl Into<Rule>) -> Self {
199+
Self::Add(Box::new(lhs.into()), Box::new(rhs.into()))
200+
}
201+
pub fn new_sub(lhs: impl Into<Rule>, rhs: impl Into<Rule>) -> Self {
202+
Self::Sub(Box::new(lhs.into()), Box::new(rhs.into()))
203+
}
204+
pub fn new_mod(lhs: impl Into<Rule>, rhs: impl Into<Rule>) -> Self {
205+
Self::Mod(Box::new(lhs.into()), Box::new(rhs.into()))
206+
}
207+
pub fn new_min(lhs: impl Into<Rule>, rhs: impl Into<Rule>) -> Self {
208+
Self::Min(Box::new(lhs.into()), Box::new(rhs.into()))
209+
}
210+
pub fn new_max(lhs: impl Into<Rule>, rhs: impl Into<Rule>) -> Self {
211+
Self::Max(Box::new(lhs.into()), Box::new(rhs.into()))
212+
}
213+
pub fn new_lt(lhs: impl Into<Rule>, rhs: impl Into<Rule>) -> Self {
214+
Self::Lt(Box::new(lhs.into()), Box::new(rhs.into()))
215+
}
216+
pub fn new_lte(lhs: impl Into<Rule>, rhs: impl Into<Rule>) -> Self {
217+
Self::Lte(Box::new(lhs.into()), Box::new(rhs.into()))
218+
}
219+
pub fn new_gt(lhs: impl Into<Rule>, rhs: impl Into<Rule>) -> Self {
220+
Self::Gt(Box::new(lhs.into()), Box::new(rhs.into()))
221+
}
222+
pub fn new_gte(lhs: impl Into<Rule>, rhs: impl Into<Rule>) -> Self {
223+
Self::Gte(Box::new(lhs.into()), Box::new(rhs.into()))
224+
}
177225
pub fn new_if(condition: impl Into<Rule>, then: impl Into<Rule>) -> Self {
178226
Self::If(Box::new(condition.into()), Box::new(then.into()))
179227
}
@@ -234,6 +282,14 @@ impl Function {
234282
)
235283
}
236284

285+
pub fn new_eq(lhs: impl Into<Rule>, rhs: impl Into<Rule>) -> Self {
286+
Self::Eq(Box::new(lhs.into()), Box::new(rhs.into()))
287+
}
288+
289+
pub fn new_neq(lhs: impl Into<Rule>, rhs: impl Into<Rule>) -> Self {
290+
Self::Neq(Box::new(lhs.into()), Box::new(rhs.into()))
291+
}
292+
237293
pub fn new_split(string: impl Into<Rule>, separator: impl Into<Rule>) -> Self {
238294
Self::Split(Box::new(string.into()), Box::new(separator.into()))
239295
}
@@ -254,6 +310,10 @@ impl Function {
254310
Self::OnlyShowIf(Box::new(condition.into()))
255311
}
256312

313+
pub fn new_do(rule: impl Into<Rule>) -> Self {
314+
Self::Do(Box::new(rule.into()))
315+
}
316+
257317
pub fn new_get(key: &str) -> Self {
258318
Self::Get(key.to_string())
259319
}
@@ -1019,209 +1079,3 @@ fn math_operator(lhs: Number, rhs: Number, ops: MathOperator) -> Result<Number,
10191079
},
10201080
}
10211081
}
1022-
1023-
#[cfg(test)]
1024-
mod test {
1025-
use super::*;
1026-
use crate::targeting::AdSlot;
1027-
1028-
#[test]
1029-
fn deserialize_intersects_with_get_rule() {
1030-
let json = r#"{"intersects": [{ "get": "adSlot.categories" }, ["News", "Bitcoin"]]}"#;
1031-
1032-
let parsed_rule = serde_json::from_str::<Rule>(json).expect("Should deserialize");
1033-
1034-
let expected = Rule::Function(Function::new_intersects(
1035-
Rule::Function(Function::new_get("adSlot.categories")),
1036-
Rule::Value(Value::Array(vec![
1037-
Value::new_string("News"),
1038-
Value::new_string("Bitcoin"),
1039-
])),
1040-
));
1041-
1042-
assert_eq!(expected, parsed_rule)
1043-
}
1044-
1045-
/// ```json
1046-
/// {
1047-
/// "intersects": [
1048-
/// {
1049-
/// "get": "adSlot.categories"
1050-
/// },
1051-
/// [
1052-
/// "News",
1053-
/// "Bitcoin"
1054-
/// ]
1055-
/// ]
1056-
/// }
1057-
/// ```
1058-
#[test]
1059-
fn test_intersects_eval() {
1060-
let mut input = Input::default();
1061-
input.ad_slot = Some(AdSlot {
1062-
categories: vec!["Bitcoin".to_string(), "Ethereum".to_string()],
1063-
hostname: Default::default(),
1064-
alexa_rank: 0.0,
1065-
});
1066-
1067-
let mut output = Output {
1068-
show: true,
1069-
boost: 1.0,
1070-
price: Default::default(),
1071-
};
1072-
1073-
let categories = vec![Value::new_string("News"), Value::new_string("Bitcoin")];
1074-
1075-
let rules = Rule::Function(Function::new_intersects(
1076-
Function::new_get("adSlot.categories"),
1077-
Value::Array(categories),
1078-
));
1079-
1080-
let result = rules.eval(&input, &mut output).expect("Should eval rules");
1081-
1082-
assert_eq!(
1083-
Value::Bool(true),
1084-
result.expect("Should return Non-NULL result!")
1085-
);
1086-
1087-
let mut input = Input::default();
1088-
input.ad_slot = Some(AdSlot {
1089-
categories: vec!["Advertisement".to_string(), "Programming".to_string()],
1090-
hostname: Default::default(),
1091-
alexa_rank: 0.0,
1092-
});
1093-
1094-
let result = rules.eval(&input, &mut output).expect("Should eval rules");
1095-
1096-
assert_eq!(
1097-
Value::Bool(false),
1098-
result.expect("Should return Non-NULL result!")
1099-
);
1100-
}
1101-
1102-
#[test]
1103-
fn test_and_eval() {
1104-
let input = Input::default();
1105-
let mut output = Output {
1106-
show: true,
1107-
boost: 1.0,
1108-
price: Default::default(),
1109-
};
1110-
1111-
let cases = [
1112-
(true, true, true),
1113-
(false, false, false),
1114-
(false, true, false),
1115-
(true, false, false),
1116-
];
1117-
1118-
for (lhs, rhs, expected) in cases.iter() {
1119-
let rule = Rule::Function(Function::new_and(Value::Bool(*lhs), Value::Bool(*rhs)));
1120-
let expected = Some(Value::Bool(*expected));
1121-
1122-
assert_eq!(Ok(expected), rule.eval(&input, &mut output));
1123-
}
1124-
}
1125-
1126-
#[test]
1127-
fn test_if_eval() {
1128-
let input = Input::default();
1129-
let mut output = Output {
1130-
show: true,
1131-
boost: 1.0,
1132-
price: Default::default(),
1133-
};
1134-
1135-
let then = Value::String("yes".to_string());
1136-
1137-
let rule = Rule::Function(Function::new_if(Value::Bool(true), then.clone()));
1138-
1139-
assert_eq!(Ok(Some(then.clone())), rule.eval(&input, &mut output));
1140-
1141-
let rule = Rule::Function(Function::new_if(Value::Bool(false), then));
1142-
1143-
assert_eq!(Ok(None), rule.eval(&input, &mut output));
1144-
}
1145-
1146-
#[test]
1147-
fn test_bn_eval_from_actual_number_value_string_bignum_or_number() {
1148-
let input = Input::default();
1149-
let mut output = Output {
1150-
show: true,
1151-
boost: 1.0,
1152-
price: Default::default(),
1153-
};
1154-
1155-
let cases = vec![
1156-
(Value::new_string("1000"), Value::BigNum(1000.into())),
1157-
(Value::new_number(2_000), Value::BigNum(2_000.into())),
1158-
(Value::BigNum(3.into()), Value::BigNum(3.into())),
1159-
// rounded floats should work!
1160-
(
1161-
Value::Number(Number::from_f64(40.0).expect("should create float number")),
1162-
Value::BigNum(40.into()),
1163-
),
1164-
];
1165-
1166-
for (from, expected) in cases.into_iter() {
1167-
let rule = Rule::Function(Function::new_bn(from));
1168-
1169-
assert_eq!(Ok(Some(expected)), rule.eval(&input, &mut output));
1170-
}
1171-
}
1172-
1173-
#[test]
1174-
fn test_bn_eval_from_actual_incorrect_value() {
1175-
let input = Input::default();
1176-
let mut output = Output {
1177-
show: true,
1178-
boost: 1.0,
1179-
price: Default::default(),
1180-
};
1181-
1182-
let error_cases = vec![
1183-
Value::new_string("text"),
1184-
// BigNums can only be positive
1185-
Value::new_number(-100),
1186-
Value::Bool(true),
1187-
Value::Array(vec![Value::Bool(false)]),
1188-
Value::Number(Number::from_f64(2.5).expect("should create float number")),
1189-
];
1190-
1191-
for error_case in error_cases.into_iter() {
1192-
let rule = Rule::Function(Function::new_bn(error_case));
1193-
1194-
assert_eq!(Err(Error::TypeError), rule.eval(&input, &mut output));
1195-
}
1196-
}
1197-
1198-
#[test]
1199-
fn test_set_eval() {
1200-
use crate::channel::{Pricing, PricingBounds};
1201-
use crate::util::tests::prep_db::DUMMY_CHANNEL;
1202-
1203-
let mut channel = DUMMY_CHANNEL.clone();
1204-
channel.spec.pricing_bounds = Some(PricingBounds {
1205-
impression: Some(Pricing {
1206-
min: 1_000.into(),
1207-
max: 2_000.into(),
1208-
}),
1209-
click: Some(Pricing {
1210-
min: 3_000.into(),
1211-
max: 4_000.into(),
1212-
}),
1213-
});
1214-
1215-
let input = Input::default();
1216-
let mut output = Output::from(&channel);
1217-
1218-
assert_eq!(Some(&BigNum::from(1_000)), output.price.get("IMPRESSION"));
1219-
1220-
let set_to = Value::BigNum(BigNum::from(20));
1221-
let rule = Rule::Function(Function::new_set("price.IMPRESSION", set_to));
1222-
1223-
assert_eq!(Ok(None), rule.eval(&input, &mut output));
1224-
1225-
assert_eq!(Some(&BigNum::from(20)), output.price.get("IMPRESSION"));
1226-
}
1227-
}

0 commit comments

Comments
 (0)