RecipeExecutor.test.js (50057B)
1 import { RecipeExecutor } from "lib/PersonalityProvider/RecipeExecutor.mjs"; 2 import { tokenize } from "lib/PersonalityProvider/Tokenize.mjs"; 3 4 class MockTagger { 5 constructor(mode, tagScoreMap) { 6 this.mode = mode; 7 this.tagScoreMap = tagScoreMap; 8 } 9 tagTokens() { 10 if (this.mode === "nb") { 11 // eslint-disable-next-line prefer-destructuring 12 let tag = Object.keys(this.tagScoreMap)[0]; 13 // eslint-disable-next-line prefer-destructuring 14 let prob = this.tagScoreMap[tag]; 15 let conf = prob >= 0.85; 16 return { 17 label: tag, 18 logProb: Math.log(prob), 19 confident: conf, 20 }; 21 } 22 return this.tagScoreMap; 23 } 24 tag(text) { 25 return this.tagTokens([text]); 26 } 27 } 28 29 describe("RecipeExecutor", () => { 30 let makeItem = () => { 31 let x = { 32 lhs: 2, 33 one: 1, 34 two: 2, 35 three: 3, 36 foo: "FOO", 37 bar: "BAR", 38 baz: ["one", "two", "three"], 39 qux: 42, 40 text: "This Is A_sentence.", 41 url: "http://www.wonder.example.com/dir1/dir2a-dir2b/dir3+4?key1&key2=val2&key3&%26amp=%3D3+4", 42 url2: "http://wonder.example.com/dir1/dir2a-dir2b/dir3+4?key1&key2=val2&key3&%26amp=%3D3+4", 43 map: { 44 c: 3, 45 a: 1, 46 b: 2, 47 }, 48 map2: { 49 b: 2, 50 c: 3, 51 d: 4, 52 }, 53 arr1: [2, 3, 4], 54 arr2: [3, 4, 5], 55 long: [3, 4, 5, 6, 7], 56 tags: { 57 a: { 58 aa: 0.1, 59 ab: 0.2, 60 ac: 0.3, 61 }, 62 b: { 63 ba: 4, 64 bb: 5, 65 bc: 6, 66 }, 67 }, 68 bogus: { 69 a: { 70 aa: "0.1", 71 ab: "0.2", 72 ac: "0.3", 73 }, 74 b: { 75 ba: "4", 76 bb: "5", 77 bc: "6", 78 }, 79 }, 80 zero: { 81 a: 0, 82 b: 0, 83 }, 84 zaro: [0, 0], 85 }; 86 return x; 87 }; 88 89 let EPSILON = 0.00001; 90 91 let instance = new RecipeExecutor( 92 [ 93 new MockTagger("nb", { tag1: 0.7 }), 94 new MockTagger("nb", { tag2: 0.86 }), 95 new MockTagger("nb", { tag3: 0.9 }), 96 new MockTagger("nb", { tag5: 0.9 }), 97 ], 98 { 99 tag1: new MockTagger("nmf", { 100 tag11: 0.9, 101 tag12: 0.8, 102 tag13: 0.7, 103 }), 104 tag2: new MockTagger("nmf", { 105 tag21: 0.8, 106 tag22: 0.7, 107 tag23: 0.6, 108 }), 109 tag3: new MockTagger("nmf", { 110 tag31: 0.7, 111 tag32: 0.6, 112 tag33: 0.5, 113 }), 114 tag4: new MockTagger("nmf", { tag41: 0.99 }), 115 }, 116 tokenize 117 ); 118 let item = null; 119 120 beforeEach(() => { 121 item = makeItem(); 122 }); 123 124 describe("#_assembleText", () => { 125 it("should simply copy a single string", () => { 126 assert.equal(instance._assembleText(item, ["foo"]), "FOO"); 127 }); 128 it("should append some strings with a space", () => { 129 assert.equal(instance._assembleText(item, ["foo", "bar"]), "FOO BAR"); 130 }); 131 it("should give an empty string for a missing field", () => { 132 assert.equal(instance._assembleText(item, ["missing"]), ""); 133 }); 134 it("should not double space an interior missing field", () => { 135 assert.equal( 136 instance._assembleText(item, ["foo", "missing", "bar"]), 137 "FOO BAR" 138 ); 139 }); 140 it("should splice in an array of strings", () => { 141 assert.equal( 142 instance._assembleText(item, ["foo", "baz", "bar"]), 143 "FOO one two three BAR" 144 ); 145 }); 146 it("should handle numbers", () => { 147 assert.equal( 148 instance._assembleText(item, ["foo", "qux", "bar"]), 149 "FOO 42 BAR" 150 ); 151 }); 152 }); 153 154 describe("#naiveBayesTag", () => { 155 it("should understand NaiveBayesTextTagger", () => { 156 item = instance.naiveBayesTag(item, { fields: ["text"] }); 157 assert.isTrue("nb_tags" in item); 158 assert.isTrue(!("tag1" in item.nb_tags)); 159 assert.equal(item.nb_tags.tag2, 0.86); 160 assert.equal(item.nb_tags.tag3, 0.9); 161 assert.equal(item.nb_tags.tag5, 0.9); 162 assert.isTrue("nb_tokens" in item); 163 assert.deepEqual(item.nb_tokens, ["this", "is", "a", "sentence"]); 164 assert.isTrue("nb_tags_extended" in item); 165 assert.isTrue(!("tag1" in item.nb_tags_extended)); 166 assert.deepEqual(item.nb_tags_extended.tag2, { 167 label: "tag2", 168 logProb: Math.log(0.86), 169 confident: true, 170 }); 171 assert.deepEqual(item.nb_tags_extended.tag3, { 172 label: "tag3", 173 logProb: Math.log(0.9), 174 confident: true, 175 }); 176 assert.deepEqual(item.nb_tags_extended.tag5, { 177 label: "tag5", 178 logProb: Math.log(0.9), 179 confident: true, 180 }); 181 assert.isTrue("nb_tokens" in item); 182 assert.deepEqual(item.nb_tokens, ["this", "is", "a", "sentence"]); 183 }); 184 }); 185 186 describe("#conditionallyNmfTag", () => { 187 it("should do nothing if it's not nb tagged", () => { 188 item = instance.conditionallyNmfTag(item, {}); 189 assert.equal(item, null); 190 }); 191 it("should populate nmf tags for the nb tags", () => { 192 item = instance.naiveBayesTag(item, { fields: ["text"] }); 193 item = instance.conditionallyNmfTag(item, {}); 194 assert.isTrue("nb_tags" in item); 195 assert.deepEqual(item.nmf_tags, { 196 tag2: { 197 tag21: 0.8, 198 tag22: 0.7, 199 tag23: 0.6, 200 }, 201 tag3: { 202 tag31: 0.7, 203 tag32: 0.6, 204 tag33: 0.5, 205 }, 206 }); 207 assert.deepEqual(item.nmf_tags_parent, { 208 tag21: "tag2", 209 tag22: "tag2", 210 tag23: "tag2", 211 tag31: "tag3", 212 tag32: "tag3", 213 tag33: "tag3", 214 }); 215 }); 216 it("should not populate nmf tags for things that were not nb tagged", () => { 217 item = instance.naiveBayesTag(item, { fields: ["text"] }); 218 item = instance.conditionallyNmfTag(item, {}); 219 assert.isTrue("nmf_tags" in item); 220 assert.isTrue(!("tag4" in item.nmf_tags)); 221 assert.isTrue("nmf_tags_parent" in item); 222 assert.isTrue(!("tag4" in item.nmf_tags_parent)); 223 }); 224 }); 225 226 describe("#acceptItemByFieldValue", () => { 227 it("should implement ==", () => { 228 assert.isTrue( 229 instance.acceptItemByFieldValue(item, { 230 field: "lhs", 231 op: "==", 232 rhsValue: 2, 233 }) !== null 234 ); 235 assert.isTrue( 236 instance.acceptItemByFieldValue(item, { 237 field: "lhs", 238 op: "==", 239 rhsValue: 3, 240 }) === null 241 ); 242 assert.isTrue( 243 instance.acceptItemByFieldValue(item, { 244 field: "lhs", 245 op: "==", 246 rhsField: "two", 247 }) !== null 248 ); 249 assert.isTrue( 250 instance.acceptItemByFieldValue(item, { 251 field: "lhs", 252 op: "==", 253 rhsField: "three", 254 }) === null 255 ); 256 }); 257 it("should implement !=", () => { 258 assert.isTrue( 259 instance.acceptItemByFieldValue(item, { 260 field: "lhs", 261 op: "!=", 262 rhsValue: 2, 263 }) === null 264 ); 265 assert.isTrue( 266 instance.acceptItemByFieldValue(item, { 267 field: "lhs", 268 op: "!=", 269 rhsValue: 3, 270 }) !== null 271 ); 272 }); 273 it("should implement < ", () => { 274 assert.isTrue( 275 instance.acceptItemByFieldValue(item, { 276 field: "lhs", 277 op: "<", 278 rhsValue: 1, 279 }) === null 280 ); 281 assert.isTrue( 282 instance.acceptItemByFieldValue(item, { 283 field: "lhs", 284 op: "<", 285 rhsValue: 2, 286 }) === null 287 ); 288 assert.isTrue( 289 instance.acceptItemByFieldValue(item, { 290 field: "lhs", 291 op: "<", 292 rhsValue: 3, 293 }) !== null 294 ); 295 }); 296 it("should implement <= ", () => { 297 assert.isTrue( 298 instance.acceptItemByFieldValue(item, { 299 field: "lhs", 300 op: "<=", 301 rhsValue: 1, 302 }) === null 303 ); 304 assert.isTrue( 305 instance.acceptItemByFieldValue(item, { 306 field: "lhs", 307 op: "<=", 308 rhsValue: 2, 309 }) !== null 310 ); 311 assert.isTrue( 312 instance.acceptItemByFieldValue(item, { 313 field: "lhs", 314 op: "<=", 315 rhsValue: 3, 316 }) !== null 317 ); 318 }); 319 it("should implement > ", () => { 320 assert.isTrue( 321 instance.acceptItemByFieldValue(item, { 322 field: "lhs", 323 op: ">", 324 rhsValue: 1, 325 }) !== null 326 ); 327 assert.isTrue( 328 instance.acceptItemByFieldValue(item, { 329 field: "lhs", 330 op: ">", 331 rhsValue: 2, 332 }) === null 333 ); 334 assert.isTrue( 335 instance.acceptItemByFieldValue(item, { 336 field: "lhs", 337 op: ">", 338 rhsValue: 3, 339 }) === null 340 ); 341 }); 342 it("should implement >= ", () => { 343 assert.isTrue( 344 instance.acceptItemByFieldValue(item, { 345 field: "lhs", 346 op: ">=", 347 rhsValue: 1, 348 }) !== null 349 ); 350 assert.isTrue( 351 instance.acceptItemByFieldValue(item, { 352 field: "lhs", 353 op: ">=", 354 rhsValue: 2, 355 }) !== null 356 ); 357 assert.isTrue( 358 instance.acceptItemByFieldValue(item, { 359 field: "lhs", 360 op: ">=", 361 rhsValue: 3, 362 }) === null 363 ); 364 }); 365 it("should skip items with missing fields", () => { 366 assert.isTrue( 367 instance.acceptItemByFieldValue(item, { 368 field: "no-left", 369 op: "==", 370 rhsValue: 1, 371 }) === null 372 ); 373 assert.isTrue( 374 instance.acceptItemByFieldValue(item, { 375 field: "lhs", 376 op: "==", 377 rhsField: "no-right", 378 }) === null 379 ); 380 assert.isTrue( 381 instance.acceptItemByFieldValue(item, { field: "lhs", op: "==" }) === 382 null 383 ); 384 }); 385 it("should skip items with bogus operators", () => { 386 assert.isTrue( 387 instance.acceptItemByFieldValue(item, { 388 field: "lhs", 389 op: "bogus", 390 rhsField: "two", 391 }) === null 392 ); 393 }); 394 }); 395 396 describe("#tokenizeUrl", () => { 397 it("should strip the leading www from a url", () => { 398 item = instance.tokenizeUrl(item, { field: "url", dest: "url_toks" }); 399 assert.deepEqual( 400 [ 401 "wonder", 402 "example", 403 "com", 404 "dir1", 405 "dir2a", 406 "dir2b", 407 "dir3", 408 "4", 409 "key1", 410 "key2", 411 "val2", 412 "key3", 413 "amp", 414 "3", 415 "4", 416 ], 417 item.url_toks 418 ); 419 }); 420 it("should tokenize the not strip the leading non-wwww token from a url", () => { 421 item = instance.tokenizeUrl(item, { field: "url2", dest: "url_toks" }); 422 assert.deepEqual( 423 [ 424 "wonder", 425 "example", 426 "com", 427 "dir1", 428 "dir2a", 429 "dir2b", 430 "dir3", 431 "4", 432 "key1", 433 "key2", 434 "val2", 435 "key3", 436 "amp", 437 "3", 438 "4", 439 ], 440 item.url_toks 441 ); 442 }); 443 it("should error for a missing url", () => { 444 item = instance.tokenizeUrl(item, { field: "missing", dest: "url_toks" }); 445 assert.equal(item, null); 446 }); 447 }); 448 449 describe("#getUrlDomain", () => { 450 it("should get only the hostname skipping the www", () => { 451 item = instance.getUrlDomain(item, { field: "url", dest: "url_domain" }); 452 assert.isTrue("url_domain" in item); 453 assert.deepEqual("wonder.example.com", item.url_domain); 454 }); 455 it("should get only the hostname", () => { 456 item = instance.getUrlDomain(item, { field: "url2", dest: "url_domain" }); 457 assert.isTrue("url_domain" in item); 458 assert.deepEqual("wonder.example.com", item.url_domain); 459 }); 460 it("should get the hostname and 2 levels of directories", () => { 461 item = instance.getUrlDomain(item, { 462 field: "url", 463 path_length: 2, 464 dest: "url_plus_2", 465 }); 466 assert.isTrue("url_plus_2" in item); 467 assert.deepEqual("wonder.example.com/dir1/dir2a-dir2b", item.url_plus_2); 468 }); 469 it("should error for a missing url", () => { 470 item = instance.getUrlDomain(item, { 471 field: "missing", 472 dest: "url_domain", 473 }); 474 assert.equal(item, null); 475 }); 476 }); 477 478 describe("#tokenizeField", () => { 479 it("should tokenize the field", () => { 480 item = instance.tokenizeField(item, { field: "text", dest: "toks" }); 481 assert.isTrue("toks" in item); 482 assert.deepEqual(["this", "is", "a", "sentence"], item.toks); 483 }); 484 it("should error for a missing field", () => { 485 item = instance.tokenizeField(item, { field: "missing", dest: "toks" }); 486 assert.equal(item, null); 487 }); 488 it("should error for a broken config", () => { 489 item = instance.tokenizeField(item, {}); 490 assert.equal(item, null); 491 }); 492 }); 493 494 describe("#_typeOf", () => { 495 it("should know this is a map", () => { 496 assert.equal(instance._typeOf({}), "map"); 497 }); 498 it("should know this is an array", () => { 499 assert.equal(instance._typeOf([]), "array"); 500 }); 501 it("should know this is a string", () => { 502 assert.equal(instance._typeOf("blah"), "string"); 503 }); 504 it("should know this is a boolean", () => { 505 assert.equal(instance._typeOf(true), "boolean"); 506 }); 507 508 it("should know this is a null", () => { 509 assert.equal(instance._typeOf(null), "null"); 510 }); 511 }); 512 513 describe("#_lookupScalar", () => { 514 it("should return the constant", () => { 515 assert.equal(instance._lookupScalar({}, 1, 0), 1); 516 }); 517 it("should return the default", () => { 518 assert.equal(instance._lookupScalar({}, "blah", 42), 42); 519 }); 520 it("should return the field's value", () => { 521 assert.equal(instance._lookupScalar({ blah: 11 }, "blah", 42), 11); 522 }); 523 }); 524 525 describe("#copyValue", () => { 526 it("should copy values", () => { 527 item = instance.copyValue(item, { src: "one", dest: "again" }); 528 assert.isTrue("again" in item); 529 assert.equal(item.again, 1); 530 item.one = 100; 531 assert.equal(item.one, 100); 532 assert.equal(item.again, 1); 533 }); 534 it("should handle maps corrects", () => { 535 item = instance.copyValue(item, { src: "map", dest: "again" }); 536 assert.deepEqual(item.again, { a: 1, b: 2, c: 3 }); 537 item.map.c = 100; 538 assert.deepEqual(item.again, { a: 1, b: 2, c: 3 }); 539 item.map = 342; 540 assert.deepEqual(item.again, { a: 1, b: 2, c: 3 }); 541 }); 542 it("should error for a missing field", () => { 543 item = instance.copyValue(item, { src: "missing", dest: "toks" }); 544 assert.equal(item, null); 545 }); 546 }); 547 548 describe("#keepTopK", () => { 549 it("should keep the 2 smallest", () => { 550 item = instance.keepTopK(item, { field: "map", k: 2, descending: false }); 551 assert.equal(Object.keys(item.map).length, 2); 552 assert.isTrue("a" in item.map); 553 assert.equal(item.map.a, 1); 554 assert.isTrue("b" in item.map); 555 assert.equal(item.map.b, 2); 556 assert.isTrue(!("c" in item.map)); 557 }); 558 it("should keep the 2 largest", () => { 559 item = instance.keepTopK(item, { field: "map", k: 2, descending: true }); 560 assert.equal(Object.keys(item.map).length, 2); 561 assert.isTrue(!("a" in item.map)); 562 assert.isTrue("b" in item.map); 563 assert.equal(item.map.b, 2); 564 assert.isTrue("c" in item.map); 565 assert.equal(item.map.c, 3); 566 }); 567 it("should still keep the 2 largest", () => { 568 item = instance.keepTopK(item, { field: "map", k: 2 }); 569 assert.equal(Object.keys(item.map).length, 2); 570 assert.isTrue(!("a" in item.map)); 571 assert.isTrue("b" in item.map); 572 assert.equal(item.map.b, 2); 573 assert.isTrue("c" in item.map); 574 assert.equal(item.map.c, 3); 575 }); 576 it("should promote up nested fields", () => { 577 item = instance.keepTopK(item, { field: "tags", k: 2 }); 578 assert.equal(Object.keys(item.tags).length, 2); 579 assert.deepEqual(item.tags, { bb: 5, bc: 6 }); 580 }); 581 it("should error for a missing field", () => { 582 item = instance.keepTopK(item, { field: "missing", k: 3 }); 583 assert.equal(item, null); 584 }); 585 }); 586 587 describe("#scalarMultiply", () => { 588 it("should use constants", () => { 589 item = instance.scalarMultiply(item, { field: "map", k: 2 }); 590 assert.equal(item.map.a, 2); 591 assert.equal(item.map.b, 4); 592 assert.equal(item.map.c, 6); 593 }); 594 it("should use fields", () => { 595 item = instance.scalarMultiply(item, { field: "map", k: "three" }); 596 assert.equal(item.map.a, 3); 597 assert.equal(item.map.b, 6); 598 assert.equal(item.map.c, 9); 599 }); 600 it("should use default", () => { 601 item = instance.scalarMultiply(item, { 602 field: "map", 603 k: "missing", 604 dfault: 4, 605 }); 606 assert.equal(item.map.a, 4); 607 assert.equal(item.map.b, 8); 608 assert.equal(item.map.c, 12); 609 }); 610 it("should error for a missing field", () => { 611 item = instance.scalarMultiply(item, { field: "missing", k: 3 }); 612 assert.equal(item, null); 613 }); 614 it("should multiply numbers", () => { 615 item = instance.scalarMultiply(item, { field: "lhs", k: 2 }); 616 assert.equal(item.lhs, 4); 617 }); 618 it("should multiply arrays", () => { 619 item = instance.scalarMultiply(item, { field: "arr1", k: 2 }); 620 assert.deepEqual(item.arr1, [4, 6, 8]); 621 }); 622 it("should should error on strings", () => { 623 item = instance.scalarMultiply(item, { field: "foo", k: 2 }); 624 assert.equal(item, null); 625 }); 626 }); 627 628 describe("#elementwiseMultiply", () => { 629 it("should handle maps", () => { 630 item = instance.elementwiseMultiply(item, { 631 left: "tags", 632 right: "map2", 633 }); 634 assert.deepEqual(item.tags, { 635 a: { aa: 0, ab: 0, ac: 0 }, 636 b: { ba: 8, bb: 10, bc: 12 }, 637 }); 638 }); 639 it("should handle arrays of same length", () => { 640 item = instance.elementwiseMultiply(item, { 641 left: "arr1", 642 right: "arr2", 643 }); 644 assert.deepEqual(item.arr1, [6, 12, 20]); 645 }); 646 it("should error for arrays of different lengths", () => { 647 item = instance.elementwiseMultiply(item, { 648 left: "arr1", 649 right: "long", 650 }); 651 assert.equal(item, null); 652 }); 653 it("should error for a missing left", () => { 654 item = instance.elementwiseMultiply(item, { 655 left: "missing", 656 right: "arr2", 657 }); 658 assert.equal(item, null); 659 }); 660 it("should error for a missing right", () => { 661 item = instance.elementwiseMultiply(item, { 662 left: "arr1", 663 right: "missing", 664 }); 665 assert.equal(item, null); 666 }); 667 it("should handle numbers", () => { 668 item = instance.elementwiseMultiply(item, { 669 left: "three", 670 right: "two", 671 }); 672 assert.equal(item.three, 6); 673 }); 674 it("should error for mismatched types", () => { 675 item = instance.elementwiseMultiply(item, { left: "arr1", right: "two" }); 676 assert.equal(item, null); 677 }); 678 it("should error for strings", () => { 679 item = instance.elementwiseMultiply(item, { left: "foo", right: "bar" }); 680 assert.equal(item, null); 681 }); 682 }); 683 684 describe("#vectorMultiply", () => { 685 it("should calculate dot products from maps", () => { 686 item = instance.vectorMultiply(item, { 687 left: "map", 688 right: "map2", 689 dest: "dot", 690 }); 691 assert.equal(item.dot, 13); 692 }); 693 it("should calculate dot products from arrays", () => { 694 item = instance.vectorMultiply(item, { 695 left: "arr1", 696 right: "arr2", 697 dest: "dot", 698 }); 699 assert.equal(item.dot, 38); 700 }); 701 it("should error for arrays of different lengths", () => { 702 item = instance.vectorMultiply(item, { left: "arr1", right: "long" }); 703 assert.equal(item, null); 704 }); 705 it("should error for a missing left", () => { 706 item = instance.vectorMultiply(item, { left: "missing", right: "arr2" }); 707 assert.equal(item, null); 708 }); 709 it("should error for a missing right", () => { 710 item = instance.vectorMultiply(item, { left: "arr1", right: "missing" }); 711 assert.equal(item, null); 712 }); 713 it("should error for mismatched types", () => { 714 item = instance.vectorMultiply(item, { left: "arr1", right: "two" }); 715 assert.equal(item, null); 716 }); 717 it("should error for strings", () => { 718 item = instance.vectorMultiply(item, { left: "foo", right: "bar" }); 719 assert.equal(item, null); 720 }); 721 }); 722 723 describe("#scalarAdd", () => { 724 it("should error for a missing field", () => { 725 item = instance.scalarAdd(item, { field: "missing", k: 10 }); 726 assert.equal(item, null); 727 }); 728 it("should error for strings", () => { 729 item = instance.scalarAdd(item, { field: "foo", k: 10 }); 730 assert.equal(item, null); 731 }); 732 it("should work for numbers", () => { 733 item = instance.scalarAdd(item, { field: "one", k: 10 }); 734 assert.equal(item.one, 11); 735 }); 736 it("should add a constant to every cell on a map", () => { 737 item = instance.scalarAdd(item, { field: "map", k: 10 }); 738 assert.deepEqual(item.map, { a: 11, b: 12, c: 13 }); 739 }); 740 it("should add a value from a field to every cell on a map", () => { 741 item = instance.scalarAdd(item, { field: "map", k: "qux" }); 742 assert.deepEqual(item.map, { a: 43, b: 44, c: 45 }); 743 }); 744 it("should add a constant to every cell on an array", () => { 745 item = instance.scalarAdd(item, { field: "arr1", k: 10 }); 746 assert.deepEqual(item.arr1, [12, 13, 14]); 747 }); 748 }); 749 750 describe("#vectorAdd", () => { 751 it("should calculate add vectors from maps", () => { 752 item = instance.vectorAdd(item, { left: "map", right: "map2" }); 753 assert.equal(Object.keys(item.map).length, 4); 754 assert.isTrue("a" in item.map); 755 assert.equal(item.map.a, 1); 756 assert.isTrue("b" in item.map); 757 assert.equal(item.map.b, 4); 758 assert.isTrue("c" in item.map); 759 assert.equal(item.map.c, 6); 760 assert.isTrue("d" in item.map); 761 assert.equal(item.map.d, 4); 762 }); 763 it("should work for missing left", () => { 764 item = instance.vectorAdd(item, { left: "missing", right: "arr2" }); 765 assert.deepEqual(item.missing, [3, 4, 5]); 766 }); 767 it("should error for missing right", () => { 768 item = instance.vectorAdd(item, { left: "arr2", right: "missing" }); 769 assert.equal(item, null); 770 }); 771 it("should error error for strings", () => { 772 item = instance.vectorAdd(item, { left: "foo", right: "bar" }); 773 assert.equal(item, null); 774 }); 775 it("should error for different types", () => { 776 item = instance.vectorAdd(item, { left: "arr2", right: "map" }); 777 assert.equal(item, null); 778 }); 779 it("should calculate add vectors from arrays", () => { 780 item = instance.vectorAdd(item, { left: "arr1", right: "arr2" }); 781 assert.deepEqual(item.arr1, [5, 7, 9]); 782 }); 783 it("should abort on different sized arrays", () => { 784 item = instance.vectorAdd(item, { left: "arr1", right: "long" }); 785 assert.equal(item, null); 786 }); 787 it("should calculate add vectors from arrays", () => { 788 item = instance.vectorAdd(item, { left: "arr1", right: "arr2" }); 789 assert.deepEqual(item.arr1, [5, 7, 9]); 790 }); 791 }); 792 793 describe("#makeBoolean", () => { 794 it("should error for missing field", () => { 795 item = instance.makeBoolean(item, { field: "missing", threshold: 2 }); 796 assert.equal(item, null); 797 }); 798 it("should 0/1 a map", () => { 799 item = instance.makeBoolean(item, { field: "map", threshold: 2 }); 800 assert.deepEqual(item.map, { a: 0, b: 0, c: 1 }); 801 }); 802 it("should a map of all 1s", () => { 803 item = instance.makeBoolean(item, { field: "map" }); 804 assert.deepEqual(item.map, { a: 1, b: 1, c: 1 }); 805 }); 806 it("should -1/1 a map", () => { 807 item = instance.makeBoolean(item, { 808 field: "map", 809 threshold: 2, 810 keep_negative: true, 811 }); 812 assert.deepEqual(item.map, { a: -1, b: -1, c: 1 }); 813 }); 814 it("should work an array", () => { 815 item = instance.makeBoolean(item, { field: "arr1", threshold: 3 }); 816 assert.deepEqual(item.arr1, [0, 0, 1]); 817 }); 818 it("should -1/1 an array", () => { 819 item = instance.makeBoolean(item, { 820 field: "arr1", 821 threshold: 3, 822 keep_negative: true, 823 }); 824 assert.deepEqual(item.arr1, [-1, -1, 1]); 825 }); 826 it("should 1 a high number", () => { 827 item = instance.makeBoolean(item, { field: "qux", threshold: 3 }); 828 assert.equal(item.qux, 1); 829 }); 830 it("should 0 a low number", () => { 831 item = instance.makeBoolean(item, { field: "qux", threshold: 70 }); 832 assert.equal(item.qux, 0); 833 }); 834 it("should -1 a low number", () => { 835 item = instance.makeBoolean(item, { 836 field: "qux", 837 threshold: 83, 838 keep_negative: true, 839 }); 840 assert.equal(item.qux, -1); 841 }); 842 it("should fail a string", () => { 843 item = instance.makeBoolean(item, { field: "foo", threshold: 3 }); 844 assert.equal(item, null); 845 }); 846 }); 847 848 describe("#allowFields", () => { 849 it("should filter the keys out of a map", () => { 850 item = instance.allowFields(item, { 851 fields: ["foo", "missing", "bar"], 852 }); 853 assert.deepEqual(item, { foo: "FOO", bar: "BAR" }); 854 }); 855 }); 856 857 describe("#filterByValue", () => { 858 it("should fail on missing field", () => { 859 item = instance.filterByValue(item, { field: "missing", threshold: 2 }); 860 assert.equal(item, null); 861 }); 862 it("should filter the keys out of a map", () => { 863 item = instance.filterByValue(item, { field: "map", threshold: 2 }); 864 assert.deepEqual(item.map, { c: 3 }); 865 }); 866 }); 867 868 describe("#l2Normalize", () => { 869 it("should fail on missing field", () => { 870 item = instance.l2Normalize(item, { field: "missing" }); 871 assert.equal(item, null); 872 }); 873 it("should L2 normalize an array", () => { 874 item = instance.l2Normalize(item, { field: "arr1" }); 875 assert.deepEqual( 876 item.arr1, 877 [0.3713906763541037, 0.5570860145311556, 0.7427813527082074] 878 ); 879 }); 880 it("should L2 normalize a map", () => { 881 item = instance.l2Normalize(item, { field: "map" }); 882 assert.deepEqual(item.map, { 883 a: 0.2672612419124244, 884 b: 0.5345224838248488, 885 c: 0.8017837257372732, 886 }); 887 }); 888 it("should fail a string", () => { 889 item = instance.l2Normalize(item, { field: "foo" }); 890 assert.equal(item, null); 891 }); 892 it("should not bomb on a zero vector", () => { 893 item = instance.l2Normalize(item, { field: "zero" }); 894 assert.deepEqual(item.zero, { a: 0, b: 0 }); 895 item = instance.l2Normalize(item, { field: "zaro" }); 896 assert.deepEqual(item.zaro, [0, 0]); 897 }); 898 }); 899 900 describe("#probNormalize", () => { 901 it("should fail on missing field", () => { 902 item = instance.probNormalize(item, { field: "missing" }); 903 assert.equal(item, null); 904 }); 905 it("should normalize an array to sum to 1", () => { 906 item = instance.probNormalize(item, { field: "arr1" }); 907 assert.deepEqual( 908 item.arr1, 909 [0.2222222222222222, 0.3333333333333333, 0.4444444444444444] 910 ); 911 }); 912 it("should normalize a map to sum to 1", () => { 913 item = instance.probNormalize(item, { field: "map" }); 914 assert.equal(Object.keys(item.map).length, 3); 915 assert.isTrue("a" in item.map); 916 assert.isTrue(Math.abs(item.map.a - 0.16667) <= EPSILON); 917 assert.isTrue("b" in item.map); 918 assert.isTrue(Math.abs(item.map.b - 0.33333) <= EPSILON); 919 assert.isTrue("c" in item.map); 920 assert.isTrue(Math.abs(item.map.c - 0.5) <= EPSILON); 921 }); 922 it("should fail a string", () => { 923 item = instance.probNormalize(item, { field: "foo" }); 924 assert.equal(item, null); 925 }); 926 it("should not bomb on a zero vector", () => { 927 item = instance.probNormalize(item, { field: "zero" }); 928 assert.deepEqual(item.zero, { a: 0, b: 0 }); 929 item = instance.probNormalize(item, { field: "zaro" }); 930 assert.deepEqual(item.zaro, [0, 0]); 931 }); 932 }); 933 934 describe("#scalarMultiplyTag", () => { 935 it("should fail on missing field", () => { 936 item = instance.scalarMultiplyTag(item, { field: "missing", k: 3 }); 937 assert.equal(item, null); 938 }); 939 it("should scalar multiply a nested map", () => { 940 item = instance.scalarMultiplyTag(item, { 941 field: "tags", 942 k: 3, 943 log_scale: false, 944 }); 945 assert.isTrue(Math.abs(item.tags.a.aa - 0.3) <= EPSILON); 946 assert.isTrue(Math.abs(item.tags.a.ab - 0.6) <= EPSILON); 947 assert.isTrue(Math.abs(item.tags.a.ac - 0.9) <= EPSILON); 948 assert.isTrue(Math.abs(item.tags.b.ba - 12) <= EPSILON); 949 assert.isTrue(Math.abs(item.tags.b.bb - 15) <= EPSILON); 950 assert.isTrue(Math.abs(item.tags.b.bc - 18) <= EPSILON); 951 }); 952 it("should scalar multiply a nested map with logrithms", () => { 953 item = instance.scalarMultiplyTag(item, { 954 field: "tags", 955 k: 3, 956 log_scale: true, 957 }); 958 assert.isTrue( 959 Math.abs(item.tags.a.aa - Math.log(0.1 + 0.000001) * 3) <= EPSILON 960 ); 961 assert.isTrue( 962 Math.abs(item.tags.a.ab - Math.log(0.2 + 0.000001) * 3) <= EPSILON 963 ); 964 assert.isTrue( 965 Math.abs(item.tags.a.ac - Math.log(0.3 + 0.000001) * 3) <= EPSILON 966 ); 967 assert.isTrue( 968 Math.abs(item.tags.b.ba - Math.log(4.0 + 0.000001) * 3) <= EPSILON 969 ); 970 assert.isTrue( 971 Math.abs(item.tags.b.bb - Math.log(5.0 + 0.000001) * 3) <= EPSILON 972 ); 973 assert.isTrue( 974 Math.abs(item.tags.b.bc - Math.log(6.0 + 0.000001) * 3) <= EPSILON 975 ); 976 }); 977 it("should fail a string", () => { 978 item = instance.scalarMultiplyTag(item, { field: "foo", k: 3 }); 979 assert.equal(item, null); 980 }); 981 }); 982 983 describe("#setDefault", () => { 984 it("should store a missing value", () => { 985 item = instance.setDefault(item, { field: "missing", value: 1111 }); 986 assert.equal(item.missing, 1111); 987 }); 988 it("should not overwrite an existing value", () => { 989 item = instance.setDefault(item, { field: "lhs", value: 1111 }); 990 assert.equal(item.lhs, 2); 991 }); 992 it("should store a complex value", () => { 993 item = instance.setDefault(item, { field: "missing", value: { a: 1 } }); 994 assert.deepEqual(item.missing, { a: 1 }); 995 }); 996 }); 997 998 describe("#lookupValue", () => { 999 it("should promote a value", () => { 1000 item = instance.lookupValue(item, { 1001 haystack: "map", 1002 needle: "c", 1003 dest: "ccc", 1004 }); 1005 assert.equal(item.ccc, 3); 1006 }); 1007 it("should handle a missing haystack", () => { 1008 item = instance.lookupValue(item, { 1009 haystack: "missing", 1010 needle: "c", 1011 dest: "ccc", 1012 }); 1013 assert.isTrue(!("ccc" in item)); 1014 }); 1015 it("should handle a missing needle", () => { 1016 item = instance.lookupValue(item, { 1017 haystack: "map", 1018 needle: "missing", 1019 dest: "ccc", 1020 }); 1021 assert.isTrue(!("ccc" in item)); 1022 }); 1023 }); 1024 1025 describe("#copyToMap", () => { 1026 it("should copy a value to a map", () => { 1027 item = instance.copyToMap(item, { 1028 src: "qux", 1029 dest_map: "map", 1030 dest_key: "zzz", 1031 }); 1032 assert.isTrue("zzz" in item.map); 1033 assert.equal(item.map.zzz, item.qux); 1034 }); 1035 it("should create a new map to hold the key", () => { 1036 item = instance.copyToMap(item, { 1037 src: "qux", 1038 dest_map: "missing", 1039 dest_key: "zzz", 1040 }); 1041 assert.equal(Object.keys(item.missing).length, 1); 1042 assert.equal(item.missing.zzz, item.qux); 1043 }); 1044 it("should not create an empty map if the src is missing", () => { 1045 item = instance.copyToMap(item, { 1046 src: "missing", 1047 dest_map: "no_map", 1048 dest_key: "zzz", 1049 }); 1050 assert.isTrue(!("no_map" in item)); 1051 }); 1052 }); 1053 1054 describe("#applySoftmaxTags", () => { 1055 it("should error on missing field", () => { 1056 item = instance.applySoftmaxTags(item, { field: "missing" }); 1057 assert.equal(item, null); 1058 }); 1059 it("should error on nonmaps", () => { 1060 item = instance.applySoftmaxTags(item, { field: "arr1" }); 1061 assert.equal(item, null); 1062 }); 1063 it("should error on unnested maps", () => { 1064 item = instance.applySoftmaxTags(item, { field: "map" }); 1065 assert.equal(item, null); 1066 }); 1067 it("should error on wrong nested maps", () => { 1068 item = instance.applySoftmaxTags(item, { field: "bogus" }); 1069 assert.equal(item, null); 1070 }); 1071 it("should apply softmax across the subtags", () => { 1072 item = instance.applySoftmaxTags(item, { field: "tags" }); 1073 assert.isTrue("a" in item.tags); 1074 assert.isTrue("aa" in item.tags.a); 1075 assert.isTrue("ab" in item.tags.a); 1076 assert.isTrue("ac" in item.tags.a); 1077 assert.isTrue(Math.abs(item.tags.a.aa - 0.30061) <= EPSILON); 1078 assert.isTrue(Math.abs(item.tags.a.ab - 0.33222) <= EPSILON); 1079 assert.isTrue(Math.abs(item.tags.a.ac - 0.36717) <= EPSILON); 1080 1081 assert.isTrue("b" in item.tags); 1082 assert.isTrue("ba" in item.tags.b); 1083 assert.isTrue("bb" in item.tags.b); 1084 assert.isTrue("bc" in item.tags.b); 1085 assert.isTrue(Math.abs(item.tags.b.ba - 0.09003) <= EPSILON); 1086 assert.isTrue(Math.abs(item.tags.b.bb - 0.24473) <= EPSILON); 1087 assert.isTrue(Math.abs(item.tags.b.bc - 0.66524) <= EPSILON); 1088 }); 1089 }); 1090 1091 describe("#combinerAdd", () => { 1092 it("should do nothing when right field is missing", () => { 1093 let right = makeItem(); 1094 let combined = instance.combinerAdd(item, right, { field: "missing" }); 1095 assert.deepEqual(combined, item); 1096 }); 1097 it("should handle missing left maps", () => { 1098 let right = makeItem(); 1099 right.missingmap = { a: 5, b: -1, c: 3 }; 1100 let combined = instance.combinerAdd(item, right, { field: "missingmap" }); 1101 assert.deepEqual(combined.missingmap, { a: 5, b: -1, c: 3 }); 1102 }); 1103 it("should add equal sized maps", () => { 1104 let right = makeItem(); 1105 let combined = instance.combinerAdd(item, right, { field: "map" }); 1106 assert.deepEqual(combined.map, { a: 2, b: 4, c: 6 }); 1107 }); 1108 it("should add long map to short map", () => { 1109 let right = makeItem(); 1110 right.map.d = 999; 1111 let combined = instance.combinerAdd(item, right, { field: "map" }); 1112 assert.deepEqual(combined.map, { a: 2, b: 4, c: 6, d: 999 }); 1113 }); 1114 it("should add short map to long map", () => { 1115 let right = makeItem(); 1116 item.map.d = 999; 1117 let combined = instance.combinerAdd(item, right, { field: "map" }); 1118 assert.deepEqual(combined.map, { a: 2, b: 4, c: 6, d: 999 }); 1119 }); 1120 it("should add equal sized arrays", () => { 1121 let right = makeItem(); 1122 let combined = instance.combinerAdd(item, right, { field: "arr1" }); 1123 assert.deepEqual(combined.arr1, [4, 6, 8]); 1124 }); 1125 it("should handle missing left arrays", () => { 1126 let right = makeItem(); 1127 right.missingarray = [5, 1, 4]; 1128 let combined = instance.combinerAdd(item, right, { 1129 field: "missingarray", 1130 }); 1131 assert.deepEqual(combined.missingarray, [5, 1, 4]); 1132 }); 1133 it("should add long array to short array", () => { 1134 let right = makeItem(); 1135 right.arr1 = [2, 3, 4, 12]; 1136 let combined = instance.combinerAdd(item, right, { field: "arr1" }); 1137 assert.deepEqual(combined.arr1, [4, 6, 8, 12]); 1138 }); 1139 it("should add short array to long array", () => { 1140 let right = makeItem(); 1141 item.arr1 = [2, 3, 4, 12]; 1142 let combined = instance.combinerAdd(item, right, { field: "arr1" }); 1143 assert.deepEqual(combined.arr1, [4, 6, 8, 12]); 1144 }); 1145 it("should handle missing left number", () => { 1146 let right = makeItem(); 1147 right.missingnumber = 999; 1148 let combined = instance.combinerAdd(item, right, { 1149 field: "missingnumber", 1150 }); 1151 assert.deepEqual(combined.missingnumber, 999); 1152 }); 1153 it("should add numbers", () => { 1154 let right = makeItem(); 1155 let combined = instance.combinerAdd(item, right, { field: "lhs" }); 1156 assert.equal(combined.lhs, 4); 1157 }); 1158 it("should error on missing left, and right is a string", () => { 1159 let right = makeItem(); 1160 right.error = "error"; 1161 let combined = instance.combinerAdd(item, right, { field: "error" }); 1162 assert.equal(combined, null); 1163 }); 1164 it("should error on left string", () => { 1165 let right = makeItem(); 1166 let combined = instance.combinerAdd(item, right, { field: "foo" }); 1167 assert.equal(combined, null); 1168 }); 1169 it("should error on mismatch types", () => { 1170 let right = makeItem(); 1171 right.lhs = [1, 2, 3]; 1172 let combined = instance.combinerAdd(item, right, { field: "lhs" }); 1173 assert.equal(combined, null); 1174 }); 1175 }); 1176 1177 describe("#combinerMax", () => { 1178 it("should do nothing when right field is missing", () => { 1179 let right = makeItem(); 1180 let combined = instance.combinerMax(item, right, { field: "missing" }); 1181 assert.deepEqual(combined, item); 1182 }); 1183 it("should handle missing left maps", () => { 1184 let right = makeItem(); 1185 right.missingmap = { a: 5, b: -1, c: 3 }; 1186 let combined = instance.combinerMax(item, right, { field: "missingmap" }); 1187 assert.deepEqual(combined.missingmap, { a: 5, b: -1, c: 3 }); 1188 }); 1189 it("should handle equal sized maps", () => { 1190 let right = makeItem(); 1191 right.map = { a: 5, b: -1, c: 3 }; 1192 let combined = instance.combinerMax(item, right, { field: "map" }); 1193 assert.deepEqual(combined.map, { a: 5, b: 2, c: 3 }); 1194 }); 1195 it("should handle short map to long map", () => { 1196 let right = makeItem(); 1197 right.map = { a: 5, b: -1, c: 3, d: 999 }; 1198 let combined = instance.combinerMax(item, right, { field: "map" }); 1199 assert.deepEqual(combined.map, { a: 5, b: 2, c: 3, d: 999 }); 1200 }); 1201 it("should handle long map to short map", () => { 1202 let right = makeItem(); 1203 right.map = { a: 5, b: -1, c: 3 }; 1204 item.map.d = 999; 1205 let combined = instance.combinerMax(item, right, { field: "map" }); 1206 assert.deepEqual(combined.map, { a: 5, b: 2, c: 3, d: 999 }); 1207 }); 1208 it("should handle equal sized arrays", () => { 1209 let right = makeItem(); 1210 right.arr1 = [5, 1, 4]; 1211 let combined = instance.combinerMax(item, right, { field: "arr1" }); 1212 assert.deepEqual(combined.arr1, [5, 3, 4]); 1213 }); 1214 it("should handle missing left arrays", () => { 1215 let right = makeItem(); 1216 right.missingarray = [5, 1, 4]; 1217 let combined = instance.combinerMax(item, right, { 1218 field: "missingarray", 1219 }); 1220 assert.deepEqual(combined.missingarray, [5, 1, 4]); 1221 }); 1222 it("should handle short array to long array", () => { 1223 let right = makeItem(); 1224 right.arr1 = [5, 1, 4, 7]; 1225 let combined = instance.combinerMax(item, right, { field: "arr1" }); 1226 assert.deepEqual(combined.arr1, [5, 3, 4, 7]); 1227 }); 1228 it("should handle long array to short array", () => { 1229 let right = makeItem(); 1230 right.arr1 = [5, 1, 4]; 1231 item.arr1.push(7); 1232 let combined = instance.combinerMax(item, right, { field: "arr1" }); 1233 assert.deepEqual(combined.arr1, [5, 3, 4, 7]); 1234 }); 1235 it("should handle missing left number", () => { 1236 let right = makeItem(); 1237 right.missingnumber = 999; 1238 let combined = instance.combinerMax(item, right, { 1239 field: "missingnumber", 1240 }); 1241 assert.deepEqual(combined.missingnumber, 999); 1242 }); 1243 it("should handle big number", () => { 1244 let right = makeItem(); 1245 right.lhs = 99; 1246 let combined = instance.combinerMax(item, right, { field: "lhs" }); 1247 assert.equal(combined.lhs, 99); 1248 }); 1249 it("should handle small number", () => { 1250 let right = makeItem(); 1251 item.lhs = 99; 1252 let combined = instance.combinerMax(item, right, { field: "lhs" }); 1253 assert.equal(combined.lhs, 99); 1254 }); 1255 it("should error on missing left, and right is a string", () => { 1256 let right = makeItem(); 1257 right.error = "error"; 1258 let combined = instance.combinerMax(item, right, { field: "error" }); 1259 assert.equal(combined, null); 1260 }); 1261 it("should error on left string", () => { 1262 let right = makeItem(); 1263 let combined = instance.combinerMax(item, right, { field: "foo" }); 1264 assert.equal(combined, null); 1265 }); 1266 it("should error on mismatch types", () => { 1267 let right = makeItem(); 1268 right.lhs = [1, 2, 3]; 1269 let combined = instance.combinerMax(item, right, { field: "lhs" }); 1270 assert.equal(combined, null); 1271 }); 1272 }); 1273 1274 describe("#combinerCollectValues", () => { 1275 it("should error on bogus operation", () => { 1276 let right = makeItem(); 1277 right.url_domain = "maseratiusa.com/maserati"; 1278 right.time = 41; 1279 let combined = instance.combinerCollectValues(item, right, { 1280 left_field: "combined_map", 1281 right_key_field: "url_domain", 1282 right_value_field: "time", 1283 operation: "missing", 1284 }); 1285 assert.equal(combined, null); 1286 }); 1287 it("should sum when missing left", () => { 1288 let right = makeItem(); 1289 right.url_domain = "maseratiusa.com/maserati"; 1290 right.time = 41; 1291 let combined = instance.combinerCollectValues(item, right, { 1292 left_field: "combined_map", 1293 right_key_field: "url_domain", 1294 right_value_field: "time", 1295 operation: "sum", 1296 }); 1297 assert.deepEqual(combined.combined_map, { 1298 "maseratiusa.com/maserati": 41, 1299 }); 1300 }); 1301 it("should sum when missing right", () => { 1302 let right = makeItem(); 1303 item.combined_map = { fake: 42 }; 1304 let combined = instance.combinerCollectValues(item, right, { 1305 left_field: "combined_map", 1306 right_key_field: "url_domain", 1307 right_value_field: "time", 1308 operation: "sum", 1309 }); 1310 assert.deepEqual(combined.combined_map, { fake: 42 }); 1311 }); 1312 it("should sum when both", () => { 1313 let right = makeItem(); 1314 right.url_domain = "maseratiusa.com/maserati"; 1315 right.time = 41; 1316 item.combined_map = { fake: 42, "maseratiusa.com/maserati": 41 }; 1317 let combined = instance.combinerCollectValues(item, right, { 1318 left_field: "combined_map", 1319 right_key_field: "url_domain", 1320 right_value_field: "time", 1321 operation: "sum", 1322 }); 1323 assert.deepEqual(combined.combined_map, { 1324 fake: 42, 1325 "maseratiusa.com/maserati": 82, 1326 }); 1327 }); 1328 1329 it("should max when missing left", () => { 1330 let right = makeItem(); 1331 right.url_domain = "maseratiusa.com/maserati"; 1332 right.time = 41; 1333 let combined = instance.combinerCollectValues(item, right, { 1334 left_field: "combined_map", 1335 right_key_field: "url_domain", 1336 right_value_field: "time", 1337 operation: "max", 1338 }); 1339 assert.deepEqual(combined.combined_map, { 1340 "maseratiusa.com/maserati": 41, 1341 }); 1342 }); 1343 it("should max when missing right", () => { 1344 let right = makeItem(); 1345 item.combined_map = { fake: 42 }; 1346 let combined = instance.combinerCollectValues(item, right, { 1347 left_field: "combined_map", 1348 right_key_field: "url_domain", 1349 right_value_field: "time", 1350 operation: "max", 1351 }); 1352 assert.deepEqual(combined.combined_map, { fake: 42 }); 1353 }); 1354 it("should max when both (right)", () => { 1355 let right = makeItem(); 1356 right.url_domain = "maseratiusa.com/maserati"; 1357 right.time = 99; 1358 item.combined_map = { fake: 42, "maseratiusa.com/maserati": 41 }; 1359 let combined = instance.combinerCollectValues(item, right, { 1360 left_field: "combined_map", 1361 right_key_field: "url_domain", 1362 right_value_field: "time", 1363 operation: "max", 1364 }); 1365 assert.deepEqual(combined.combined_map, { 1366 fake: 42, 1367 "maseratiusa.com/maserati": 99, 1368 }); 1369 }); 1370 it("should max when both (left)", () => { 1371 let right = makeItem(); 1372 right.url_domain = "maseratiusa.com/maserati"; 1373 right.time = -99; 1374 item.combined_map = { fake: 42, "maseratiusa.com/maserati": 41 }; 1375 let combined = instance.combinerCollectValues(item, right, { 1376 left_field: "combined_map", 1377 right_key_field: "url_domain", 1378 right_value_field: "time", 1379 operation: "max", 1380 }); 1381 assert.deepEqual(combined.combined_map, { 1382 fake: 42, 1383 "maseratiusa.com/maserati": 41, 1384 }); 1385 }); 1386 1387 it("should overwrite when missing left", () => { 1388 let right = makeItem(); 1389 right.url_domain = "maseratiusa.com/maserati"; 1390 right.time = 41; 1391 let combined = instance.combinerCollectValues(item, right, { 1392 left_field: "combined_map", 1393 right_key_field: "url_domain", 1394 right_value_field: "time", 1395 operation: "overwrite", 1396 }); 1397 assert.deepEqual(combined.combined_map, { 1398 "maseratiusa.com/maserati": 41, 1399 }); 1400 }); 1401 it("should overwrite when missing right", () => { 1402 let right = makeItem(); 1403 item.combined_map = { fake: 42 }; 1404 let combined = instance.combinerCollectValues(item, right, { 1405 left_field: "combined_map", 1406 right_key_field: "url_domain", 1407 right_value_field: "time", 1408 operation: "overwrite", 1409 }); 1410 assert.deepEqual(combined.combined_map, { fake: 42 }); 1411 }); 1412 it("should overwrite when both", () => { 1413 let right = makeItem(); 1414 right.url_domain = "maseratiusa.com/maserati"; 1415 right.time = 41; 1416 item.combined_map = { fake: 42, "maseratiusa.com/maserati": 77 }; 1417 let combined = instance.combinerCollectValues(item, right, { 1418 left_field: "combined_map", 1419 right_key_field: "url_domain", 1420 right_value_field: "time", 1421 operation: "overwrite", 1422 }); 1423 assert.deepEqual(combined.combined_map, { 1424 fake: 42, 1425 "maseratiusa.com/maserati": 41, 1426 }); 1427 }); 1428 1429 it("should count when missing left", () => { 1430 let right = makeItem(); 1431 right.url_domain = "maseratiusa.com/maserati"; 1432 right.time = 41; 1433 let combined = instance.combinerCollectValues(item, right, { 1434 left_field: "combined_map", 1435 right_key_field: "url_domain", 1436 right_value_field: "time", 1437 operation: "count", 1438 }); 1439 assert.deepEqual(combined.combined_map, { 1440 "maseratiusa.com/maserati": 1, 1441 }); 1442 }); 1443 it("should count when missing right", () => { 1444 let right = makeItem(); 1445 item.combined_map = { fake: 42 }; 1446 let combined = instance.combinerCollectValues(item, right, { 1447 left_field: "combined_map", 1448 right_key_field: "url_domain", 1449 right_value_field: "time", 1450 operation: "count", 1451 }); 1452 assert.deepEqual(combined.combined_map, { fake: 42 }); 1453 }); 1454 it("should count when both", () => { 1455 let right = makeItem(); 1456 right.url_domain = "maseratiusa.com/maserati"; 1457 right.time = 41; 1458 item.combined_map = { fake: 42, "maseratiusa.com/maserati": 1 }; 1459 let combined = instance.combinerCollectValues(item, right, { 1460 left_field: "combined_map", 1461 right_key_field: "url_domain", 1462 right_value_field: "time", 1463 operation: "count", 1464 }); 1465 assert.deepEqual(combined.combined_map, { 1466 fake: 42, 1467 "maseratiusa.com/maserati": 2, 1468 }); 1469 }); 1470 }); 1471 1472 describe("#executeRecipe", () => { 1473 it("should handle working steps", () => { 1474 let final = instance.executeRecipe({}, [ 1475 { function: "set_default", field: "foo", value: 1 }, 1476 { function: "set_default", field: "bar", value: 10 }, 1477 ]); 1478 assert.equal(final.foo, 1); 1479 assert.equal(final.bar, 10); 1480 }); 1481 it("should handle unknown steps", () => { 1482 let final = instance.executeRecipe({}, [ 1483 { function: "set_default", field: "foo", value: 1 }, 1484 { function: "missing" }, 1485 { function: "set_default", field: "bar", value: 10 }, 1486 ]); 1487 assert.equal(final, null); 1488 }); 1489 it("should handle erroring steps", () => { 1490 let final = instance.executeRecipe({}, [ 1491 { function: "set_default", field: "foo", value: 1 }, 1492 { 1493 function: "accept_item_by_field_value", 1494 field: "missing", 1495 op: "invalid", 1496 rhsField: "moot", 1497 rhsValue: "m00t", 1498 }, 1499 { function: "set_default", field: "bar", value: 10 }, 1500 ]); 1501 assert.equal(final, null); 1502 }); 1503 }); 1504 1505 describe("#executeCombinerRecipe", () => { 1506 it("should handle working steps", () => { 1507 let final = instance.executeCombinerRecipe( 1508 { foo: 1, bar: 10 }, 1509 { foo: 1, bar: 10 }, 1510 [ 1511 { function: "combiner_add", field: "foo" }, 1512 { function: "combiner_add", field: "bar" }, 1513 ] 1514 ); 1515 assert.equal(final.foo, 2); 1516 assert.equal(final.bar, 20); 1517 }); 1518 it("should handle unknown steps", () => { 1519 let final = instance.executeCombinerRecipe( 1520 { foo: 1, bar: 10 }, 1521 { foo: 1, bar: 10 }, 1522 [ 1523 { function: "combiner_add", field: "foo" }, 1524 { function: "missing" }, 1525 { function: "combiner_add", field: "bar" }, 1526 ] 1527 ); 1528 assert.equal(final, null); 1529 }); 1530 it("should handle erroring steps", () => { 1531 let final = instance.executeCombinerRecipe( 1532 { foo: 1, bar: 10, baz: 0 }, 1533 { foo: 1, bar: 10, baz: "hundred" }, 1534 [ 1535 { function: "combiner_add", field: "foo" }, 1536 { function: "combiner_add", field: "baz" }, 1537 { function: "combiner_add", field: "bar" }, 1538 ] 1539 ); 1540 assert.equal(final, null); 1541 }); 1542 }); 1543 });