RecipeExecutor.test.js 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185
  1. import {RecipeExecutor} from "lib/RecipeExecutor.jsm";
  2. class MockTagger {
  3. constructor(mode, tagScoreMap) {
  4. this.mode = mode;
  5. this.tagScoreMap = tagScoreMap;
  6. }
  7. tagTokens(tokens) {
  8. if (this.mode === "nb") {
  9. // eslint-disable-next-line prefer-destructuring
  10. let tag = Object.keys(this.tagScoreMap)[0];
  11. // eslint-disable-next-line prefer-destructuring
  12. let prob = this.tagScoreMap[tag];
  13. let conf = prob >= 0.85;
  14. return {
  15. label: tag,
  16. logProb: Math.log(prob),
  17. confident: conf,
  18. };
  19. }
  20. return this.tagScoreMap;
  21. }
  22. tag(text) {
  23. return this.tagTokens([text]);
  24. }
  25. }
  26. describe("RecipeExecutor", () => {
  27. let makeItem = () => {
  28. let x = {
  29. lhs: 2,
  30. one: 1,
  31. two: 2,
  32. three: 3,
  33. foo: "FOO",
  34. bar: "BAR",
  35. baz: ["one", "two", "three"],
  36. qux: 42,
  37. text: "This Is A_sentence.",
  38. url: "http://www.wonder.example.com/dir1/dir2a-dir2b/dir3+4?key1&key2=val2&key3&%26amp=%3D3+4",
  39. url2: "http://wonder.example.com/dir1/dir2a-dir2b/dir3+4?key1&key2=val2&key3&%26amp=%3D3+4",
  40. map: {
  41. c: 3,
  42. a: 1,
  43. b: 2,
  44. },
  45. map2: {
  46. b: 2,
  47. c: 3,
  48. d: 4,
  49. },
  50. arr1: [2, 3, 4],
  51. arr2: [3, 4, 5],
  52. long: [3, 4, 5, 6, 7],
  53. tags: {
  54. a: {
  55. aa: 0.1,
  56. ab: 0.2,
  57. ac: 0.3,
  58. },
  59. b: {
  60. ba: 4,
  61. bb: 5,
  62. bc: 6,
  63. },
  64. },
  65. bogus: {
  66. a: {
  67. aa: "0.1",
  68. ab: "0.2",
  69. ac: "0.3",
  70. },
  71. b: {
  72. ba: "4",
  73. bb: "5",
  74. bc: "6",
  75. },
  76. },
  77. zero: {
  78. a: 0,
  79. b: 0,
  80. },
  81. zaro: [0, 0],
  82. };
  83. return x;
  84. };
  85. let EPSILON = 0.00001;
  86. let instance = new RecipeExecutor(
  87. [new MockTagger("nb", {tag1: 0.70}),
  88. new MockTagger("nb", {tag2: 0.86}),
  89. new MockTagger("nb", {tag3: 0.90}),
  90. new MockTagger("nb", {tag5: 0.90})],
  91. {
  92. tag1: new MockTagger("nmf", {
  93. tag11: 0.9,
  94. tag12: 0.8,
  95. tag13: 0.7,
  96. }),
  97. tag2: new MockTagger("nmf", {
  98. tag21: 0.8,
  99. tag22: 0.7,
  100. tag23: 0.6,
  101. }),
  102. tag3: new MockTagger("nmf", {
  103. tag31: 0.7,
  104. tag32: 0.6,
  105. tag33: 0.5,
  106. }),
  107. tag4: new MockTagger("nmf", {tag41: 0.99}),
  108. });
  109. let item = null;
  110. beforeEach(() => {
  111. item = makeItem();
  112. });
  113. describe("#_assembleText", () => {
  114. it("should simply copy a single string", () => {
  115. assert.equal(instance._assembleText(item, ["foo"]), "FOO");
  116. });
  117. it("should append some strings with a space", () => {
  118. assert.equal(instance._assembleText(item, ["foo", "bar"]), "FOO BAR");
  119. });
  120. it("should give an empty string for a missing field", () => {
  121. assert.equal(instance._assembleText(item, ["missing"]), "");
  122. });
  123. it("should not double space an interior missing field", () => {
  124. assert.equal(instance._assembleText(item, ["foo", "missing", "bar"]), "FOO BAR");
  125. });
  126. it("should splice in an array of strings", () => {
  127. assert.equal(instance._assembleText(item, ["foo", "baz", "bar"]), "FOO one two three BAR");
  128. });
  129. it("should handle numbers", () => {
  130. assert.equal(instance._assembleText(item, ["foo", "qux", "bar"]), "FOO 42 BAR");
  131. });
  132. });
  133. describe("#naiveBayesTag", () => {
  134. it("should understand NaiveBayesTextTagger", () => {
  135. item = instance.naiveBayesTag(item, {fields: ["text"]});
  136. assert.isTrue("nb_tags" in item);
  137. assert.isTrue(!("tag1" in item.nb_tags));
  138. assert.equal(item.nb_tags.tag2, 0.86);
  139. assert.equal(item.nb_tags.tag3, 0.90);
  140. assert.equal(item.nb_tags.tag5, 0.90);
  141. assert.isTrue("nb_tokens" in item);
  142. assert.deepEqual(item.nb_tokens, ["this", "is", "a", "sentence"]);
  143. assert.isTrue("nb_tags_extended" in item);
  144. assert.isTrue(!("tag1" in item.nb_tags_extended));
  145. assert.deepEqual(item.nb_tags_extended.tag2, {
  146. label: "tag2",
  147. logProb: Math.log(0.86),
  148. confident: true,
  149. });
  150. assert.deepEqual(item.nb_tags_extended.tag3, {
  151. label: "tag3",
  152. logProb: Math.log(0.90),
  153. confident: true,
  154. });
  155. assert.deepEqual(item.nb_tags_extended.tag5, {
  156. label: "tag5",
  157. logProb: Math.log(0.90),
  158. confident: true,
  159. });
  160. assert.isTrue("nb_tokens" in item);
  161. assert.deepEqual(item.nb_tokens, ["this", "is", "a", "sentence"]);
  162. });
  163. });
  164. describe("#conditionallyNmfTag", () => {
  165. it("should do nothing if it's not nb tagged", () => {
  166. item = instance.conditionallyNmfTag(item, {});
  167. assert.equal(item, null);
  168. });
  169. it("should populate nmf tags for the nb tags", () => {
  170. item = instance.naiveBayesTag(item, {fields: ["text"]});
  171. item = instance.conditionallyNmfTag(item, {});
  172. assert.isTrue(("nb_tags" in item));
  173. assert.deepEqual(item.nmf_tags, {
  174. tag2: {
  175. tag21: 0.8,
  176. tag22: 0.7,
  177. tag23: 0.6,
  178. },
  179. tag3: {
  180. tag31: 0.7,
  181. tag32: 0.6,
  182. tag33: 0.5,
  183. },
  184. });
  185. assert.deepEqual(item.nmf_tags_parent, {
  186. tag21: "tag2",
  187. tag22: "tag2",
  188. tag23: "tag2",
  189. tag31: "tag3",
  190. tag32: "tag3",
  191. tag33: "tag3",
  192. });
  193. });
  194. it("should not populate nmf tags for things that were not nb tagged", () => {
  195. item = instance.naiveBayesTag(item, {fields: ["text"]});
  196. item = instance.conditionallyNmfTag(item, {});
  197. assert.isTrue(("nmf_tags" in item));
  198. assert.isTrue(!("tag4" in item.nmf_tags));
  199. assert.isTrue(("nmf_tags_parent" in item));
  200. assert.isTrue(!("tag4" in item.nmf_tags_parent));
  201. });
  202. });
  203. describe("#acceptItemByFieldValue", () => {
  204. it("should implement ==", () => {
  205. assert.isTrue(instance.acceptItemByFieldValue(item,
  206. {field: "lhs", op: "==", rhsValue: 2}) !== null);
  207. assert.isTrue(instance.acceptItemByFieldValue(item,
  208. {field: "lhs", op: "==", rhsValue: 3}) === null);
  209. assert.isTrue(instance.acceptItemByFieldValue(item,
  210. {field: "lhs", op: "==", rhsField: "two"}) !== null);
  211. assert.isTrue(instance.acceptItemByFieldValue(item,
  212. {field: "lhs", op: "==", rhsField: "three"}) === null);
  213. });
  214. it("should implement !=", () => {
  215. assert.isTrue(instance.acceptItemByFieldValue(item,
  216. {field: "lhs", op: "!=", rhsValue: 2}) === null);
  217. assert.isTrue(instance.acceptItemByFieldValue(item,
  218. {field: "lhs", op: "!=", rhsValue: 3}) !== null);
  219. });
  220. it("should implement < ", () => {
  221. assert.isTrue(instance.acceptItemByFieldValue(item,
  222. {field: "lhs", op: "<", rhsValue: 1}) === null);
  223. assert.isTrue(instance.acceptItemByFieldValue(item,
  224. {field: "lhs", op: "<", rhsValue: 2}) === null);
  225. assert.isTrue(instance.acceptItemByFieldValue(item,
  226. {field: "lhs", op: "<", rhsValue: 3}) !== null);
  227. });
  228. it("should implement <= ", () => {
  229. assert.isTrue(instance.acceptItemByFieldValue(item,
  230. {field: "lhs", op: "<=", rhsValue: 1}) === null);
  231. assert.isTrue(instance.acceptItemByFieldValue(item,
  232. {field: "lhs", op: "<=", rhsValue: 2}) !== null);
  233. assert.isTrue(instance.acceptItemByFieldValue(item,
  234. {field: "lhs", op: "<=", rhsValue: 3}) !== null);
  235. });
  236. it("should implement > ", () => {
  237. assert.isTrue(instance.acceptItemByFieldValue(item,
  238. {field: "lhs", op: ">", rhsValue: 1}) !== null);
  239. assert.isTrue(instance.acceptItemByFieldValue(item,
  240. {field: "lhs", op: ">", rhsValue: 2}) === null);
  241. assert.isTrue(instance.acceptItemByFieldValue(item,
  242. {field: "lhs", op: ">", rhsValue: 3}) === null);
  243. });
  244. it("should implement >= ", () => {
  245. assert.isTrue(instance.acceptItemByFieldValue(item,
  246. {field: "lhs", op: ">=", rhsValue: 1}) !== null);
  247. assert.isTrue(instance.acceptItemByFieldValue(item,
  248. {field: "lhs", op: ">=", rhsValue: 2}) !== null);
  249. assert.isTrue(instance.acceptItemByFieldValue(item,
  250. {field: "lhs", op: ">=", rhsValue: 3}) === null);
  251. });
  252. it("should skip items with missing fields", () => {
  253. assert.isTrue(instance.acceptItemByFieldValue(item,
  254. {field: "no-left", op: "==", rhsValue: 1}) === null);
  255. assert.isTrue(instance.acceptItemByFieldValue(item,
  256. {field: "lhs", op: "==", rhsField: "no-right"}) === null);
  257. assert.isTrue(instance.acceptItemByFieldValue(item,
  258. {field: "lhs", op: "=="}) === null);
  259. });
  260. it("should skip items with bogus operators", () => {
  261. assert.isTrue(instance.acceptItemByFieldValue(item,
  262. {field: "lhs", op: "bogus", rhsField: "two"}) === null);
  263. });
  264. });
  265. describe("#tokenizeUrl", () => {
  266. it("should strip the leading www from a url", () => {
  267. item = instance.tokenizeUrl(item, {field: "url", dest: "url_toks"});
  268. assert.deepEqual(
  269. ["wonder", "example", "com", "dir1", "dir2a", "dir2b", "dir3", "4", "key1", "key2", "val2", "key3", "amp", "3", "4"],
  270. item.url_toks);
  271. });
  272. it("should tokenize the not strip the leading non-wwww token from a url", () => {
  273. item = instance.tokenizeUrl(item, {field: "url2", dest: "url_toks"});
  274. assert.deepEqual(
  275. ["wonder", "example", "com", "dir1", "dir2a", "dir2b", "dir3", "4", "key1", "key2", "val2", "key3", "amp", "3", "4"],
  276. item.url_toks);
  277. });
  278. it("should error for a missing url", () => {
  279. item = instance.tokenizeUrl(item, {field: "missing", dest: "url_toks"});
  280. assert.equal(item, null);
  281. });
  282. });
  283. describe("#getUrlDomain", () => {
  284. it("should get only the hostname skipping the www", () => {
  285. item = instance.getUrlDomain(item, {field: "url", dest: "url_domain"});
  286. assert.isTrue("url_domain" in item);
  287. assert.deepEqual("wonder.example.com", item.url_domain);
  288. });
  289. it("should get only the hostname", () => {
  290. item = instance.getUrlDomain(item, {field: "url2", dest: "url_domain"});
  291. assert.isTrue("url_domain" in item);
  292. assert.deepEqual("wonder.example.com", item.url_domain);
  293. });
  294. it("should get the hostname and 2 levels of directories", () => {
  295. item = instance.getUrlDomain(item, {field: "url", path_length: 2, dest: "url_plus_2"});
  296. assert.isTrue("url_plus_2" in item);
  297. assert.deepEqual("wonder.example.com/dir1/dir2a-dir2b", item.url_plus_2);
  298. });
  299. it("should error for a missing url", () => {
  300. item = instance.getUrlDomain(item, {field: "missing", dest: "url_domain"});
  301. assert.equal(item, null);
  302. });
  303. });
  304. describe("#tokenizeField", () => {
  305. it("should tokenize the field", () => {
  306. item = instance.tokenizeField(item, {field: "text", dest: "toks"});
  307. assert.isTrue("toks" in item);
  308. assert.deepEqual(["this", "is", "a", "sentence"], item.toks);
  309. });
  310. it("should error for a missing field", () => {
  311. item = instance.tokenizeField(item, {field: "missing", dest: "toks"});
  312. assert.equal(item, null);
  313. });
  314. it("should error for a broken config", () => {
  315. item = instance.tokenizeField(item, {});
  316. assert.equal(item, null);
  317. });
  318. });
  319. describe("#_typeOf", () => {
  320. it("should know this is a map", () => {
  321. assert.equal(instance._typeOf({}), "map");
  322. });
  323. it("should know this is an array", () => {
  324. assert.equal(instance._typeOf([]), "array");
  325. });
  326. it("should know this is a string", () => {
  327. assert.equal(instance._typeOf("blah"), "string");
  328. });
  329. it("should know this is a boolean", () => {
  330. assert.equal(instance._typeOf(true), "boolean");
  331. });
  332. it("should know this is a null", () => {
  333. assert.equal(instance._typeOf(null), "null");
  334. });
  335. });
  336. describe("#_lookupScalar", () => {
  337. it("should return the constant", () => {
  338. assert.equal(instance._lookupScalar({}, 1, 0), 1);
  339. });
  340. it("should return the default", () => {
  341. assert.equal(instance._lookupScalar({}, "blah", 42), 42);
  342. });
  343. it("should return the field's value", () => {
  344. assert.equal(instance._lookupScalar({blah: 11}, "blah", 42), 11);
  345. });
  346. });
  347. describe("#copyValue", () => {
  348. it("should copy values", () => {
  349. item = instance.copyValue(item, {src: "one", dest: "again"});
  350. assert.isTrue("again" in item);
  351. assert.equal(item.again, 1);
  352. item.one = 100;
  353. assert.equal(item.one, 100);
  354. assert.equal(item.again, 1);
  355. });
  356. it("should handle maps corrects", () => {
  357. item = instance.copyValue(item, {src: "map", dest: "again"});
  358. assert.deepEqual(item.again, {a: 1, b: 2, c: 3});
  359. item.map.c = 100;
  360. assert.deepEqual(item.again, {a: 1, b: 2, c: 3});
  361. item.map = 342;
  362. assert.deepEqual(item.again, {a: 1, b: 2, c: 3});
  363. });
  364. it("should error for a missing field", () => {
  365. item = instance.copyValue(item, {src: "missing", dest: "toks"});
  366. assert.equal(item, null);
  367. });
  368. });
  369. describe("#keepTopK", () => {
  370. it("should keep the 2 smallest", () => {
  371. item = instance.keepTopK(item, {field: "map", k: 2, descending: false});
  372. assert.equal(Object.keys(item.map).length, 2);
  373. assert.isTrue("a" in item.map);
  374. assert.equal(item.map.a, 1);
  375. assert.isTrue("b" in item.map);
  376. assert.equal(item.map.b, 2);
  377. assert.isTrue(!("c" in item.map));
  378. });
  379. it("should keep the 2 largest", () => {
  380. item = instance.keepTopK(item, {field: "map", k: 2, descending: true});
  381. assert.equal(Object.keys(item.map).length, 2);
  382. assert.isTrue(!("a" in item.map));
  383. assert.isTrue("b" in item.map);
  384. assert.equal(item.map.b, 2);
  385. assert.isTrue("c" in item.map);
  386. assert.equal(item.map.c, 3);
  387. });
  388. it("should still keep the 2 largest", () => {
  389. item = instance.keepTopK(item, {field: "map", k: 2});
  390. assert.equal(Object.keys(item.map).length, 2);
  391. assert.isTrue(!("a" in item.map));
  392. assert.isTrue("b" in item.map);
  393. assert.equal(item.map.b, 2);
  394. assert.isTrue("c" in item.map);
  395. assert.equal(item.map.c, 3);
  396. });
  397. it("should promote up nested fields", () => {
  398. item = instance.keepTopK(item, {field: "tags", k: 2});
  399. assert.equal(Object.keys(item.tags).length, 2);
  400. assert.deepEqual(item.tags, {bb: 5, bc: 6});
  401. });
  402. it("should error for a missing field", () => {
  403. item = instance.keepTopK(item, {field: "missing", k: 3});
  404. assert.equal(item, null);
  405. });
  406. });
  407. describe("#scalarMultiply", () => {
  408. it("should use constants", () => {
  409. item = instance.scalarMultiply(item, {field: "map", k: 2});
  410. assert.equal(item.map.a, 2);
  411. assert.equal(item.map.b, 4);
  412. assert.equal(item.map.c, 6);
  413. });
  414. it("should use fields", () => {
  415. item = instance.scalarMultiply(item, {field: "map", k: "three"});
  416. assert.equal(item.map.a, 3);
  417. assert.equal(item.map.b, 6);
  418. assert.equal(item.map.c, 9);
  419. });
  420. it("should use default", () => {
  421. item = instance.scalarMultiply(item, {field: "map", k: "missing", dfault: 4});
  422. assert.equal(item.map.a, 4);
  423. assert.equal(item.map.b, 8);
  424. assert.equal(item.map.c, 12);
  425. });
  426. it("should error for a missing field", () => {
  427. item = instance.scalarMultiply(item, {field: "missing", k: 3});
  428. assert.equal(item, null);
  429. });
  430. it("should multiply numbers", () => {
  431. item = instance.scalarMultiply(item, {field: "lhs", k: 2});
  432. assert.equal(item.lhs, 4);
  433. });
  434. it("should multiply arrays", () => {
  435. item = instance.scalarMultiply(item, {field: "arr1", k: 2});
  436. assert.deepEqual(item.arr1, [4, 6, 8]);
  437. });
  438. it("should should error on strings", () => {
  439. item = instance.scalarMultiply(item, {field: "foo", k: 2});
  440. assert.equal(item, null);
  441. });
  442. });
  443. describe("#elementwiseMultiply", () => {
  444. it("should handle maps", () => {
  445. item = instance.elementwiseMultiply(item, {left: "tags", right: "map2"});
  446. assert.deepEqual(item.tags, {a: {aa: 0, ab: 0, ac: 0}, b: {ba: 8, bb: 10, bc: 12}});
  447. });
  448. it("should handle arrays of same length", () => {
  449. item = instance.elementwiseMultiply(item, {left: "arr1", right: "arr2"});
  450. assert.deepEqual(item.arr1, [6, 12, 20]);
  451. });
  452. it("should error for arrays of different lengths", () => {
  453. item = instance.elementwiseMultiply(item, {left: "arr1", right: "long"});
  454. assert.equal(item, null);
  455. });
  456. it("should error for a missing left", () => {
  457. item = instance.elementwiseMultiply(item, {left: "missing", right: "arr2"});
  458. assert.equal(item, null);
  459. });
  460. it("should error for a missing right", () => {
  461. item = instance.elementwiseMultiply(item, {left: "arr1", right: "missing"});
  462. assert.equal(item, null);
  463. });
  464. it("should handle numbers", () => {
  465. item = instance.elementwiseMultiply(item, {left: "three", right: "two"});
  466. assert.equal(item.three, 6);
  467. });
  468. it("should error for mismatched types", () => {
  469. item = instance.elementwiseMultiply(item, {left: "arr1", right: "two"});
  470. assert.equal(item, null);
  471. });
  472. it("should error for strings", () => {
  473. item = instance.elementwiseMultiply(item, {left: "foo", right: "bar"});
  474. assert.equal(item, null);
  475. });
  476. });
  477. describe("#vectorMultiply", () => {
  478. it("should calculate dot products from maps", () => {
  479. item = instance.vectorMultiply(item, {left: "map", right: "map2", dest: "dot"});
  480. assert.equal(item.dot, 13);
  481. });
  482. it("should calculate dot products from arrays", () => {
  483. item = instance.vectorMultiply(item, {left: "arr1", right: "arr2", dest: "dot"});
  484. assert.equal(item.dot, 38);
  485. });
  486. it("should error for arrays of different lengths", () => {
  487. item = instance.vectorMultiply(item, {left: "arr1", right: "long"});
  488. assert.equal(item, null);
  489. });
  490. it("should error for a missing left", () => {
  491. item = instance.vectorMultiply(item, {left: "missing", right: "arr2"});
  492. assert.equal(item, null);
  493. });
  494. it("should error for a missing right", () => {
  495. item = instance.vectorMultiply(item, {left: "arr1", right: "missing"});
  496. assert.equal(item, null);
  497. });
  498. it("should error for mismatched types", () => {
  499. item = instance.vectorMultiply(item, {left: "arr1", right: "two"});
  500. assert.equal(item, null);
  501. });
  502. it("should error for strings", () => {
  503. item = instance.vectorMultiply(item, {left: "foo", right: "bar"});
  504. assert.equal(item, null);
  505. });
  506. });
  507. describe("#scalarAdd", () => {
  508. it("should error for a missing field", () => {
  509. item = instance.scalarAdd(item, {field: "missing", k: 10});
  510. assert.equal(item, null);
  511. });
  512. it("should error for strings", () => {
  513. item = instance.scalarAdd(item, {field: "foo", k: 10});
  514. assert.equal(item, null);
  515. });
  516. it("should work for numbers", () => {
  517. item = instance.scalarAdd(item, {field: "one", k: 10});
  518. assert.equal(item.one, 11);
  519. });
  520. it("should add a constant to every cell on a map", () => {
  521. item = instance.scalarAdd(item, {field: "map", k: 10});
  522. assert.deepEqual(item.map, {a: 11, b: 12, c: 13});
  523. });
  524. it("should add a value from a field to every cell on a map", () => {
  525. item = instance.scalarAdd(item, {field: "map", k: "qux"});
  526. assert.deepEqual(item.map, {a: 43, b: 44, c: 45});
  527. });
  528. it("should add a constant to every cell on an array", () => {
  529. item = instance.scalarAdd(item, {field: "arr1", k: 10});
  530. assert.deepEqual(item.arr1, [12, 13, 14]);
  531. });
  532. });
  533. describe("#vectorAdd", () => {
  534. it("should calculate add vectors from maps", () => {
  535. item = instance.vectorAdd(item, {left: "map", right: "map2"});
  536. assert.equal(Object.keys(item.map).length, 4);
  537. assert.isTrue("a" in item.map);
  538. assert.equal(item.map.a, 1);
  539. assert.isTrue("b" in item.map);
  540. assert.equal(item.map.b, 4);
  541. assert.isTrue("c" in item.map);
  542. assert.equal(item.map.c, 6);
  543. assert.isTrue("d" in item.map);
  544. assert.equal(item.map.d, 4);
  545. });
  546. it("should work for missing left", () => {
  547. item = instance.vectorAdd(item, {left: "missing", right: "arr2"});
  548. assert.deepEqual(item.missing, [3, 4, 5]);
  549. });
  550. it("should error for missing right", () => {
  551. item = instance.vectorAdd(item, {left: "arr2", right: "missing"});
  552. assert.equal(item, null);
  553. });
  554. it("should error error for strings", () => {
  555. item = instance.vectorAdd(item, {left: "foo", right: "bar"});
  556. assert.equal(item, null);
  557. });
  558. it("should error for different types", () => {
  559. item = instance.vectorAdd(item, {left: "arr2", right: "map"});
  560. assert.equal(item, null);
  561. });
  562. it("should calculate add vectors from arrays", () => {
  563. item = instance.vectorAdd(item, {left: "arr1", right: "arr2"});
  564. assert.deepEqual(item.arr1, [5, 7, 9]);
  565. });
  566. it("should abort on different sized arrays", () => {
  567. item = instance.vectorAdd(item, {left: "arr1", right: "long"});
  568. assert.equal(item, null);
  569. });
  570. it("should calculate add vectors from arrays", () => {
  571. item = instance.vectorAdd(item, {left: "arr1", right: "arr2"});
  572. assert.deepEqual(item.arr1, [5, 7, 9]);
  573. });
  574. });
  575. describe("#makeBoolean", () => {
  576. it("should error for missing field", () => {
  577. item = instance.makeBoolean(item, {field: "missing", threshold: 2});
  578. assert.equal(item, null);
  579. });
  580. it("should 0/1 a map", () => {
  581. item = instance.makeBoolean(item, {field: "map", threshold: 2});
  582. assert.deepEqual(item.map, {a: 0, b: 0, c: 1});
  583. });
  584. it("should a map of all 1s", () => {
  585. item = instance.makeBoolean(item, {field: "map"});
  586. assert.deepEqual(item.map, {a: 1, b: 1, c: 1});
  587. });
  588. it("should -1/1 a map", () => {
  589. item = instance.makeBoolean(item, {field: "map", threshold: 2, keep_negative: true});
  590. assert.deepEqual(item.map, {a: -1, b: -1, c: 1});
  591. });
  592. it("should work an array", () => {
  593. item = instance.makeBoolean(item, {field: "arr1", threshold: 3});
  594. assert.deepEqual(item.arr1, [0, 0, 1]);
  595. });
  596. it("should -1/1 an array", () => {
  597. item = instance.makeBoolean(item, {field: "arr1", threshold: 3, keep_negative: true});
  598. assert.deepEqual(item.arr1, [-1, -1, 1]);
  599. });
  600. it("should 1 a high number", () => {
  601. item = instance.makeBoolean(item, {field: "qux", threshold: 3});
  602. assert.equal(item.qux, 1);
  603. });
  604. it("should 0 a low number", () => {
  605. item = instance.makeBoolean(item, {field: "qux", threshold: 70});
  606. assert.equal(item.qux, 0);
  607. });
  608. it("should -1 a low number", () => {
  609. item = instance.makeBoolean(item, {field: "qux", threshold: 83, keep_negative: true});
  610. assert.equal(item.qux, -1);
  611. });
  612. it("should fail a string", () => {
  613. item = instance.makeBoolean(item, {field: "foo", threshold: 3});
  614. assert.equal(item, null);
  615. });
  616. });
  617. describe("#whitelistFields", () => {
  618. it("should filter the keys out of a map", () => {
  619. item = instance.whitelistFields(item, {fields: ["foo", "missing", "bar"]});
  620. assert.deepEqual(item, {foo: "FOO", bar: "BAR"});
  621. });
  622. });
  623. describe("#filterByValue", () => {
  624. it("should fail on missing field", () => {
  625. item = instance.filterByValue(item, {field: "missing", threshold: 2});
  626. assert.equal(item, null);
  627. });
  628. it("should filter the keys out of a map", () => {
  629. item = instance.filterByValue(item, {field: "map", threshold: 2});
  630. assert.deepEqual(item.map, {c: 3});
  631. });
  632. });
  633. describe("#l2Normalize", () => {
  634. it("should fail on missing field", () => {
  635. item = instance.l2Normalize(item, {field: "missing"});
  636. assert.equal(item, null);
  637. });
  638. it("should L2 normalize an array", () => {
  639. item = instance.l2Normalize(item, {field: "arr1"});
  640. assert.deepEqual(item.arr1, [0.3713906763541037, 0.5570860145311556, 0.7427813527082074]);
  641. });
  642. it("should L2 normalize a map", () => {
  643. item = instance.l2Normalize(item, {field: "map"});
  644. assert.deepEqual(item.map, {a: 0.2672612419124244, b: 0.5345224838248488, c: 0.8017837257372732});
  645. });
  646. it("should fail a string", () => {
  647. item = instance.l2Normalize(item, {field: "foo"});
  648. assert.equal(item, null);
  649. });
  650. it("should not bomb on a zero vector", () => {
  651. item = instance.l2Normalize(item, {field: "zero"});
  652. assert.deepEqual(item.zero, {a: 0, b: 0});
  653. item = instance.l2Normalize(item, {field: "zaro"});
  654. assert.deepEqual(item.zaro, [0, 0]);
  655. });
  656. });
  657. describe("#probNormalize", () => {
  658. it("should fail on missing field", () => {
  659. item = instance.probNormalize(item, {field: "missing"});
  660. assert.equal(item, null);
  661. });
  662. it("should normalize an array to sum to 1", () => {
  663. item = instance.probNormalize(item, {field: "arr1"});
  664. assert.deepEqual(item.arr1, [0.2222222222222222, 0.3333333333333333, 0.4444444444444444]);
  665. });
  666. it("should normalize a map to sum to 1", () => {
  667. item = instance.probNormalize(item, {field: "map"});
  668. assert.equal(Object.keys(item.map).length, 3);
  669. assert.isTrue("a" in item.map);
  670. assert.isTrue(Math.abs(item.map.a - 0.16667) <= EPSILON);
  671. assert.isTrue("b" in item.map);
  672. assert.isTrue(Math.abs(item.map.b - 0.33333) <= EPSILON);
  673. assert.isTrue("c" in item.map);
  674. assert.isTrue(Math.abs(item.map.c - 0.5) <= EPSILON);
  675. });
  676. it("should fail a string", () => {
  677. item = instance.probNormalize(item, {field: "foo"});
  678. assert.equal(item, null);
  679. });
  680. it("should not bomb on a zero vector", () => {
  681. item = instance.probNormalize(item, {field: "zero"});
  682. assert.deepEqual(item.zero, {a: 0, b: 0});
  683. item = instance.probNormalize(item, {field: "zaro"});
  684. assert.deepEqual(item.zaro, [0, 0]);
  685. });
  686. });
  687. describe("#scalarMultiplyTag", () => {
  688. it("should fail on missing field", () => {
  689. item = instance.scalarMultiplyTag(item, {field: "missing", k: 3});
  690. assert.equal(item, null);
  691. });
  692. it("should scalar multiply a nested map", () => {
  693. item = instance.scalarMultiplyTag(item, {field: "tags", k: 3, log_scale: false});
  694. assert.isTrue(Math.abs(item.tags.a.aa - 0.3) <= EPSILON);
  695. assert.isTrue(Math.abs(item.tags.a.ab - 0.6) <= EPSILON);
  696. assert.isTrue(Math.abs(item.tags.a.ac - 0.9) <= EPSILON);
  697. assert.isTrue(Math.abs(item.tags.b.ba - 12) <= EPSILON);
  698. assert.isTrue(Math.abs(item.tags.b.bb - 15) <= EPSILON);
  699. assert.isTrue(Math.abs(item.tags.b.bc - 18) <= EPSILON);
  700. });
  701. it("should scalar multiply a nested map with logrithms", () => {
  702. item = instance.scalarMultiplyTag(item, {field: "tags", k: 3, log_scale: true});
  703. assert.isTrue(Math.abs(item.tags.a.aa - Math.log(0.1 + 0.000001) * 3) <= EPSILON);
  704. assert.isTrue(Math.abs(item.tags.a.ab - Math.log(0.2 + 0.000001) * 3) <= EPSILON);
  705. assert.isTrue(Math.abs(item.tags.a.ac - Math.log(0.3 + 0.000001) * 3) <= EPSILON);
  706. assert.isTrue(Math.abs(item.tags.b.ba - Math.log(4.0 + 0.000001) * 3) <= EPSILON);
  707. assert.isTrue(Math.abs(item.tags.b.bb - Math.log(5.0 + 0.000001) * 3) <= EPSILON);
  708. assert.isTrue(Math.abs(item.tags.b.bc - Math.log(6.0 + 0.000001) * 3) <= EPSILON);
  709. });
  710. it("should fail a string", () => {
  711. item = instance.scalarMultiplyTag(item, {field: "foo", k: 3});
  712. assert.equal(item, null);
  713. });
  714. });
  715. describe("#setDefault", () => {
  716. it("should store a missing value", () => {
  717. item = instance.setDefault(item, {field: "missing", value: 1111});
  718. assert.equal(item.missing, 1111);
  719. });
  720. it("should not overwrite an existing value", () => {
  721. item = instance.setDefault(item, {field: "lhs", value: 1111});
  722. assert.equal(item.lhs, 2);
  723. });
  724. it("should store a complex value", () => {
  725. item = instance.setDefault(item, {field: "missing", value: {a: 1}});
  726. assert.deepEqual(item.missing, {a: 1});
  727. });
  728. });
  729. describe("#lookupValue", () => {
  730. it("should promote a value", () => {
  731. item = instance.lookupValue(item, {haystack: "map", needle: "c", dest: "ccc"});
  732. assert.equal(item.ccc, 3);
  733. });
  734. it("should handle a missing haystack", () => {
  735. item = instance.lookupValue(item, {haystack: "missing", needle: "c", dest: "ccc"});
  736. assert.isTrue(!("ccc" in item));
  737. });
  738. it("should handle a missing needle", () => {
  739. item = instance.lookupValue(item, {haystack: "map", needle: "missing", dest: "ccc"});
  740. assert.isTrue(!("ccc" in item));
  741. });
  742. });
  743. describe("#copyToMap", () => {
  744. it("should copy a value to a map", () => {
  745. item = instance.copyToMap(item, {src: "qux", dest_map: "map", dest_key: "zzz"});
  746. assert.isTrue("zzz" in item.map);
  747. assert.equal(item.map.zzz, item.qux);
  748. });
  749. it("should create a new map to hold the key", () => {
  750. item = instance.copyToMap(item, {src: "qux", dest_map: "missing", dest_key: "zzz"});
  751. assert.equal(Object.keys(item.missing).length, 1);
  752. assert.equal(item.missing.zzz, item.qux);
  753. });
  754. it("should not create an empty map if the src is missing", () => {
  755. item = instance.copyToMap(item, {src: "missing", dest_map: "no_map", dest_key: "zzz"});
  756. assert.isTrue(!("no_map" in item));
  757. });
  758. });
  759. describe("#applySoftmaxTags", () => {
  760. it("should error on missing field", () => {
  761. item = instance.applySoftmaxTags(item, {field: "missing"});
  762. assert.equal(item, null);
  763. });
  764. it("should error on nonmaps", () => {
  765. item = instance.applySoftmaxTags(item, {field: "arr1"});
  766. assert.equal(item, null);
  767. });
  768. it("should error on unnested maps", () => {
  769. item = instance.applySoftmaxTags(item, {field: "map"});
  770. assert.equal(item, null);
  771. });
  772. it("should error on wrong nested maps", () => {
  773. item = instance.applySoftmaxTags(item, {field: "bogus"});
  774. assert.equal(item, null);
  775. });
  776. it("should apply softmax across the subtags", () => {
  777. item = instance.applySoftmaxTags(item, {field: "tags"});
  778. assert.isTrue("a" in item.tags);
  779. assert.isTrue("aa" in item.tags.a);
  780. assert.isTrue("ab" in item.tags.a);
  781. assert.isTrue("ac" in item.tags.a);
  782. assert.isTrue(Math.abs(item.tags.a.aa - 0.30061) <= EPSILON);
  783. assert.isTrue(Math.abs(item.tags.a.ab - 0.33222) <= EPSILON);
  784. assert.isTrue(Math.abs(item.tags.a.ac - 0.36717) <= EPSILON);
  785. assert.isTrue("b" in item.tags);
  786. assert.isTrue("ba" in item.tags.b);
  787. assert.isTrue("bb" in item.tags.b);
  788. assert.isTrue("bc" in item.tags.b);
  789. assert.isTrue(Math.abs(item.tags.b.ba - 0.09003) <= EPSILON);
  790. assert.isTrue(Math.abs(item.tags.b.bb - 0.24473) <= EPSILON);
  791. assert.isTrue(Math.abs(item.tags.b.bc - 0.66524) <= EPSILON);
  792. });
  793. });
  794. describe("#combinerAdd", () => {
  795. it("should do nothing when right field is missing", () => {
  796. let right = makeItem();
  797. let combined = instance.combinerAdd(item, right, {field: "missing"});
  798. assert.deepEqual(combined, item);
  799. });
  800. it("should handle missing left maps", () => {
  801. let right = makeItem();
  802. right.missingmap = {a: 5, b: -1, c: 3};
  803. let combined = instance.combinerAdd(item, right, {field: "missingmap"});
  804. assert.deepEqual(combined.missingmap, {a: 5, b: -1, c: 3});
  805. });
  806. it("should add equal sized maps", () => {
  807. let right = makeItem();
  808. let combined = instance.combinerAdd(item, right, {field: "map"});
  809. assert.deepEqual(combined.map, {a: 2, b: 4, c: 6});
  810. });
  811. it("should add long map to short map", () => {
  812. let right = makeItem();
  813. right.map.d = 999;
  814. let combined = instance.combinerAdd(item, right, {field: "map"});
  815. assert.deepEqual(combined.map, {a: 2, b: 4, c: 6, d: 999});
  816. });
  817. it("should add short map to long map", () => {
  818. let right = makeItem();
  819. item.map.d = 999;
  820. let combined = instance.combinerAdd(item, right, {field: "map"});
  821. assert.deepEqual(combined.map, {a: 2, b: 4, c: 6, d: 999});
  822. });
  823. it("should add equal sized arrays", () => {
  824. let right = makeItem();
  825. let combined = instance.combinerAdd(item, right, {field: "arr1"});
  826. assert.deepEqual(combined.arr1, [4, 6, 8]);
  827. });
  828. it("should handle missing left arrays", () => {
  829. let right = makeItem();
  830. right.missingarray = [5, 1, 4];
  831. let combined = instance.combinerAdd(item, right, {field: "missingarray"});
  832. assert.deepEqual(combined.missingarray, [5, 1, 4]);
  833. });
  834. it("should add long array to short array", () => {
  835. let right = makeItem();
  836. right.arr1 = [2, 3, 4, 12];
  837. let combined = instance.combinerAdd(item, right, {field: "arr1"});
  838. assert.deepEqual(combined.arr1, [4, 6, 8, 12]);
  839. });
  840. it("should add short array to long array", () => {
  841. let right = makeItem();
  842. item.arr1 = [2, 3, 4, 12];
  843. let combined = instance.combinerAdd(item, right, {field: "arr1"});
  844. assert.deepEqual(combined.arr1, [4, 6, 8, 12]);
  845. });
  846. it("should handle missing left number", () => {
  847. let right = makeItem();
  848. right.missingnumber = 999;
  849. let combined = instance.combinerAdd(item, right, {field: "missingnumber"});
  850. assert.deepEqual(combined.missingnumber, 999);
  851. });
  852. it("should add numbers", () => {
  853. let right = makeItem();
  854. let combined = instance.combinerAdd(item, right, {field: "lhs"});
  855. assert.equal(combined.lhs, 4);
  856. });
  857. it("should error on missing left, and right is a string", () => {
  858. let right = makeItem();
  859. right.error = "error";
  860. let combined = instance.combinerAdd(item, right, {field: "error"});
  861. assert.equal(combined, null);
  862. });
  863. it("should error on left string", () => {
  864. let right = makeItem();
  865. let combined = instance.combinerAdd(item, right, {field: "foo"});
  866. assert.equal(combined, null);
  867. });
  868. it("should error on mismatch types", () => {
  869. let right = makeItem();
  870. right.lhs = [1, 2, 3];
  871. let combined = instance.combinerAdd(item, right, {field: "lhs"});
  872. assert.equal(combined, null);
  873. });
  874. });
  875. describe("#combinerMax", () => {
  876. it("should do nothing when right field is missing", () => {
  877. let right = makeItem();
  878. let combined = instance.combinerMax(item, right, {field: "missing"});
  879. assert.deepEqual(combined, item);
  880. });
  881. it("should handle missing left maps", () => {
  882. let right = makeItem();
  883. right.missingmap = {a: 5, b: -1, c: 3};
  884. let combined = instance.combinerMax(item, right, {field: "missingmap"});
  885. assert.deepEqual(combined.missingmap, {a: 5, b: -1, c: 3});
  886. });
  887. it("should handle equal sized maps", () => {
  888. let right = makeItem();
  889. right.map = {a: 5, b: -1, c: 3};
  890. let combined = instance.combinerMax(item, right, {field: "map"});
  891. assert.deepEqual(combined.map, {a: 5, b: 2, c: 3});
  892. });
  893. it("should handle short map to long map", () => {
  894. let right = makeItem();
  895. right.map = {a: 5, b: -1, c: 3, d: 999};
  896. let combined = instance.combinerMax(item, right, {field: "map"});
  897. assert.deepEqual(combined.map, {a: 5, b: 2, c: 3, d: 999});
  898. });
  899. it("should handle long map to short map", () => {
  900. let right = makeItem();
  901. right.map = {a: 5, b: -1, c: 3};
  902. item.map.d = 999;
  903. let combined = instance.combinerMax(item, right, {field: "map"});
  904. assert.deepEqual(combined.map, {a: 5, b: 2, c: 3, d: 999});
  905. });
  906. it("should handle equal sized arrays", () => {
  907. let right = makeItem();
  908. right.arr1 = [5, 1, 4];
  909. let combined = instance.combinerMax(item, right, {field: "arr1"});
  910. assert.deepEqual(combined.arr1, [5, 3, 4]);
  911. });
  912. it("should handle missing left arrays", () => {
  913. let right = makeItem();
  914. right.missingarray = [5, 1, 4];
  915. let combined = instance.combinerMax(item, right, {field: "missingarray"});
  916. assert.deepEqual(combined.missingarray, [5, 1, 4]);
  917. });
  918. it("should handle short array to long array", () => {
  919. let right = makeItem();
  920. right.arr1 = [5, 1, 4, 7];
  921. let combined = instance.combinerMax(item, right, {field: "arr1"});
  922. assert.deepEqual(combined.arr1, [5, 3, 4, 7]);
  923. });
  924. it("should handle long array to short array", () => {
  925. let right = makeItem();
  926. right.arr1 = [5, 1, 4];
  927. item.arr1.push(7);
  928. let combined = instance.combinerMax(item, right, {field: "arr1"});
  929. assert.deepEqual(combined.arr1, [5, 3, 4, 7]);
  930. });
  931. it("should handle missing left number", () => {
  932. let right = makeItem();
  933. right.missingnumber = 999;
  934. let combined = instance.combinerMax(item, right, {field: "missingnumber"});
  935. assert.deepEqual(combined.missingnumber, 999);
  936. });
  937. it("should handle big number", () => {
  938. let right = makeItem();
  939. right.lhs = 99;
  940. let combined = instance.combinerMax(item, right, {field: "lhs"});
  941. assert.equal(combined.lhs, 99);
  942. });
  943. it("should handle small number", () => {
  944. let right = makeItem();
  945. item.lhs = 99;
  946. let combined = instance.combinerMax(item, right, {field: "lhs"});
  947. assert.equal(combined.lhs, 99);
  948. });
  949. it("should error on missing left, and right is a string", () => {
  950. let right = makeItem();
  951. right.error = "error";
  952. let combined = instance.combinerMax(item, right, {field: "error"});
  953. assert.equal(combined, null);
  954. });
  955. it("should error on left string", () => {
  956. let right = makeItem();
  957. let combined = instance.combinerMax(item, right, {field: "foo"});
  958. assert.equal(combined, null);
  959. });
  960. it("should error on mismatch types", () => {
  961. let right = makeItem();
  962. right.lhs = [1, 2, 3];
  963. let combined = instance.combinerMax(item, right, {field: "lhs"});
  964. assert.equal(combined, null);
  965. });
  966. });
  967. describe("#combinerCollectValues", () => {
  968. it("should error on bogus operation", () => {
  969. let right = makeItem();
  970. right.url_domain = "maseratiusa.com/maserati";
  971. right.time = 41;
  972. let combined = instance.combinerCollectValues(item, right,
  973. {left_field: "combined_map", right_key_field: "url_domain", right_value_field: "time", operation: "missing"});
  974. assert.equal(combined, null);
  975. });
  976. it("should sum when missing left", () => {
  977. let right = makeItem();
  978. right.url_domain = "maseratiusa.com/maserati";
  979. right.time = 41;
  980. let combined = instance.combinerCollectValues(item, right,
  981. {left_field: "combined_map", right_key_field: "url_domain", right_value_field: "time", operation: "sum"});
  982. assert.deepEqual(combined.combined_map, {"maseratiusa.com/maserati": 41});
  983. });
  984. it("should sum when missing right", () => {
  985. let right = makeItem();
  986. item.combined_map = {"fake": 42};
  987. let combined = instance.combinerCollectValues(item, right,
  988. {left_field: "combined_map", right_key_field: "url_domain", right_value_field: "time", operation: "sum"});
  989. assert.deepEqual(combined.combined_map, {"fake": 42});
  990. });
  991. it("should sum when both", () => {
  992. let right = makeItem();
  993. right.url_domain = "maseratiusa.com/maserati";
  994. right.time = 41;
  995. item.combined_map = {"fake": 42, "maseratiusa.com/maserati": 41};
  996. let combined = instance.combinerCollectValues(item, right,
  997. {left_field: "combined_map", right_key_field: "url_domain", right_value_field: "time", operation: "sum"});
  998. assert.deepEqual(combined.combined_map, {"fake": 42, "maseratiusa.com/maserati": 82});
  999. });
  1000. it("should max when missing left", () => {
  1001. let right = makeItem();
  1002. right.url_domain = "maseratiusa.com/maserati";
  1003. right.time = 41;
  1004. let combined = instance.combinerCollectValues(item, right,
  1005. {left_field: "combined_map", right_key_field: "url_domain", right_value_field: "time", operation: "max"});
  1006. assert.deepEqual(combined.combined_map, {"maseratiusa.com/maserati": 41});
  1007. });
  1008. it("should max when missing right", () => {
  1009. let right = makeItem();
  1010. item.combined_map = {"fake": 42};
  1011. let combined = instance.combinerCollectValues(item, right,
  1012. {left_field: "combined_map", right_key_field: "url_domain", right_value_field: "time", operation: "max"});
  1013. assert.deepEqual(combined.combined_map, {"fake": 42});
  1014. });
  1015. it("should max when both (right)", () => {
  1016. let right = makeItem();
  1017. right.url_domain = "maseratiusa.com/maserati";
  1018. right.time = 99;
  1019. item.combined_map = {"fake": 42, "maseratiusa.com/maserati": 41};
  1020. let combined = instance.combinerCollectValues(item, right,
  1021. {left_field: "combined_map", right_key_field: "url_domain", right_value_field: "time", operation: "max"});
  1022. assert.deepEqual(combined.combined_map, {"fake": 42, "maseratiusa.com/maserati": 99});
  1023. });
  1024. it("should max when both (left)", () => {
  1025. let right = makeItem();
  1026. right.url_domain = "maseratiusa.com/maserati";
  1027. right.time = -99;
  1028. item.combined_map = {"fake": 42, "maseratiusa.com/maserati": 41};
  1029. let combined = instance.combinerCollectValues(item, right,
  1030. {left_field: "combined_map", right_key_field: "url_domain", right_value_field: "time", operation: "max"});
  1031. assert.deepEqual(combined.combined_map, {"fake": 42, "maseratiusa.com/maserati": 41});
  1032. });
  1033. it("should overwrite when missing left", () => {
  1034. let right = makeItem();
  1035. right.url_domain = "maseratiusa.com/maserati";
  1036. right.time = 41;
  1037. let combined = instance.combinerCollectValues(item, right,
  1038. {left_field: "combined_map", right_key_field: "url_domain", right_value_field: "time", operation: "overwrite"});
  1039. assert.deepEqual(combined.combined_map, {"maseratiusa.com/maserati": 41});
  1040. });
  1041. it("should overwrite when missing right", () => {
  1042. let right = makeItem();
  1043. item.combined_map = {"fake": 42};
  1044. let combined = instance.combinerCollectValues(item, right,
  1045. {left_field: "combined_map", right_key_field: "url_domain", right_value_field: "time", operation: "overwrite"});
  1046. assert.deepEqual(combined.combined_map, {"fake": 42});
  1047. });
  1048. it("should overwrite when both", () => {
  1049. let right = makeItem();
  1050. right.url_domain = "maseratiusa.com/maserati";
  1051. right.time = 41;
  1052. item.combined_map = {"fake": 42, "maseratiusa.com/maserati": 77};
  1053. let combined = instance.combinerCollectValues(item, right,
  1054. {left_field: "combined_map", right_key_field: "url_domain", right_value_field: "time", operation: "overwrite"});
  1055. assert.deepEqual(combined.combined_map, {"fake": 42, "maseratiusa.com/maserati": 41});
  1056. });
  1057. it("should count when missing left", () => {
  1058. let right = makeItem();
  1059. right.url_domain = "maseratiusa.com/maserati";
  1060. right.time = 41;
  1061. let combined = instance.combinerCollectValues(item, right,
  1062. {left_field: "combined_map", right_key_field: "url_domain", right_value_field: "time", operation: "count"});
  1063. assert.deepEqual(combined.combined_map, {"maseratiusa.com/maserati": 1});
  1064. });
  1065. it("should count when missing right", () => {
  1066. let right = makeItem();
  1067. item.combined_map = {"fake": 42};
  1068. let combined = instance.combinerCollectValues(item, right,
  1069. {left_field: "combined_map", right_key_field: "url_domain", right_value_field: "time", operation: "count"});
  1070. assert.deepEqual(combined.combined_map, {"fake": 42});
  1071. });
  1072. it("should count when both", () => {
  1073. let right = makeItem();
  1074. right.url_domain = "maseratiusa.com/maserati";
  1075. right.time = 41;
  1076. item.combined_map = {"fake": 42, "maseratiusa.com/maserati": 1};
  1077. let combined = instance.combinerCollectValues(item, right,
  1078. {left_field: "combined_map", right_key_field: "url_domain", right_value_field: "time", operation: "count"});
  1079. assert.deepEqual(combined.combined_map, {"fake": 42, "maseratiusa.com/maserati": 2});
  1080. });
  1081. });
  1082. describe("#executeRecipe", () => {
  1083. it("should handle working steps", () => {
  1084. let final = instance.executeRecipe({}, [
  1085. {function: "set_default", field: "foo", value: 1},
  1086. {function: "set_default", field: "bar", value: 10},
  1087. ]);
  1088. assert.equal(final.foo, 1);
  1089. assert.equal(final.bar, 10);
  1090. });
  1091. it("should handle unknown steps", () => {
  1092. let final = instance.executeRecipe({}, [
  1093. {function: "set_default", field: "foo", value: 1},
  1094. {function: "missing"},
  1095. {function: "set_default", field: "bar", value: 10},
  1096. ]);
  1097. assert.equal(final, null);
  1098. });
  1099. it("should handle erroring steps", () => {
  1100. let final = instance.executeRecipe({}, [
  1101. {function: "set_default", field: "foo", value: 1},
  1102. {function: "accept_item_by_field_value", field: "missing", op: "invalid", rhsField: "moot", rhsValue: "m00t"},
  1103. {function: "set_default", field: "bar", value: 10},
  1104. ]);
  1105. assert.equal(final, null);
  1106. });
  1107. });
  1108. describe("#executeCombinerRecipe", () => {
  1109. it("should handle working steps", () => {
  1110. let final = instance.executeCombinerRecipe(
  1111. {foo: 1, bar: 10},
  1112. {foo: 1, bar: 10},
  1113. [
  1114. {function: "combiner_add", field: "foo"},
  1115. {function: "combiner_add", field: "bar"},
  1116. ]);
  1117. assert.equal(final.foo, 2);
  1118. assert.equal(final.bar, 20);
  1119. });
  1120. it("should handle unknown steps", () => {
  1121. let final = instance.executeCombinerRecipe(
  1122. {foo: 1, bar: 10},
  1123. {foo: 1, bar: 10},
  1124. [
  1125. {function: "combiner_add", field: "foo"},
  1126. {function: "missing"},
  1127. {function: "combiner_add", field: "bar"},
  1128. ]);
  1129. assert.equal(final, null);
  1130. });
  1131. it("should handle erroring steps", () => {
  1132. let final = instance.executeCombinerRecipe(
  1133. {foo: 1, bar: 10, baz: 0},
  1134. {foo: 1, bar: 10, baz: "hundred"},
  1135. [
  1136. {function: "combiner_add", field: "foo"},
  1137. {function: "combiner_add", field: "baz"},
  1138. {function: "combiner_add", field: "bar"},
  1139. ]);
  1140. assert.equal(final, null);
  1141. });
  1142. });
  1143. });