├── .eslintrc ├── .gitignore ├── .travis.yml ├── README.md ├── docker.md ├── index.js ├── lib ├── bulker.js ├── client.js ├── index.js ├── mapping.js └── utils.js ├── package-lock.json ├── package.json ├── scripts └── docker │ ├── es2 │ ├── Dockerfile │ └── elasticsearch.yml │ ├── es5 │ ├── Dockerfile │ └── elasticsearch.yml │ ├── es6 │ ├── Dockerfile │ └── elasticsearch.yml │ ├── es7 │ ├── Dockerfile │ └── elasticsearch.yml │ └── start.js ├── test ├── .eslintrc ├── es2 │ ├── countOnly.js │ ├── customIds.js │ ├── document-hook.js │ ├── esCount.js │ ├── esIndex.js │ ├── esRefresh.js │ ├── esRemove.js │ ├── esSearch.js │ ├── esSynchronise.js │ ├── es_extend.js │ ├── es_value.js │ ├── hydratation.js │ ├── idsOnly.js │ ├── model-mapping.js │ └── promise.js ├── es5 │ ├── countOnly.js │ ├── customIds.js │ ├── document-hook.js │ ├── esCount.js │ ├── esIndex.js │ ├── esRefresh.js │ ├── esRemove.js │ ├── esSearch.js │ ├── esSynchronise.js │ ├── es_extend.js │ ├── es_value.js │ ├── hydratation.js │ ├── idsOnly.js │ ├── model-mapping.js │ ├── promise.js │ └── withDiscriminators.js ├── es6 │ ├── countOnly.js │ ├── customIds.js │ ├── document-hook.js │ ├── esCount.js │ ├── esIndex.js │ ├── esRefresh.js │ ├── esRemove.js │ ├── esSearch.js │ ├── esSynchronise.js │ ├── es_extend.js │ ├── es_value.js │ ├── hydratation.js │ ├── idsOnly.js │ ├── model-mapping.js │ ├── promise.js │ └── withDiscriminators.js ├── es7 │ ├── countOnly.js │ ├── customIds.js │ ├── document-hook.js │ ├── esCount.js │ ├── esIndex.js │ ├── esRefresh.js │ ├── esRemove.js │ ├── esSearch.js │ ├── esSynchronise.js │ ├── es_extend.js │ ├── es_value.js │ ├── hydratation.js │ ├── idsOnly.js │ ├── model-mapping.js │ ├── promise.js │ └── withDiscriminators.js └── utils │ └── index.js └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "airbnb-base", 4 | "prettier" 5 | ], 6 | "rules": { 7 | "prettier/prettier": ["error", { 8 | "trailingComma": "es5", 9 | "singleQuote": true, 10 | "printWidth": 80 11 | }], 12 | "no-underscore-dangle": 0, 13 | "no-plusplus": 0, 14 | "no-use-before-define": 0, 15 | "func-names": 0, 16 | "no-shadow": 0, 17 | "consistent-return": 0, 18 | "no-restricted-syntax": 0, 19 | "no-multi-assign": 0, 20 | "prefer-rest-params": 0, 21 | "no-param-reassign": 0, 22 | "strict": 0, 23 | "import/no-cycle": 0, 24 | "prefer-spread": 0, 25 | "prefer-destructuring": 0 26 | }, 27 | "env": { 28 | "mocha": true 29 | }, 30 | "plugins": [ 31 | "prettier", 32 | "chai-expect" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .idea 4 | coverage 5 | .nyc_output 6 | npm-debug.log 7 | .vscode -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | yarn: true 4 | directories: 5 | - node_modules 6 | 7 | node_js: 8 | - "10" 9 | - "8" 10 | 11 | addons: 12 | apt: 13 | packages: 14 | - oracle-java9-set-default 15 | 16 | services: 17 | - mongodb 18 | - elasticsearch 19 | 20 | env: 21 | - ES=2 22 | - ES=5 23 | - ES=6 24 | - ES=7 25 | 26 | before_install: 27 | - if [ "$ES" -eq "2" ]; then echo "DOWNLOADING V2"; curl -O https://download.elasticsearch.org/elasticsearch/release/org/elasticsearch/distribution/deb/elasticsearch/2.2.0/elasticsearch-2.2.0.deb && sudo dpkg -i --force-confnew elasticsearch-2.2.0.deb ; fi 28 | - if [ "$ES" -eq "5" ]; then echo "DOWNLOADING V5"; curl -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.0.0.deb && sudo dpkg -i --force-confnew elasticsearch-5.0.0.deb ; fi 29 | - if [ "$ES" -eq "6" ]; then echo "DOWNLOADING V6"; curl -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.0.0.deb && sudo dpkg -i --force-confnew elasticsearch-6.0.0.deb ; fi 30 | - if [ "$ES" -eq "7" ]; then echo "DOWNLOADING V7"; curl -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.0.1-amd64.deb && sudo dpkg -i --force-confnew elasticsearch-7.0.1-amd64.deb ; fi 31 | - echo `pwd` 32 | - sudo cat ./scripts/docker/es${ES}/elasticsearch.yml |sudo tee -a /etc/elasticsearch/elasticsearch.yml 33 | - sudo cat /etc/elasticsearch/elasticsearch.yml 34 | - sudo chown -R elasticsearch:elasticsearch /etc/default/elasticsearch # https://discuss.elastic.co/t/permission-denied-starting-elasticsearch-7-0/179336 35 | - sudo chown -R elasticsearch:elasticsearch /etc/elasticsearch 36 | - sudo chown -R elasticsearch:elasticsearch /usr/share/elasticsearch 37 | - if [ "$ES" -eq "5" -o "$ES" -eq "6" ];then sudo service elasticsearch start; fi 38 | - if [ "$ES" -eq "2" -o "$ES" -eq "7" ];then sudo service elasticsearch restart; fi 39 | - sleep 15 40 | - sudo tail -n50 /var/log/elasticsearch/elasticsearch_xp.log || true # Don't die on this 41 | - sudo service elasticsearch status 42 | 43 | script: 44 | - if [ "$ES" -eq "2" ]; then echo "RUNNING V2"; npm run-script test-v2-covered ; fi 45 | - if [ "$ES" -eq "5" ]; then echo "RUNNING V5"; npm run-script test-v5-covered ; fi 46 | - if [ "$ES" -eq "6" ]; then echo "RUNNING V6"; npm run-script test-v6-covered ; fi 47 | - if [ "$ES" -eq "7" ]; then echo "RUNNING V7"; npm run-script test-v7-covered ; fi 48 | 49 | 50 | 51 | 52 | 53 | after_success: 54 | - 'curl -Lo travis_after_all.py https://git.io/travis_after_all' 55 | - python travis_after_all.py 56 | - export $(cat .to_export_back) &> /dev/null 57 | - if [[ "$TRAVIS_JOB_NUMBER" == *.1 ]]; then cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js; fi 58 | - if [[ "$TRAVIS_JOB_NUMBER" == *.1 && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == "false" ]]; then npm run semantic-release; fi 59 | -------------------------------------------------------------------------------- /docker.md: -------------------------------------------------------------------------------- 1 | Steps to build a docker machine based on elasticsearch with scripting inline activated 2 | 3 | 4 | cat elasticsearch.yml 5 | 6 | script.inline: true 7 | script.indexed: true 8 | network.host: 0.0.0.0 9 | 10 | 11 | cat Dockerfile 12 | 13 | from elasticsearch 14 | copy elasticsearch.yml /usr/share/elasticsearch/config/elasticsearch.yml 15 | 16 | 17 | Build the image 18 | 19 | docker build -t elasticsearch-script . 20 | [...] 21 | Successfully built 8faedfb9d4f8 <= get the tag 22 | 23 | 24 | Run it 25 | 26 | docker run -p 9200:9200 8faedfb9d4f8 27 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/index.js'); 2 | -------------------------------------------------------------------------------- /lib/bulker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EventEmitter = require('events').EventEmitter; 4 | 5 | module.exports = Bulker; 6 | 7 | /** 8 | * Bulk buffer handler 9 | * @param {Object} client - Elasticsearch client 10 | * @param {Object} [options] - bulk options 11 | * @param {Number} [options.delay=1000] - delay before sending the buffer when buffer is not full 12 | * @param {Number} [options.size=1000] - buffer size before sending without waiting delay 13 | * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter 14 | * @event `error`: Emitted when bulk return an error. 15 | * @event `sent`: Emitted when bulk has sent a buffer. 16 | */ 17 | function Bulker(client, options) { 18 | options = options || {}; // eslint-disable-line 19 | 20 | let timeout; 21 | let flushing = false; 22 | const self = this; 23 | const delay = options.delay || 1000; 24 | const size = options.size || 1000; 25 | let buffer = []; 26 | 27 | self.push = function() { 28 | let sending = false; 29 | if (arguments.length) { 30 | buffer.push.apply(buffer, arguments); 31 | sending = buffer.length >= size; 32 | if (sending) { 33 | self.flush(); 34 | } else { 35 | self.delay(); 36 | } 37 | } 38 | return sending; 39 | }; 40 | 41 | self.delay = function() { 42 | clearTimeout(timeout); 43 | timeout = setTimeout(() => { 44 | self.flush(); 45 | }, delay); 46 | }; 47 | 48 | self.filled = function() { 49 | return !!buffer.length; 50 | }; 51 | 52 | self.isFlushing = function() { 53 | return flushing; 54 | }; 55 | 56 | self.flush = function() { 57 | const len = buffer.length; 58 | clearTimeout(timeout); 59 | if (len) { 60 | flushing = true; 61 | client.bulk({ body: buffer }, err => { 62 | flushing = false; 63 | if (err) { 64 | self.emit('error', err); 65 | } else { 66 | self.emit('sent', len); 67 | } 68 | }); 69 | buffer = []; 70 | } 71 | }; 72 | } 73 | 74 | Bulker.prototype = Object.create(EventEmitter.prototype); 75 | Bulker.prototype.constructor = Bulker; 76 | -------------------------------------------------------------------------------- /lib/client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const elasticsearch = require('elasticsearch'); 4 | 5 | module.exports = function(options) { 6 | const opts = {}; 7 | 8 | if (Array.isArray(options.hosts)) { 9 | opts.host = options.hosts; 10 | } else { 11 | opts.host = { 12 | host: options.host || '127.0.0.1', 13 | port: options.port || 9200, 14 | protocol: options.protocol || 'http', 15 | auth: options.auth || null, 16 | keepAlive: false, 17 | }; 18 | } 19 | 20 | opts.log = options.log || null; 21 | 22 | return new elasticsearch.Client(opts); 23 | }; 24 | -------------------------------------------------------------------------------- /lib/mapping.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const utils = require('./utils'); 4 | 5 | module.exports = {}; 6 | 7 | module.exports.generate = generate; 8 | 9 | /** 10 | * Generate mapping 11 | * @param {Object} schema - mongoose schema 12 | * @param {integer} [version] 13 | * @returns {Object} 14 | */ 15 | function generate(schema, version) { 16 | const mapping = {}; 17 | const typeKey = getTypeKey(schema); 18 | const explicit = hasExplicit(schema, typeKey); 19 | const defaultTypes = getDefault(version); 20 | 21 | Object.keys(schema.paths).forEach(name => { 22 | // ignore _id because is used as index 23 | if (name === '_id') { 24 | return; 25 | } 26 | 27 | const path = schema.paths[name]; 28 | const type = utils.getType(path); 29 | const options = getOptions(path, typeKey); 30 | 31 | if ( 32 | explicit && 33 | isEmbedded(type) && 34 | !{}.hasOwnProperty.call(options, 'es_indexed') 35 | ) { 36 | options.es_indexed = hasExplicit(path.schema, typeKey); 37 | } 38 | 39 | if (explicit && !options.es_indexed) { 40 | return; 41 | } 42 | 43 | let current = mapping; 44 | const names = name.split('.'); 45 | 46 | // handle plain object 47 | if (names.length > 1) { 48 | names.forEach((name, index) => { 49 | if (index === names.length - 1) { 50 | // last item is the target 51 | current = current[name] = { type }; 52 | } else { 53 | if (!current[name]) { 54 | current[name] = { type: 'object', properties: {} }; 55 | } 56 | current = current[name].properties; 57 | } 58 | }); 59 | } else { 60 | current = mapping[name] = { type }; 61 | } 62 | 63 | if (options.es_type && typeof options.es_type === 'object') { 64 | current.type = 'object'; 65 | current.properties = generateESTypeMapping(options.es_type); 66 | } else { 67 | if (!{}.hasOwnProperty.call(options, 'es_value') || !options.es_type) { 68 | if (isEmbedded(type)) { 69 | current.type = 'object'; 70 | current.properties = generate(path.schema, version); 71 | } 72 | if (defaultTypes[type]) { 73 | current.type = defaultTypes[type]; 74 | } 75 | } 76 | 77 | // propagate es_ options from schema to mapping 78 | Object.keys(options).forEach(key => { 79 | if (key !== 'es_indexed' && key.substr(0, 3) === 'es_') { 80 | current[key.substr(3)] = options[key]; 81 | } 82 | }); 83 | } 84 | 85 | if ({}.hasOwnProperty.call(options, 'es_value')) { 86 | current.value = 87 | typeof options.es_value === 'function' 88 | ? options.es_value 89 | : function() { 90 | return options.es_value; 91 | }; 92 | } 93 | }); 94 | 95 | delete mapping[schema.get('versionKey')]; 96 | 97 | if (schema.options && schema.options.es_extend) { 98 | Object.assign( 99 | mapping, 100 | generateESTypeMapping(schema.options.es_extend, true) 101 | ); 102 | } 103 | 104 | return mapping; 105 | } 106 | 107 | function isEmbedded(type) { 108 | return type === 'embedded' || type === 'array'; // || type === 'mixed'; 109 | } 110 | 111 | function getTypeKey(schema) { 112 | return schema.options.typeKey || 'type'; 113 | } 114 | 115 | function hasExplicit(schema, typeKey) { 116 | return schema && schema.paths 117 | ? Object.keys(schema.paths).some(name => { 118 | if (name === '_id') { 119 | return; // eslint-disable-line 120 | } 121 | const path = schema.paths[name]; 122 | const type = utils.getType(path); 123 | if (isEmbedded(type)) { 124 | if (hasExplicit(path.schema, getTypeKey(path.schema))) { 125 | return true; 126 | } 127 | } 128 | return {}.hasOwnProperty.call(getOptions(path, typeKey), 'es_indexed'); 129 | }) 130 | : false; 131 | } 132 | 133 | function getOptions(path, typeKey) { 134 | if ( 135 | Array.isArray(path.options[typeKey]) && 136 | path.options[typeKey].length === 1 && 137 | path.options[typeKey][0].ref 138 | ) { 139 | return path.options[typeKey][0]; 140 | } 141 | return path.options; 142 | } 143 | 144 | function generateESTypeMapping(content, esExtendMode) { 145 | const properties = {}; 146 | // browse properties 147 | Object.keys(content).forEach(key => { 148 | if (content[key] && typeof content[key] === 'object') { 149 | // only browse well formed object 150 | properties[key] = {}; 151 | if (content[key].es_type && typeof content[key].es_type === 'object') { 152 | properties[key].type = 'object'; 153 | properties[key].properties = generateESTypeMapping( 154 | content[key].es_type, 155 | esExtendMode 156 | ); 157 | } else { 158 | Object.keys(content[key]).forEach(subkey => { 159 | const targetSubkey = subkey.replace(/^es_/, ''); // remove plugin prefix 160 | let value = content[key][subkey]; 161 | const original = value; 162 | 163 | // build a function to be ignored in the mapping sent 164 | if (subkey === 'es_value' && esExtendMode) { 165 | value = function(_, context) { 166 | // serialised function of es_value handle prototype (value, context) where value is always undefined in options.es_extend feature 167 | return typeof original === 'function' 168 | ? original(context.document) 169 | : original; 170 | }; 171 | } 172 | 173 | properties[key][targetSubkey] = value; 174 | }); 175 | } 176 | } 177 | }); 178 | return properties; 179 | } 180 | 181 | /** 182 | * Return default type mapping depending on Elasticsearch version 183 | * @param {number} version 184 | * @return {object} 185 | */ 186 | function getDefault(version) { 187 | if (version === 2) { 188 | return { 189 | objectid: 'string', 190 | number: 'double', 191 | mixed: 'object', 192 | }; 193 | } 194 | if (version === 5 || version === 6) { 195 | return { 196 | objectid: 'keyword', 197 | number: 'double', 198 | mixed: 'object', 199 | string: 'text', 200 | }; 201 | } 202 | if (version === 7) { 203 | return { 204 | objectid: 'keyword', 205 | number: 'long', 206 | mixed: 'object', 207 | string: 'text', 208 | }; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | 5 | const _Promise = mongoose.Promise.ES6 || global.Promise; 6 | 7 | module.exports = {}; 8 | 9 | /** 10 | * Promify 11 | * @param {Function} [callback] 12 | * @param {Function} fn - Function to execute 13 | * @returns {Promise|undefined} 14 | */ 15 | module.exports.run = function(callback, fn) { 16 | if (typeof callback === 'function') { 17 | try { 18 | fn( 19 | result => { 20 | callback(undefined, result); 21 | }, 22 | reason => { 23 | callback(reason); 24 | } 25 | ); 26 | } catch (err) { 27 | callback(err); 28 | } 29 | } else { 30 | return new _Promise(fn); 31 | } 32 | }; 33 | 34 | /** 35 | * Lower case first character 36 | * @param {String} str 37 | * @returns {String} 38 | */ 39 | module.exports.lcFirst = function(str) { 40 | return str ? str[0].toLowerCase() + str.substr(1) : ''; 41 | }; 42 | 43 | /** 44 | * Create an object with the same keys / values 45 | * @param {Object} source 46 | * @returns {Object} 47 | */ 48 | module.exports.highClone = function(source) { 49 | const result = {}; 50 | if (source) { 51 | Object.keys(source).forEach(key => { 52 | result[key] = source[key]; 53 | }); 54 | } 55 | return result; 56 | }; 57 | 58 | /** 59 | * Return updated fields to undefined 60 | * @param {Object} document 61 | * @param {Object} mapping 62 | * @returns {Array} 63 | */ 64 | module.exports.getUndefineds = function getUndefineds(document, mapping) { 65 | let field; 66 | const result = []; 67 | for (field in mapping.properties) { 68 | if ({}.hasOwnProperty.call(mapping.properties, field)) { 69 | if (document[field] === undefined && document.isModified(field)) { 70 | result.push(field); 71 | } 72 | } 73 | } 74 | return result; 75 | }; 76 | 77 | /** 78 | * Generate the serialised object to send to ElasticSearch 79 | * @param {Object} document - mongoose document 80 | * @param {Object} mapping 81 | * @param {Object} [main] - main mongoose document 82 | * @returns {Object} 83 | */ 84 | module.exports.serialize = function serialize(document, mapping, main) { 85 | let name; 86 | 87 | main = main || document; // eslint-disable-line 88 | 89 | function _serializeObject(object, mappingData) { 90 | const serialized = {}; 91 | Object.keys(mappingData.properties).forEach(field => { 92 | let value; 93 | const property = mappingData.properties[field]; 94 | try { 95 | if ({}.hasOwnProperty.call(property, 'value')) { 96 | value = 97 | typeof property.value === 'function' 98 | ? property.value(object[field], { 99 | document: main, 100 | container: object, 101 | field, 102 | }) 103 | : property.value; 104 | } else if (object[field] !== undefined) { 105 | value = serialize.call(object, object[field], property, main); 106 | } 107 | if (value !== undefined) { 108 | serialized[field] = value; 109 | } 110 | } catch (err) { 111 | // do nothing 112 | } 113 | }); 114 | if ( 115 | !Object.keys(serialized).length && 116 | (typeof object !== 'object' || object instanceof mongoose.Types.ObjectId) 117 | ) { 118 | return; 119 | } 120 | return serialized; 121 | } 122 | 123 | if (mapping.properties && document) { 124 | if (Array.isArray(document)) { 125 | return document.map(object => _serializeObject(object, mapping)); 126 | } 127 | return _serializeObject(document, mapping); 128 | } 129 | 130 | if (document && typeof document === 'object') { 131 | name = document.constructor.name; 132 | if (name === 'model') { 133 | return document.id; 134 | } 135 | 136 | if (name === 'ObjectID') { 137 | return document.toString(); 138 | } 139 | 140 | if (name === 'Date') { 141 | return new Date(document).toJSON(); 142 | } 143 | } 144 | 145 | return document; 146 | }; 147 | 148 | /** 149 | * Return the Type of a schema path 150 | * @param {object} path 151 | * @return {string} 152 | */ 153 | module.exports.getType = function getType(path) { 154 | return (path.caster && path.caster.instance 155 | ? path.caster.instance 156 | : path.instance 157 | ).toLowerCase(); 158 | }; 159 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongoose-elasticsearch-xp", 3 | "version": "0.0.0-semantically-released", 4 | "description": "A mongoose plugin that indexes models into Elasticsearch 2 / 5 (an alternative to mongoosastic)", 5 | "tags": [ 6 | "elasticsearch", 7 | "mongodb", 8 | "mongoose", 9 | "mongoosastic", 10 | "auto index", 11 | "full text search", 12 | "promise" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/jbdemonte/mongoose-elasticsearch-xp.git" 17 | }, 18 | "files": [ 19 | "index.js", 20 | "lib" 21 | ], 22 | "main": "lib/index.js", 23 | "scripts": { 24 | "lint": "eslint lib test *.js", 25 | "docker-v2": "node ./scripts/docker/start 2 & wait", 26 | "test-v2": "nyc mocha -t 10000 test/es2/*", 27 | "test-v2-covered": "npm run lint && nyc mocha --report lcovonly -- -t 10000 -R spec ./test/es2/*", 28 | "test-v2-covered-html": "nyc --reporter=lcov --reporter=text-lcov mocha -- -t 10000 -R spec ./test/es2/*", 29 | "docker-v5": "node ./scripts/docker/start 5 & wait", 30 | "test-v5": "nyc mocha -t 10000 test/es5/*", 31 | "test-v5-covered": "npm run lint && nyc --reporter=lcov --reporter=text-lcov mocha --report lcovonly -- -t 10000 -R spec ./test/es5/*", 32 | "test-v5-covered-html": "nyc --reporter=lcov --reporter=text-lcov mocha -- -t 10000 -R spec ./test/es5/*", 33 | "docker-v6": "node ./scripts/docker/start 6 & wait", 34 | "test-v6": "nyc mocha -t 10000 test/es6/*", 35 | "test-v6-covered": "npm run lint && nyc --reporter=lcov --reporter=text-lcov mocha --report lcovonly -- -t 10000 -R spec ./test/es6/*", 36 | "test-v6-covered-html": "nyc --reporter=lcov --reporter=text-lcov mocha -- -t 10000 -R spec ./test/es6/*", 37 | "docker-v7": "node ./scripts/docker/start 7 & wait", 38 | "test-v7": "nyc mocha -t 10000 test/es7/*", 39 | "test-v7-covered": "npm run lint && nyc --reporter=lcov --reporter=text-lcov mocha --report lcovonly -- -t 10000 -R spec ./test/es7/*", 40 | "test-v7-covered-html": "nyc --reporter=lcov --reporter=text-lcov mocha -- -t 10000 -R spec ./test/es7/*", 41 | "watch": "npm run test-v7 -- --watch", 42 | "semantic-release": "semantic-release" 43 | }, 44 | "author": "Jean-Baptiste Demonte ", 45 | "license": "MIT", 46 | "peerDependencies": { 47 | "elasticsearch": "^12.1.3 || ^13.0.0 || ^14.0.0 || ^16.0.0", 48 | "mongoose": "^4.9.0 || ^5.0.0" 49 | }, 50 | "devDependencies": { 51 | "chai": "^4.2.0", 52 | "coveralls": "^3.0.3", 53 | "cz-conventional-changelog": "^2.1.0", 54 | "elasticsearch": "^16.0.0", 55 | "eslint": "^5.16.0", 56 | "eslint-config-airbnb-base": "^13.1.0", 57 | "eslint-config-prettier": "^4.2.0", 58 | "eslint-plugin-chai-expect": "^2.0.1", 59 | "eslint-plugin-import": "^2.9.0", 60 | "eslint-plugin-prettier": "^3.0.1", 61 | "mocha": "^6.1.4", 62 | "nyc": "^14.1.0", 63 | "prettier": "^1.17.0", 64 | "semantic-release": "^15.13.3", 65 | "shortid": "^2.2.8" 66 | }, 67 | "config": { 68 | "commitizen": { 69 | "path": "./node_modules/cz-conventional-changelog" 70 | } 71 | }, 72 | "dependencies": { 73 | "mongoose": "^5.5.6" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /scripts/docker/es2/Dockerfile: -------------------------------------------------------------------------------- 1 | from elasticsearch:2-alpine 2 | 3 | copy elasticsearch.yml /usr/share/elasticsearch/config/elasticsearch.yml 4 | -------------------------------------------------------------------------------- /scripts/docker/es2/elasticsearch.yml: -------------------------------------------------------------------------------- 1 | script.inline: true 2 | script.indexed: true 3 | network.host: 0.0.0.0 4 | http.cors.enabled: true 5 | http.cors.allow-origin: "*" 6 | cluster.name: elasticsearch_xp 7 | -------------------------------------------------------------------------------- /scripts/docker/es5/Dockerfile: -------------------------------------------------------------------------------- 1 | from elasticsearch:5-alpine 2 | 3 | copy elasticsearch.yml /usr/share/elasticsearch/config/elasticsearch.yml 4 | -------------------------------------------------------------------------------- /scripts/docker/es5/elasticsearch.yml: -------------------------------------------------------------------------------- 1 | script.inline: true 2 | network.host: 0.0.0.0 3 | http.cors.enabled: true 4 | http.cors.allow-origin: "*" 5 | cluster.name: elasticsearch_xp 6 | -------------------------------------------------------------------------------- /scripts/docker/es6/Dockerfile: -------------------------------------------------------------------------------- 1 | from elasticsearch:6.7.2 2 | 3 | copy elasticsearch.yml /usr/share/elasticsearch/config/elasticsearch.yml 4 | -------------------------------------------------------------------------------- /scripts/docker/es6/elasticsearch.yml: -------------------------------------------------------------------------------- 1 | transport.host: localhost 2 | network.host: _eth0_ 3 | http.cors.enabled: true 4 | http.cors.allow-origin: "*" 5 | cluster.name: elasticsearch_xp 6 | -------------------------------------------------------------------------------- /scripts/docker/es7/Dockerfile: -------------------------------------------------------------------------------- 1 | from elasticsearch:7.0.1 2 | 3 | COPY --chown=elasticsearch:elasticsearch elasticsearch.yml /usr/share/elasticsearch/config/ 4 | RUN chown -R elasticsearch:elasticsearch /etc/ 5 | #RUN chown -R elasticsearch:elasticsearch /etc/elasticsearch 6 | RUN chown -R elasticsearch:elasticsearch /usr/share/ -------------------------------------------------------------------------------- /scripts/docker/es7/elasticsearch.yml: -------------------------------------------------------------------------------- 1 | transport.host: localhost 2 | network.host: 127.0.0.1 3 | http.cors.enabled: true 4 | http.cors.allow-origin: "*" 5 | cluster.name: elasticsearch_xp 6 | node.name: es01 7 | cluster.initial_master_nodes: es01 -------------------------------------------------------------------------------- /scripts/docker/start.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const cp = require('child_process'); 4 | const path = require('path'); 5 | 6 | const dockerImageName = 'elasticsearch-xp'; 7 | const version = process.argv[2] || 5; 8 | 9 | function isDockerImageExists(imageNameWithTag) { 10 | const imageId = cp 11 | .execSync(`docker images -q ${imageNameWithTag}`, { cwd: '.' }) 12 | .toString(); 13 | return imageId && imageId.length > 0; 14 | } 15 | 16 | function buildDockerContainer(v) { 17 | const imageNameWithTag = `${dockerImageName}:${v}`; 18 | const dockerContextFolder = path.resolve(__dirname, `./es${v}`); 19 | console.log( 20 | `Building docker container ${imageNameWithTag} from ${dockerContextFolder}/Dockerfile ...` 21 | ); 22 | cp.execSync( 23 | `docker build \ 24 | -t ${imageNameWithTag} \ 25 | ${dockerContextFolder}`, 26 | { 27 | cwd: dockerContextFolder, 28 | stdio: [0, 1, 2], 29 | } 30 | ); 31 | } 32 | 33 | function runDockerContainer(v) { 34 | const imageNameWithTag = `${dockerImageName}:${v}`; 35 | if (!isDockerImageExists(imageNameWithTag)) { 36 | buildDockerContainer(v); 37 | } 38 | 39 | console.log(`Starting docker container ${imageNameWithTag} ...`); 40 | cp.execSync(`docker run -i --rm -p 9200:9200 ${imageNameWithTag}`, { 41 | stdio: [0, 1, 2], 42 | }); 43 | } 44 | 45 | function removeDockerContainer(v) { 46 | const imageNameWithTag = `${dockerImageName}:${v}`; 47 | console.log(`Removing docker image ${imageNameWithTag} ...`); 48 | cp.execSync(`docker rmi ${imageNameWithTag}`, { stdio: [0, 1, 2] }); 49 | } 50 | 51 | function onExit() { 52 | removeDockerContainer(version); 53 | process.exit(0); 54 | } 55 | process.on('SIGINT', onExit); // catch ctrl-c 56 | process.on('SIGTERM', onExit); // catch kill 57 | runDockerContainer(version); 58 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "expect": true 4 | }, 5 | "rules": { 6 | "arrow-body-style": 0, 7 | "no-param-reassign": 0, 8 | "no-unused-expressions": 0, 9 | "func-names": 0, 10 | "import/no-extraneous-dependencies": 0, 11 | "strict": 0, 12 | "import/no-cycle": 0, 13 | "no-use-before-define": 0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/es2/countOnly.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v2; 6 | 7 | describe('countOnly', () => { 8 | utils.setup(); 9 | let UserModel; 10 | 11 | beforeEach(() => { 12 | const UserSchema = new mongoose.Schema({ 13 | name: String, 14 | age: Number, 15 | }); 16 | 17 | UserSchema.plugin(plugin); 18 | UserModel = mongoose.model('User', UserSchema); 19 | 20 | const john = new UserModel({ name: 'John', age: 35 }); 21 | const jane = new UserModel({ name: 'Jane', age: 34 }); 22 | const bob = new UserModel({ name: 'Bob', age: 36 }); 23 | 24 | return utils 25 | .deleteModelIndexes(UserModel) 26 | .then(() => { 27 | return UserModel.esCreateMapping(); 28 | }) 29 | .then(() => { 30 | return utils.Promise.all( 31 | [john, jane, bob].map(user => { 32 | return new utils.Promise(resolve => { 33 | user.on('es-indexed', resolve); 34 | user.save(); 35 | }); 36 | }) 37 | ); 38 | }) 39 | .then(() => { 40 | return UserModel.esRefresh(); 41 | }); 42 | }); 43 | 44 | it('should return count', () => { 45 | return UserModel.esCount( 46 | { 47 | query: { match_all: {} }, 48 | filter: { range: { age: { gte: 35 } } }, 49 | }, 50 | { countOnly: true } 51 | ).then(count => { 52 | expect(count).to.eql(2); 53 | }); 54 | }); 55 | 56 | it('should return 0', () => { 57 | return UserModel.esCount( 58 | { 59 | query: { match_all: {} }, 60 | filter: { range: { age: { gte: 100 } } }, 61 | }, 62 | { countOnly: true } 63 | ).then(count => { 64 | expect(count).to.eql(0); 65 | }); 66 | }); 67 | 68 | it('should return count when defined in plugin', () => { 69 | utils.deleteMongooseModels(); 70 | 71 | const UserSchema = new mongoose.Schema({ 72 | name: String, 73 | age: Number, 74 | }); 75 | 76 | UserSchema.plugin(plugin, { countOnly: true }); 77 | 78 | const UserModelCountOnly = mongoose.model('User', UserSchema); 79 | 80 | return UserModelCountOnly.esCount({ 81 | query: { match_all: {} }, 82 | filter: { range: { age: { gte: 35 } } }, 83 | }).then(count => { 84 | expect(count).to.eql(2); 85 | }); 86 | }); 87 | 88 | it('should overwrite defined in plugin value', () => { 89 | utils.deleteMongooseModels(); 90 | 91 | const UserSchema = new mongoose.Schema({ 92 | name: String, 93 | age: Number, 94 | }); 95 | 96 | UserSchema.plugin(plugin, { countOnly: true }); 97 | const UserModelCountOnly = mongoose.model('User', UserSchema); 98 | 99 | return UserModelCountOnly.esCount( 100 | { 101 | query: { match_all: {} }, 102 | filter: { range: { age: { gte: 35 } } }, 103 | }, 104 | { countOnly: false } 105 | ).then(result => { 106 | expect(result.count).to.eql(2); 107 | }); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /test/es2/customIds.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const shortid = require('shortid'); 5 | const utils = require('../utils'); 6 | const plugin = require('../../').v2; 7 | 8 | describe('custom ids', () => { 9 | utils.setup(); 10 | let UserModel; 11 | let john; 12 | let jane; 13 | let bob; 14 | 15 | beforeEach(() => { 16 | const UserSchema = new mongoose.Schema({ 17 | _id: { 18 | type: String, 19 | default: shortid.generate, 20 | }, 21 | name: String, 22 | age: Number, 23 | }); 24 | 25 | UserSchema.plugin(plugin); 26 | 27 | UserModel = mongoose.model('User', UserSchema); 28 | 29 | john = new UserModel({ name: 'John', age: 35 }); 30 | jane = new UserModel({ name: 'Jane', age: 34 }); 31 | bob = new UserModel({ name: 'Bob', age: 36 }); 32 | 33 | return utils 34 | .deleteModelIndexes(UserModel) 35 | .then(() => { 36 | return UserModel.esCreateMapping(); 37 | }) 38 | .then(() => { 39 | return utils.Promise.all( 40 | [john, jane, bob].map(user => { 41 | return new utils.Promise(resolve => { 42 | user.on('es-indexed', resolve); 43 | user.save(); 44 | }); 45 | }) 46 | ); 47 | }) 48 | .then(() => { 49 | return UserModel.esRefresh(); 50 | }); 51 | }); 52 | 53 | it('should return ids', () => { 54 | return UserModel.esSearch( 55 | { 56 | query: { 57 | bool: { 58 | must: { match_all: {} }, 59 | filter: { range: { age: { gte: 35 } } }, 60 | }, 61 | }, 62 | sort: [{ age: { order: 'desc' } }], 63 | }, 64 | { idsOnly: true } 65 | ).then(ids => { 66 | expect(ids.length).to.eql(2); 67 | expect(ids).to.eql([bob._id, john._id]); 68 | }); 69 | }); 70 | 71 | it('should return people', () => { 72 | return UserModel.esSearch({ 73 | query: { 74 | bool: { 75 | must: { match_all: {} }, 76 | filter: { range: { age: { gte: 35 } } }, 77 | }, 78 | }, 79 | sort: [{ age: { order: 'desc' } }], 80 | }).then(result => { 81 | expect(result.hits.total).to.eql(2); 82 | expect(result.hits.hits[0]._source).to.eql({ name: 'Bob', age: 36 }); 83 | expect(result.hits.hits[1]._source).to.eql({ name: 'John', age: 35 }); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/es2/esCount.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v2; 6 | 7 | describe('esCount', () => { 8 | utils.setup(); 9 | let UserModel; 10 | 11 | beforeEach(() => { 12 | const UserSchema = new mongoose.Schema({ 13 | name: String, 14 | age: Number, 15 | }); 16 | 17 | UserSchema.plugin(plugin); 18 | 19 | UserModel = mongoose.model('User', UserSchema); 20 | 21 | const john = new UserModel({ name: 'John', age: 35 }); 22 | const jane = new UserModel({ name: 'Jane', age: 34 }); 23 | const bob = new UserModel({ name: 'Bob', age: 36 }); 24 | 25 | return utils 26 | .deleteModelIndexes(UserModel) 27 | .then(() => { 28 | return UserModel.esCreateMapping(); 29 | }) 30 | .then(() => { 31 | const options = UserModel.esOptions(); 32 | const client = options.client; 33 | return client.bulk({ 34 | refresh: true, 35 | body: [ 36 | { 37 | index: { 38 | _index: options.index, 39 | _type: options.type, 40 | _id: john._id.toString(), 41 | }, 42 | }, 43 | { name: 'John', age: 35 }, 44 | { 45 | index: { 46 | _index: options.index, 47 | _type: options.type, 48 | _id: jane._id.toString(), 49 | }, 50 | }, 51 | { name: 'Jane', age: 34 }, 52 | { 53 | index: { 54 | _index: options.index, 55 | _type: options.type, 56 | _id: bob._id.toString(), 57 | }, 58 | }, 59 | { name: 'Bob', age: 36 }, 60 | ], 61 | }); 62 | }); 63 | }); 64 | 65 | it('should handle a lucene query', () => { 66 | return UserModel.esCount('name:jane').then(result => { 67 | expect(result.count).to.eql(1); 68 | }); 69 | }); 70 | 71 | it('should accept callback', done => { 72 | const returned = UserModel.esCount('name:jane', (err, result) => { 73 | if (err) { 74 | done(err); 75 | return; 76 | } 77 | expect(result.count).to.eql(1); 78 | expect(returned).to.be.undefined; 79 | done(); 80 | }); 81 | }); 82 | 83 | it('should handle a full query', () => { 84 | return UserModel.esCount({ 85 | query: { match_all: {} }, 86 | filter: { range: { age: { lt: 35 } } }, 87 | }).then(result => { 88 | expect(result.count).to.eql(1); 89 | }); 90 | }); 91 | 92 | it('should handle a short query', () => { 93 | return UserModel.esCount({ match: { age: 34 } }).then(result => { 94 | expect(result.count).to.eql(1); 95 | }); 96 | }); 97 | 98 | it('should handle 0 hit', () => { 99 | return UserModel.esCount({ match: { age: 100 } }).then(result => { 100 | expect(result.count).to.eql(0); 101 | }); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /test/es2/esIndex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v2; 6 | 7 | describe('esIndex', () => { 8 | utils.setup(); 9 | 10 | it('should be indexed', () => { 11 | const UserSchema = new mongoose.Schema({ 12 | name: String, 13 | age: Number, 14 | pos: { 15 | type: [Number], 16 | index: '2dsphere', 17 | es_type: 'geo_point', 18 | es_boost: 1.5, 19 | }, 20 | }); 21 | 22 | UserSchema.plugin(plugin); 23 | 24 | const UserModel = mongoose.model('User', UserSchema); 25 | 26 | const john = new UserModel({ name: 'John', age: 35, pos: [5.7333, 43.5] }); 27 | 28 | return utils 29 | .deleteModelIndexes(UserModel) 30 | .then(() => { 31 | return UserModel.esCreateMapping(); 32 | }) 33 | .then(() => { 34 | return john.esIndex(); 35 | }) 36 | .then(() => { 37 | return UserModel.esRefresh(); 38 | }) 39 | .then(() => { 40 | const options = UserModel.esOptions(); 41 | const client = options.client; 42 | return client.search({ 43 | index: options.index, 44 | type: options.type, 45 | body: { query: { match_all: {} } }, 46 | }); 47 | }) 48 | .then(resp => { 49 | expect(resp.hits.total).to.eql(1); 50 | const hit = resp.hits.hits[0]; 51 | expect(hit._id).to.eql(john._id.toString()); 52 | expect(hit._source).to.eql({ 53 | name: 'John', 54 | age: 35, 55 | pos: [5.7333, 43.5], 56 | }); 57 | }); 58 | }); 59 | 60 | it('should index ObjectId from object populated or not', () => { 61 | const CountrySchema = new mongoose.Schema({ 62 | name: String, 63 | }); 64 | 65 | const CitySchema = new mongoose.Schema({ 66 | name: String, 67 | }); 68 | 69 | const UserSchema = new mongoose.Schema({ 70 | name: String, 71 | city: { type: mongoose.Schema.Types.ObjectId, ref: 'City' }, 72 | country: { type: mongoose.Schema.Types.ObjectId, ref: 'Country' }, 73 | }); 74 | 75 | UserSchema.plugin(plugin); 76 | 77 | const UserModel = mongoose.model('User', UserSchema); 78 | const CityModel = mongoose.model('City', CitySchema); 79 | const CountryModel = mongoose.model('Country', CountrySchema); 80 | 81 | const country = new CountryModel({ name: 'France' }); 82 | const city = new CityModel({ name: 'Paris' }); 83 | const john = new UserModel({ 84 | name: 'John', 85 | city, 86 | country: country._id, 87 | }); 88 | 89 | return utils 90 | .deleteModelIndexes(UserModel) 91 | .then(() => { 92 | return UserModel.esCreateMapping(); 93 | }) 94 | .then(() => { 95 | return john.esIndex(); 96 | }) 97 | .then(() => { 98 | return UserModel.esRefresh(); 99 | }) 100 | .then(() => { 101 | const options = UserModel.esOptions(); 102 | const client = options.client; 103 | return client.search({ 104 | index: options.index, 105 | type: options.type, 106 | body: { query: { match_all: {} } }, 107 | }); 108 | }) 109 | .then(resp => { 110 | expect(resp.hits.total).to.eql(1); 111 | const hit = resp.hits.hits[0]; 112 | expect(hit._id).to.eql(john._id.toString()); 113 | expect(hit._source).to.eql({ 114 | name: 'John', 115 | city: city.id, 116 | country: country.id, 117 | }); 118 | }); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /test/es2/esRefresh.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v2; 6 | 7 | describe('esRefresh', () => { 8 | utils.setup(); 9 | 10 | it('should handle callback', done => { 11 | const UserSchema = new mongoose.Schema({ 12 | name: String, 13 | }); 14 | 15 | UserSchema.plugin(plugin); 16 | 17 | const UserModel = mongoose.model('User', UserSchema); 18 | 19 | let start; 20 | 21 | utils 22 | .deleteModelIndexes(UserModel) 23 | .then(() => { 24 | return UserModel.esCreateMapping(); 25 | }) 26 | .then(() => { 27 | start = Date.now(); 28 | return UserModel.esRefresh(err => { 29 | if (err) { 30 | done(err); 31 | return; 32 | } 33 | expect(Date.now() - start).to.be.lt(800); 34 | done(); 35 | }); 36 | }); 37 | }); 38 | 39 | it('should handle callback and options', done => { 40 | const UserSchema = new mongoose.Schema({ 41 | name: String, 42 | }); 43 | 44 | UserSchema.plugin(plugin); 45 | 46 | const UserModel = mongoose.model('User', UserSchema); 47 | 48 | let start; 49 | 50 | utils 51 | .deleteModelIndexes(UserModel) 52 | .then(() => { 53 | return UserModel.esCreateMapping(); 54 | }) 55 | .then(() => { 56 | start = Date.now(); 57 | UserModel.esRefresh({ refreshDelay: 1000 }, err => { 58 | if (err) { 59 | done(err); 60 | return; 61 | } 62 | expect(Date.now() - start).to.be.gte(1000); 63 | done(); 64 | }); 65 | }) 66 | .catch(err => { 67 | done(err); 68 | }); 69 | }); 70 | 71 | it('should not be delayed', () => { 72 | const UserSchema = new mongoose.Schema({ 73 | name: String, 74 | }); 75 | 76 | UserSchema.plugin(plugin); 77 | 78 | const UserModel = mongoose.model('User', UserSchema); 79 | 80 | let start; 81 | 82 | return utils 83 | .deleteModelIndexes(UserModel) 84 | .then(() => { 85 | return UserModel.esCreateMapping(); 86 | }) 87 | .then(() => { 88 | start = Date.now(); 89 | return UserModel.esRefresh(); 90 | }) 91 | .then(() => { 92 | expect(Date.now() - start).to.be.lt(800); 93 | }); 94 | }); 95 | 96 | it('should be delayed', () => { 97 | const UserSchema = new mongoose.Schema({ 98 | name: String, 99 | }); 100 | 101 | UserSchema.plugin(plugin); 102 | 103 | const UserModel = mongoose.model('User', UserSchema); 104 | 105 | let start; 106 | 107 | return utils 108 | .deleteModelIndexes(UserModel) 109 | .then(() => { 110 | return UserModel.esCreateMapping(); 111 | }) 112 | .then(() => { 113 | start = Date.now(); 114 | return UserModel.esRefresh({ refreshDelay: 1000 }); 115 | }) 116 | .then(() => { 117 | expect(Date.now() - start).to.be.gte(1000); 118 | }); 119 | }); 120 | 121 | it('should be delayed when defined in plugin', () => { 122 | const UserSchema = new mongoose.Schema({ 123 | name: String, 124 | }); 125 | 126 | UserSchema.plugin(plugin, { refreshDelay: 1000 }); 127 | 128 | const UserModel = mongoose.model('User', UserSchema); 129 | 130 | let start; 131 | 132 | return utils 133 | .deleteModelIndexes(UserModel) 134 | .then(() => { 135 | return UserModel.esCreateMapping(); 136 | }) 137 | .then(() => { 138 | start = Date.now(); 139 | return UserModel.esRefresh(); 140 | }) 141 | .then(() => { 142 | expect(Date.now() - start).to.be.gte(1000); 143 | }); 144 | }); 145 | 146 | it('should overwrite defined in plugin value', () => { 147 | const UserSchema = new mongoose.Schema({ 148 | name: String, 149 | }); 150 | 151 | UserSchema.plugin(plugin, { refreshDelay: 1000 }); 152 | 153 | const UserModel = mongoose.model('User', UserSchema); 154 | 155 | let start; 156 | 157 | return utils 158 | .deleteModelIndexes(UserModel) 159 | .then(() => { 160 | return UserModel.esCreateMapping(); 161 | }) 162 | .then(() => { 163 | start = Date.now(); 164 | return UserModel.esRefresh({ refreshDelay: false }); 165 | }) 166 | .then(() => { 167 | expect(Date.now() - start).to.be.lt(800); 168 | }); 169 | }); 170 | }); 171 | -------------------------------------------------------------------------------- /test/es2/esRemove.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v2; 6 | 7 | describe('esRemove', () => { 8 | utils.setup(); 9 | 10 | it('should be removed', () => { 11 | const UserSchema = new mongoose.Schema({ 12 | name: String, 13 | age: Number, 14 | }); 15 | 16 | UserSchema.plugin(plugin); 17 | 18 | const UserModel = mongoose.model('User', UserSchema); 19 | 20 | const john = new UserModel({ name: 'John', age: 35 }); 21 | const jane = new UserModel({ name: 'Jane', age: 34 }); 22 | const bob = new UserModel({ name: 'Bob', age: 36 }); 23 | 24 | return utils 25 | .deleteModelIndexes(UserModel) 26 | .then(() => { 27 | return UserModel.esCreateMapping(); 28 | }) 29 | .then(() => { 30 | return new utils.Promise((resolve, reject) => { 31 | const options = UserModel.esOptions(); 32 | const client = options.client; 33 | client.bulk( 34 | { 35 | refresh: true, 36 | body: [ 37 | { 38 | index: { 39 | _index: options.index, 40 | _type: options.type, 41 | _id: john._id.toString(), 42 | }, 43 | }, 44 | { name: 'John', age: 35 }, 45 | { 46 | index: { 47 | _index: options.index, 48 | _type: options.type, 49 | _id: jane._id.toString(), 50 | }, 51 | }, 52 | { name: 'Jane', age: 34 }, 53 | { 54 | index: { 55 | _index: options.index, 56 | _type: options.type, 57 | _id: bob._id.toString(), 58 | }, 59 | }, 60 | { name: 'Bob', age: 36 }, 61 | ], 62 | }, 63 | err => { 64 | if (err) { 65 | reject(err); 66 | } else { 67 | resolve(); 68 | } 69 | } 70 | ); 71 | }); 72 | }) 73 | .then(() => { 74 | return jane.esRemove(); 75 | }) 76 | .then(() => { 77 | return UserModel.esRefresh(); 78 | }) 79 | .then(() => { 80 | const options = UserModel.esOptions(); 81 | const client = options.client; 82 | return client.search({ 83 | index: options.index, 84 | type: options.type, 85 | body: { query: { match_all: {} } }, 86 | }); 87 | }) 88 | .then(resp => { 89 | const ids = resp.hits.hits.map(hit => { 90 | return hit._id; 91 | }); 92 | ids.sort(); 93 | 94 | const expectedIds = [john, bob].map(user => { 95 | return user._id.toString(); 96 | }); 97 | 98 | expect(ids).to.eql(expectedIds); 99 | }); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /test/es2/esSearch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v2; 6 | 7 | describe('esSearch', () => { 8 | utils.setup(); 9 | let UserModel; 10 | let users; 11 | 12 | beforeEach(() => { 13 | const UserSchema = new mongoose.Schema({ 14 | name: String, 15 | age: Number, 16 | }); 17 | 18 | UserSchema.plugin(plugin); 19 | 20 | UserModel = mongoose.model('User', UserSchema); 21 | 22 | const john = new UserModel({ name: 'John', age: 35 }); 23 | const jane = new UserModel({ name: 'Jane', age: 34 }); 24 | const bob = new UserModel({ name: 'Bob', age: 36 }); 25 | 26 | users = { 27 | john, 28 | jane, 29 | bob, 30 | }; 31 | 32 | return utils 33 | .deleteModelIndexes(UserModel) 34 | .then(() => { 35 | return UserModel.esCreateMapping(); 36 | }) 37 | .then(() => { 38 | const options = UserModel.esOptions(); 39 | const client = options.client; 40 | return client.bulk({ 41 | refresh: true, 42 | body: [ 43 | { 44 | index: { 45 | _index: options.index, 46 | _type: options.type, 47 | _id: john._id.toString(), 48 | }, 49 | }, 50 | { name: 'John', age: 35 }, 51 | { 52 | index: { 53 | _index: options.index, 54 | _type: options.type, 55 | _id: jane._id.toString(), 56 | }, 57 | }, 58 | { name: 'Jane', age: 34 }, 59 | { 60 | index: { 61 | _index: options.index, 62 | _type: options.type, 63 | _id: bob._id.toString(), 64 | }, 65 | }, 66 | { name: 'Bob', age: 36 }, 67 | ], 68 | }); 69 | }); 70 | }); 71 | 72 | it('should handle a lucene query', () => { 73 | return UserModel.esSearch('name:jane').then(result => { 74 | expect(result.hits.total).to.eql(1); 75 | const hit = result.hits.hits[0]; 76 | expect(hit._id).to.eql(users.jane._id.toString()); 77 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 78 | }); 79 | }); 80 | 81 | it('should accept callback', done => { 82 | const returned = UserModel.esSearch('name:jane', {}, (err, result) => { 83 | if (err) { 84 | done(err); 85 | return; 86 | } 87 | expect(result.hits.total).to.eql(1); 88 | const hit = result.hits.hits[0]; 89 | expect(hit._id).to.eql(users.jane._id.toString()); 90 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 91 | expect(returned).to.be.undefined; 92 | done(); 93 | }); 94 | }); 95 | 96 | it('should accept callback without options', done => { 97 | const returned = UserModel.esSearch('name:jane', (err, result) => { 98 | if (err) { 99 | done(err); 100 | return; 101 | } 102 | expect(result.hits.total).to.eql(1); 103 | const hit = result.hits.hits[0]; 104 | expect(hit._id).to.eql(users.jane._id.toString()); 105 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 106 | expect(returned).to.be.undefined; 107 | done(); 108 | }); 109 | }); 110 | 111 | it('should handle a full query', () => { 112 | return UserModel.esSearch({ 113 | query: { match_all: {} }, 114 | filter: { range: { age: { lt: 35 } } }, 115 | }).then(result => { 116 | expect(result.hits.total).to.eql(1); 117 | const hit = result.hits.hits[0]; 118 | expect(hit._id).to.eql(users.jane._id.toString()); 119 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 120 | }); 121 | }); 122 | 123 | it('should handle a short query', () => { 124 | return UserModel.esSearch({ match: { age: 34 } }).then(result => { 125 | expect(result.hits.total).to.eql(1); 126 | const hit = result.hits.hits[0]; 127 | expect(hit._id).to.eql(users.jane._id.toString()); 128 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 129 | }); 130 | }); 131 | 132 | it('should handle 0 hit', () => { 133 | return UserModel.esSearch({ match: { age: 100 } }).then(result => { 134 | expect(result.hits.total).to.eql(0); 135 | }); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /test/es2/es_extend.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v2; 6 | 7 | describe('es_extend', () => { 8 | utils.setup(); 9 | 10 | it('should add some fields', () => { 11 | let john; 12 | 13 | const UserSchema = new mongoose.Schema( 14 | { 15 | name: String, 16 | }, 17 | { 18 | es_extend: { 19 | num: { 20 | es_type: 'integer', 21 | es_value: 123, 22 | }, 23 | length: { 24 | es_type: 'integer', 25 | es_value(document) { 26 | expect(document === john).to.be.true; 27 | return document.name.length; 28 | }, 29 | }, 30 | }, 31 | } 32 | ); 33 | 34 | UserSchema.plugin(plugin); 35 | 36 | const UserModel = mongoose.model('User', UserSchema); 37 | 38 | john = new UserModel({ 39 | name: 'John', 40 | }); 41 | 42 | return utils 43 | .deleteModelIndexes(UserModel) 44 | .then(() => { 45 | return UserModel.esCreateMapping(); 46 | }) 47 | .then(() => { 48 | const options = UserModel.esOptions(); 49 | return options.client.indices.getMapping({ 50 | index: options.index, 51 | type: options.type, 52 | }); 53 | }) 54 | .then(mapping => { 55 | const properties = mapping.users.mappings.user.properties; 56 | expect(properties).to.have.all.keys('name', 'num', 'length'); 57 | expect(properties.name.type).to.be.equal('string'); 58 | expect(properties.num.type).to.be.equal('integer'); 59 | expect(properties.length.type).to.be.equal('integer'); 60 | }) 61 | .then(() => { 62 | return new utils.Promise(resolve => { 63 | john.on('es-indexed', () => { 64 | resolve(); 65 | }); 66 | john.save(); 67 | }); 68 | }) 69 | .then(() => { 70 | return UserModel.esRefresh(); 71 | }) 72 | .then(() => { 73 | return UserModel.esSearch({ 74 | query: { match_all: {} }, 75 | }); 76 | }) 77 | .then(result => { 78 | expect(result.hits.total).to.eql(1); 79 | expect(result.hits.hits[0]._source).to.eql({ 80 | name: 'John', 81 | num: 123, 82 | length: 4, 83 | }); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/es2/es_value.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v2; 6 | 7 | describe('es_value', () => { 8 | utils.setup(); 9 | 10 | it('should handle es_value as a function', () => { 11 | let john; 12 | 13 | const Sub2Schema = new mongoose.Schema({ 14 | _id: false, 15 | value: { 16 | type: String, 17 | es_value(value, context) { 18 | expect(value).to.equal('x2'); 19 | expect(context.document === john).to.be.true; 20 | expect(context.container === john.sub.sub2).to.be.true; 21 | expect(context.field).to.eql('value'); 22 | return 'xyz'; 23 | }, 24 | }, 25 | }); 26 | 27 | const SubSchema = new mongoose.Schema({ 28 | _id: false, 29 | sub1: String, 30 | sub2: Sub2Schema, 31 | }); 32 | 33 | const TagSchema = new mongoose.Schema({ 34 | _id: false, 35 | value: String, 36 | }); 37 | 38 | const UserSchema = new mongoose.Schema({ 39 | name: String, 40 | sub: SubSchema, 41 | age: { 42 | type: Number, 43 | es_value(age, context) { 44 | expect(age).to.equal(35); 45 | expect(context.document === john).to.be.true; 46 | expect(context.container === john).to.be.true; 47 | expect(context.field).to.eql('age'); 48 | return age - (age % 10); 49 | }, 50 | }, 51 | tags: { 52 | type: [TagSchema], 53 | es_type: 'string', 54 | es_value(tags, context) { 55 | expect(tags === john.tags).to.be.true; 56 | expect(context.document === john).to.be.true; 57 | expect(context.container === john).to.be.true; 58 | expect(context.field).to.eql('tags'); 59 | return tags.map(tag => { 60 | return tag.value; 61 | }); 62 | }, 63 | }, 64 | }); 65 | 66 | UserSchema.plugin(plugin); 67 | 68 | const UserModel = mongoose.model('User', UserSchema); 69 | 70 | john = new UserModel({ 71 | name: 'John', 72 | age: 35, 73 | sub: { 74 | sub1: 'x1', 75 | sub2: { 76 | value: 'x2', 77 | nb: 7, 78 | }, 79 | }, 80 | tags: [{ value: 'cool' }, { value: 'green' }], 81 | }); 82 | 83 | return utils 84 | .deleteModelIndexes(UserModel) 85 | .then(() => { 86 | return UserModel.esCreateMapping(); 87 | }) 88 | .then(() => { 89 | return john.esIndex(); 90 | }) 91 | .then(() => { 92 | return UserModel.esRefresh(); 93 | }) 94 | .then(() => { 95 | const options = UserModel.esOptions(); 96 | const client = options.client; 97 | return client.search({ 98 | index: options.index, 99 | type: options.type, 100 | body: { query: { match_all: {} } }, 101 | }); 102 | }) 103 | .then(resp => { 104 | expect(resp.hits.total).to.eql(1); 105 | const hit = resp.hits.hits[0]; 106 | expect(hit._id).to.eql(john._id.toString()); 107 | expect(hit._source).to.eql({ 108 | name: 'John', 109 | age: 30, 110 | tags: ['cool', 'green'], 111 | sub: { sub1: 'x1', sub2: { value: 'xyz' } }, 112 | }); 113 | }); 114 | }); 115 | 116 | it('should handle es_value as a value', () => { 117 | const UserSchema = new mongoose.Schema({ 118 | name: String, 119 | numberArray: { 120 | type: Number, 121 | es_value: [1, 2, 3], 122 | }, 123 | falsy: { 124 | type: Number, 125 | es_value: 0, 126 | }, 127 | stringArray: { 128 | type: [String], 129 | es_value: ['az', 'er', 'ty'], 130 | }, 131 | obj: { 132 | type: String, 133 | es_type: { 134 | a: { es_type: 'string' }, 135 | b: { es_type: 'integer' }, 136 | }, 137 | es_value: { 138 | a: 'azerty', 139 | b: 123, 140 | }, 141 | }, 142 | }); 143 | 144 | UserSchema.plugin(plugin); 145 | 146 | const UserModel = mongoose.model('User', UserSchema); 147 | 148 | const john = new UserModel({ 149 | name: 'John', 150 | numberArray: 35, 151 | falsy: 98, 152 | stringArray: ['GHJ'], 153 | obj: 'obj', 154 | }); 155 | 156 | const bob = new UserModel({ name: 'Bob' }); 157 | 158 | return utils 159 | .deleteModelIndexes(UserModel) 160 | .then(() => { 161 | return UserModel.esCreateMapping(); 162 | }) 163 | .then(() => { 164 | return john.esIndex(); 165 | }) 166 | .then(() => { 167 | return bob.esIndex(); 168 | }) 169 | .then(() => { 170 | return UserModel.esRefresh(); 171 | }) 172 | .then(() => { 173 | const options = UserModel.esOptions(); 174 | const client = options.client; 175 | return client.search({ 176 | index: options.index, 177 | type: options.type, 178 | body: { 179 | query: { match_all: {} }, 180 | sort: [{ name: { order: 'desc' } }], 181 | }, 182 | }); 183 | }) 184 | .then(resp => { 185 | expect(resp.hits.total).to.eql(2); 186 | let hit = resp.hits.hits[0]; 187 | expect(hit._id).to.eql(john._id.toString()); 188 | expect(hit._source).to.eql({ 189 | name: 'John', 190 | numberArray: [1, 2, 3], 191 | falsy: 0, 192 | stringArray: ['az', 'er', 'ty'], 193 | obj: { 194 | a: 'azerty', 195 | b: 123, 196 | }, 197 | }); 198 | hit = resp.hits.hits[1]; 199 | expect(hit._id).to.eql(bob._id.toString()); 200 | expect(hit._source).to.eql({ 201 | name: 'Bob', 202 | numberArray: [1, 2, 3], 203 | falsy: 0, 204 | stringArray: ['az', 'er', 'ty'], 205 | obj: { 206 | a: 'azerty', 207 | b: 123, 208 | }, 209 | }); 210 | }); 211 | }); 212 | }); 213 | -------------------------------------------------------------------------------- /test/es2/hydratation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v2; 6 | 7 | describe('hydratation', () => { 8 | utils.setup(); 9 | 10 | let UserModel; 11 | let BookModel; 12 | let CityModel; 13 | let john; 14 | let jane; 15 | let bob; 16 | let city1; 17 | let city2; 18 | let book1; 19 | let book2; 20 | let author1; 21 | let author2; 22 | 23 | beforeEach(() => { 24 | const CitySchema = new mongoose.Schema({ 25 | name: String, 26 | }); 27 | 28 | const BookSchema = new mongoose.Schema({ 29 | title: String, 30 | author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, 31 | }); 32 | 33 | const UserSchema = new mongoose.Schema({ 34 | name: { type: String, es_indexed: true }, 35 | age: { type: Number, es_indexed: true }, 36 | city: { 37 | type: mongoose.Schema.Types.ObjectId, 38 | ref: 'City', 39 | es_indexed: false, 40 | }, 41 | books: { 42 | type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Book' }], 43 | es_indexed: false, 44 | }, 45 | }); 46 | 47 | UserSchema.plugin(plugin); 48 | 49 | UserModel = mongoose.model('User', UserSchema); 50 | BookModel = mongoose.model('Book', BookSchema); 51 | CityModel = mongoose.model('City', CitySchema); 52 | 53 | city1 = new CityModel({ name: 'New York' }); 54 | city2 = new CityModel({ name: 'Los Angeles' }); 55 | 56 | author1 = new UserModel({ name: 'Rudyard Kipling' }); 57 | author2 = new UserModel({ name: 'George Orwell' }); 58 | 59 | book1 = new BookModel({ title: 'The Jungle Book', author: author1 }); 60 | book2 = new BookModel({ title: '1984', author: author2 }); 61 | 62 | john = new UserModel({ 63 | name: 'John', 64 | age: 35, 65 | city: city1, 66 | books: [book1, book2], 67 | }); 68 | jane = new UserModel({ 69 | name: 'Jane', 70 | age: 34, 71 | city: city1, 72 | books: [book1], 73 | }); 74 | bob = new UserModel({ name: 'Bob', age: 36, city: city2, books: [book2] }); 75 | 76 | return utils 77 | .deleteModelIndexes(UserModel) 78 | .then(() => { 79 | return Promise.all([ 80 | city1.save(), 81 | city2.save(), 82 | book1.save(), 83 | book2.save(), 84 | ]); 85 | }) 86 | .then(() => { 87 | return UserModel.esCreateMapping(); 88 | }) 89 | .then(() => { 90 | return utils.Promise.all( 91 | [john, jane, bob, author1, author2].map(user => { 92 | return new utils.Promise(resolve => { 93 | user.on('es-indexed', resolve); 94 | user.save(); 95 | }); 96 | }) 97 | ); 98 | }) 99 | .then(() => { 100 | return UserModel.esRefresh(); 101 | }); 102 | }); 103 | 104 | it('should hydrate', () => { 105 | return UserModel.esSearch( 106 | { 107 | query: { match_all: {} }, 108 | sort: [{ age: { order: 'desc' } }], 109 | filter: { range: { age: { gte: 35 } } }, 110 | }, 111 | { hydrate: true } 112 | ).then(result => { 113 | let hit; 114 | expect(result.hits.total).to.eql(2); 115 | 116 | hit = result.hits.hits[0]; 117 | expect(hit._source).to.be.undefined; 118 | expect(hit.doc).to.be.an.instanceof(UserModel); 119 | expect(hit.doc._id.toString()).to.eql(bob._id.toString()); 120 | expect(hit.doc.name).to.eql(bob.name); 121 | expect(hit.doc.age).to.eql(bob.age); 122 | 123 | hit = result.hits.hits[1]; 124 | expect(hit._source).to.be.undefined; 125 | expect(hit.doc).to.be.an.instanceof(UserModel); 126 | expect(hit.doc._id.toString()).to.eql(john._id.toString()); 127 | expect(hit.doc.name).to.eql(john.name); 128 | expect(hit.doc.age).to.eql(john.age); 129 | }); 130 | }); 131 | 132 | it('should hydrate returning only models', () => { 133 | return UserModel.esSearch( 134 | { 135 | query: { match_all: {} }, 136 | sort: [{ age: { order: 'desc' } }], 137 | filter: { range: { age: { gte: 35 } } }, 138 | }, 139 | { hydrate: { docsOnly: true } } 140 | ).then(users => { 141 | let user; 142 | expect(users.length).to.eql(2); 143 | 144 | user = users[0]; 145 | expect(user._id.toString()).to.eql(bob._id.toString()); 146 | expect(user.name).to.eql(bob.name); 147 | expect(user.age).to.eql(bob.age); 148 | 149 | user = users[1]; 150 | expect(user._id.toString()).to.eql(john._id.toString()); 151 | expect(user.name).to.eql(john.name); 152 | expect(user.age).to.eql(john.age); 153 | }); 154 | }); 155 | 156 | it('should return an empty array when hydrating only models on 0 hit', () => { 157 | return UserModel.esSearch( 158 | { 159 | query: { match_all: {} }, 160 | sort: [{ age: { order: 'desc' } }], 161 | filter: { range: { age: { gte: 100 } } }, 162 | }, 163 | { hydrate: { docsOnly: true } } 164 | ).then(users => { 165 | expect(users).to.eql([]); 166 | }); 167 | }); 168 | 169 | it('should hydrate using projection', () => { 170 | return UserModel.esSearch('name:jane', { 171 | hydrate: { select: 'name' }, 172 | }).then(result => { 173 | expect(result.hits.total).to.eql(1); 174 | const hit = result.hits.hits[0]; 175 | expect(hit._source).to.be.undefined; 176 | expect(hit.doc).to.be.an.instanceof(UserModel); 177 | expect(hit.doc._id.toString()).to.eql(jane._id.toString()); 178 | expect(hit.doc.name).to.eql(jane.name); 179 | expect(hit.doc.age).to.be.undefined; 180 | }); 181 | }); 182 | 183 | it('should hydrate using options', () => { 184 | return UserModel.esSearch('name:jane', { 185 | hydrate: { options: { lean: true } }, 186 | }).then(result => { 187 | expect(result.hits.total).to.eql(1); 188 | const hit = result.hits.hits[0]; 189 | expect(hit._source).to.be.undefined; 190 | expect(hit.doc).not.to.be.an.instanceof(UserModel); 191 | expect(hit.doc._id.toString()).to.eql(jane._id.toString()); 192 | expect(hit.doc.name).to.eql(jane.name); 193 | expect(hit.doc.age).to.eql(jane.age); 194 | }); 195 | }); 196 | 197 | it('should hydrate when defined in plugin', () => { 198 | utils.deleteMongooseModels(); 199 | 200 | const UserSchema = new mongoose.Schema({ 201 | name: String, 202 | age: Number, 203 | }); 204 | 205 | UserSchema.plugin(plugin, { hydrate: true }); 206 | 207 | const UserModelHydrate = mongoose.model('User', UserSchema); 208 | 209 | return UserModelHydrate.esSearch('name:jane').then(result => { 210 | expect(result.hits.total).to.eql(1); 211 | const hit = result.hits.hits[0]; 212 | expect(hit._source).to.be.undefined; 213 | expect(hit.doc).to.be.an.instanceof(UserModelHydrate); 214 | expect(hit.doc._id.toString()).to.eql(jane._id.toString()); 215 | expect(hit.doc.name).to.eql(jane.name); 216 | expect(hit.doc.age).to.eql(jane.age); 217 | }); 218 | }); 219 | 220 | it('should hydrate when defined in plugin returning only models', () => { 221 | utils.deleteMongooseModels(); 222 | 223 | const UserSchema = new mongoose.Schema({ 224 | name: String, 225 | age: Number, 226 | }); 227 | 228 | UserSchema.plugin(plugin, { hydrate: { docsOnly: true } }); 229 | 230 | const UserModelHydrate = mongoose.model('User', UserSchema); 231 | 232 | return UserModelHydrate.esSearch({ 233 | query: { match_all: {} }, 234 | sort: [{ age: { order: 'desc' } }], 235 | filter: { range: { age: { gte: 35 } } }, 236 | }).then(users => { 237 | let user; 238 | expect(users.length).to.eql(2); 239 | 240 | user = users[0]; 241 | expect(user._id.toString()).to.eql(bob._id.toString()); 242 | expect(user.name).to.eql(bob.name); 243 | expect(user.age).to.eql(bob.age); 244 | 245 | user = users[1]; 246 | expect(user._id.toString()).to.eql(john._id.toString()); 247 | expect(user.name).to.eql(john.name); 248 | expect(user.age).to.eql(john.age); 249 | }); 250 | }); 251 | 252 | it('should hydrate when defined in plugin using projection', () => { 253 | utils.deleteMongooseModels(); 254 | 255 | const UserSchema = new mongoose.Schema({ 256 | name: String, 257 | age: Number, 258 | }); 259 | 260 | UserSchema.plugin(plugin, { hydrate: { select: 'name' } }); 261 | 262 | const UserModelHydrate = mongoose.model('User', UserSchema); 263 | 264 | return UserModelHydrate.esSearch('name:jane').then(result => { 265 | expect(result.hits.total).to.eql(1); 266 | const hit = result.hits.hits[0]; 267 | expect(hit._source).to.be.undefined; 268 | expect(hit.doc).to.be.an.instanceof(UserModelHydrate); 269 | expect(hit.doc._id.toString()).to.eql(jane._id.toString()); 270 | expect(hit.doc.name).to.eql(jane.name); 271 | expect(hit.doc.age).to.be.undefined; 272 | }); 273 | }); 274 | 275 | it('should hydrate when defined in plugin using options', () => { 276 | utils.deleteMongooseModels(); 277 | 278 | const UserSchema = new mongoose.Schema({ 279 | name: String, 280 | age: Number, 281 | }); 282 | 283 | UserSchema.plugin(plugin, { hydrate: { options: { lean: true } } }); 284 | 285 | const UserModelHydrate = mongoose.model('User', UserSchema); 286 | 287 | return UserModelHydrate.esSearch('name:jane').then(result => { 288 | expect(result.hits.total).to.eql(1); 289 | const hit = result.hits.hits[0]; 290 | expect(hit._source).to.be.undefined; 291 | expect(hit.doc).not.to.be.an.instanceof(UserModelHydrate); 292 | expect(hit.doc._id.toString()).to.eql(jane._id.toString()); 293 | expect(hit.doc.name).to.eql(jane.name); 294 | expect(hit.doc.age).to.eql(jane.age); 295 | }); 296 | }); 297 | 298 | it('should hydrate overwriting defined in plugin using options', () => { 299 | utils.deleteMongooseModels(); 300 | 301 | const UserSchema = new mongoose.Schema({ 302 | name: String, 303 | age: Number, 304 | }); 305 | 306 | UserSchema.plugin(plugin, { hydrate: { options: { lean: true } } }); 307 | 308 | const UserModelHydrate = mongoose.model('User', UserSchema); 309 | 310 | return UserModelHydrate.esSearch( 311 | 'name:jane', 312 | { hydrate: { select: 'name' } } // not lean 313 | ).then(result => { 314 | expect(result.hits.total).to.eql(1); 315 | const hit = result.hits.hits[0]; 316 | expect(hit._source).to.be.undefined; 317 | expect(hit.doc).to.be.an.instanceof(UserModelHydrate); 318 | expect(hit.doc._id.toString()).to.eql(jane._id.toString()); 319 | expect(hit.doc.name).to.eql(jane.name); 320 | expect(hit.doc.age).to.be.undefined; 321 | }); 322 | }); 323 | 324 | it('should not hydrate overwriting defined in plugin', () => { 325 | utils.deleteMongooseModels(); 326 | 327 | const UserSchema = new mongoose.Schema({ 328 | name: String, 329 | age: Number, 330 | }); 331 | 332 | UserSchema.plugin(plugin, { hydrate: { options: { lean: true } } }); 333 | 334 | const UserModelHydrate = mongoose.model('User', UserSchema); 335 | 336 | return UserModelHydrate.esSearch( 337 | 'name:jane', 338 | { hydrate: false } // not lean 339 | ).then(result => { 340 | expect(result.hits.total).to.eql(1); 341 | const hit = result.hits.hits[0]; 342 | expect(hit.doc).to.be.undefined; 343 | expect(hit._source).not.to.be.undefined; 344 | expect(hit._source.name).to.eql(jane.name); 345 | expect(hit._source.age).to.eql(34); 346 | }); 347 | }); 348 | 349 | it('should hydrate with a simple populate', () => { 350 | return UserModel.esSearch( 351 | { 352 | query: { match_all: {} }, 353 | sort: [{ age: { order: 'desc' } }], 354 | filter: { range: { age: { gte: 35 } } }, 355 | }, 356 | { 357 | hydrate: { 358 | populate: { 359 | path: 'city', 360 | }, 361 | }, 362 | } 363 | ).then(result => { 364 | let hit; 365 | expect(result.hits.total).to.eql(2); 366 | 367 | hit = result.hits.hits[0]; 368 | 369 | expect(hit._source).to.be.undefined; 370 | expect(hit.doc).to.be.an.instanceof(UserModel); 371 | expect(hit.doc._id.toString()).to.eql(bob._id.toString()); 372 | expect(hit.doc.name).to.eql(bob.name); 373 | expect(hit.doc.age).to.eql(bob.age); 374 | expect(hit.doc.city._id.toString()).to.eql(city2._id.toString()); 375 | expect(hit.doc.city.name).to.eql(city2.name); 376 | // book should not be populated 377 | expect(hit.doc.books.length).to.eql(1); 378 | expect(hit.doc.books[0].toJSON()).to.eql(book2._id.toString()); 379 | 380 | hit = result.hits.hits[1]; 381 | expect(hit._source).to.be.undefined; 382 | expect(hit.doc).to.be.an.instanceof(UserModel); 383 | expect(hit.doc._id.toString()).to.eql(john._id.toString()); 384 | expect(hit.doc.name).to.eql(john.name); 385 | expect(hit.doc.age).to.eql(john.age); 386 | expect(hit.doc.city._id.toString()).to.eql(city1._id.toString()); 387 | expect(hit.doc.city.name).to.eql(city1.name); 388 | // book should not be populated 389 | expect(hit.doc.books.length).to.eql(2); 390 | expect(hit.doc.books[0].toJSON()).to.eql(book1._id.toString()); 391 | expect(hit.doc.books[1].toJSON()).to.eql(book2._id.toString()); 392 | }); 393 | }); 394 | 395 | it('should hydrate with an array of population including a complex one', () => { 396 | return UserModel.esSearch( 397 | { 398 | query: { match_all: {} }, 399 | sort: [{ age: { order: 'desc' } }], 400 | filter: { range: { age: { gte: 35 } } }, 401 | }, 402 | { 403 | hydrate: { 404 | populate: [ 405 | { 406 | path: 'city', 407 | }, 408 | { 409 | path: 'books', 410 | populate: { 411 | path: 'author', 412 | }, 413 | }, 414 | ], 415 | }, 416 | } 417 | ).then(result => { 418 | let hit; 419 | expect(result.hits.total).to.eql(2); 420 | 421 | hit = result.hits.hits[0]; 422 | 423 | expect(hit._source).to.be.undefined; 424 | expect(hit.doc).to.be.an.instanceof(UserModel); 425 | expect(hit.doc._id.toString()).to.eql(bob._id.toString()); 426 | expect(hit.doc.name).to.eql(bob.name); 427 | expect(hit.doc.age).to.eql(bob.age); 428 | 429 | expect(hit.doc.city._id.toString()).to.eql(city2._id.toString()); 430 | expect(hit.doc.city.name).to.eql(city2.name); 431 | 432 | expect(hit.doc.books.length).to.eql(1); 433 | expect(hit.doc.books[0].toJSON()).to.eql(book2.toJSON()); 434 | expect(hit.doc.books[0].author.toJSON()).to.eql(author2.toJSON()); 435 | 436 | hit = result.hits.hits[1]; 437 | expect(hit._source).to.be.undefined; 438 | expect(hit.doc).to.be.an.instanceof(UserModel); 439 | expect(hit.doc._id.toString()).to.eql(john._id.toString()); 440 | expect(hit.doc.name).to.eql(john.name); 441 | expect(hit.doc.age).to.eql(john.age); 442 | 443 | expect(hit.doc.city._id.toString()).to.eql(city1._id.toString()); 444 | expect(hit.doc.city.name).to.eql(city1.name); 445 | 446 | expect(hit.doc.books.length).to.eql(2); 447 | expect(hit.doc.books[0].toJSON()).to.eql(book1.toJSON()); 448 | expect(hit.doc.books[0].author.toJSON()).to.eql(author1.toJSON()); 449 | expect(hit.doc.books[1].toJSON()).to.eql(book2.toJSON()); 450 | expect(hit.doc.books[1].author.toJSON()).to.eql(author2.toJSON()); 451 | }); 452 | }); 453 | }); 454 | -------------------------------------------------------------------------------- /test/es2/idsOnly.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v2; 6 | 7 | describe('idsOnly', () => { 8 | utils.setup(); 9 | let UserModel; 10 | let john; 11 | let jane; 12 | let bob; 13 | 14 | beforeEach(() => { 15 | const UserSchema = new mongoose.Schema({ 16 | name: String, 17 | age: Number, 18 | }); 19 | 20 | UserSchema.plugin(plugin); 21 | 22 | UserModel = mongoose.model('User', UserSchema); 23 | 24 | john = new UserModel({ name: 'John', age: 35 }); 25 | jane = new UserModel({ name: 'Jane', age: 34 }); 26 | bob = new UserModel({ name: 'Bob', age: 36 }); 27 | 28 | return utils 29 | .deleteModelIndexes(UserModel) 30 | .then(() => { 31 | return UserModel.esCreateMapping(); 32 | }) 33 | .then(() => { 34 | return utils.Promise.all( 35 | [john, jane, bob].map(user => { 36 | return new utils.Promise(resolve => { 37 | user.on('es-indexed', resolve); 38 | user.save(); 39 | }); 40 | }) 41 | ); 42 | }) 43 | .then(() => { 44 | return UserModel.esRefresh(); 45 | }); 46 | }); 47 | 48 | it('should return ids', () => { 49 | return UserModel.esSearch( 50 | { 51 | query: { match_all: {} }, 52 | sort: [{ age: { order: 'desc' } }], 53 | filter: { range: { age: { gte: 35 } } }, 54 | }, 55 | { idsOnly: true } 56 | ).then(ids => { 57 | expect(ids.length).to.eql(2); 58 | const idstrings = ids.map(id => { 59 | expect(id).to.be.an.instanceof(mongoose.Types.ObjectId); 60 | return id.toString(); 61 | }); 62 | expect(idstrings).to.eql([bob._id.toString(), john._id.toString()]); 63 | }); 64 | }); 65 | 66 | it('should an empty array', () => { 67 | return UserModel.esSearch( 68 | { 69 | query: { match_all: {} }, 70 | sort: [{ age: { order: 'desc' } }], 71 | filter: { range: { age: { gte: 100 } } }, 72 | }, 73 | { idsOnly: true } 74 | ).then(ids => { 75 | expect(ids).to.eql([]); 76 | }); 77 | }); 78 | 79 | it('should return ids when defined in plugin', () => { 80 | utils.deleteMongooseModels(); 81 | 82 | const UserSchema = new mongoose.Schema({ 83 | name: String, 84 | age: Number, 85 | }); 86 | 87 | UserSchema.plugin(plugin, { idsOnly: true }); 88 | 89 | const UserModelIdsOnly = mongoose.model('User', UserSchema); 90 | 91 | return UserModelIdsOnly.esSearch({ 92 | query: { match_all: {} }, 93 | sort: [{ age: { order: 'desc' } }], 94 | filter: { range: { age: { gte: 35 } } }, 95 | }).then(ids => { 96 | expect(ids.length).to.eql(2); 97 | const idstrings = ids.map(id => { 98 | expect(id).to.be.an.instanceof(mongoose.Types.ObjectId); 99 | return id.toString(); 100 | }); 101 | expect(idstrings).to.eql([bob._id.toString(), john._id.toString()]); 102 | }); 103 | }); 104 | 105 | it('should overwrite defined in plugin value', () => { 106 | utils.deleteMongooseModels(); 107 | 108 | const UserSchema = new mongoose.Schema({ 109 | name: String, 110 | age: Number, 111 | }); 112 | 113 | UserSchema.plugin(plugin, { idsOnly: true }); 114 | 115 | const UserModelIdsOnly = mongoose.model('User', UserSchema); 116 | 117 | return UserModelIdsOnly.esSearch( 118 | { 119 | query: { match_all: {} }, 120 | sort: [{ age: { order: 'desc' } }], 121 | filter: { range: { age: { gte: 35 } } }, 122 | }, 123 | { idsOnly: false } 124 | ).then(result => { 125 | expect(result.hits.total).to.eql(2); 126 | let hit = result.hits.hits[0]; 127 | expect(hit._id).to.eql(bob._id.toString()); 128 | expect(hit._source).to.eql({ name: 'Bob', age: 36 }); 129 | 130 | hit = result.hits.hits[1]; 131 | expect(hit._id).to.eql(john._id.toString()); 132 | expect(hit._source).to.eql({ name: 'John', age: 35 }); 133 | }); 134 | }); 135 | }); 136 | -------------------------------------------------------------------------------- /test/es2/promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | 6 | const plugin = require('../../').v2; 7 | 8 | describe('promise', () => { 9 | utils.setup(); 10 | 11 | it('should return promise', () => { 12 | const UserSchema = new mongoose.Schema({ 13 | name: String, 14 | }); 15 | 16 | UserSchema.plugin(plugin); 17 | 18 | const UserModel = mongoose.model('User', UserSchema); 19 | const promise = UserModel.esCreateMapping(); 20 | expect(promise).to.be.an.instanceof(Promise); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/es5/countOnly.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('countOnly', () => { 8 | utils.setup(); 9 | let UserModel; 10 | 11 | beforeEach(() => { 12 | const UserSchema = new mongoose.Schema({ 13 | name: String, 14 | age: Number, 15 | }); 16 | 17 | UserSchema.plugin(plugin); 18 | UserModel = mongoose.model('User', UserSchema); 19 | 20 | const john = new UserModel({ name: 'John', age: 35 }); 21 | const jane = new UserModel({ name: 'Jane', age: 34 }); 22 | const bob = new UserModel({ name: 'Bob', age: 36 }); 23 | 24 | return utils 25 | .deleteModelIndexes(UserModel) 26 | .then(() => UserModel.esCreateMapping()) 27 | .then(() => 28 | utils.Promise.all( 29 | [john, jane, bob].map( 30 | user => 31 | new utils.Promise(resolve => { 32 | user.on('es-indexed', resolve); 33 | user.save(); 34 | }) 35 | ) 36 | ) 37 | ) 38 | .then(() => UserModel.esRefresh()); 39 | }); 40 | 41 | it('should return count', () => { 42 | return UserModel.esCount( 43 | { 44 | bool: { 45 | must: { match_all: {} }, 46 | filter: { range: { age: { gte: 35 } } }, 47 | }, 48 | }, 49 | { countOnly: true } 50 | ).then(count => { 51 | expect(count).to.eql(2); 52 | }); 53 | }); 54 | 55 | it('should return 0', () => { 56 | return UserModel.esCount( 57 | { 58 | bool: { 59 | must: { match_all: {} }, 60 | filter: { range: { age: { gte: 100 } } }, 61 | }, 62 | }, 63 | { countOnly: true } 64 | ).then(count => { 65 | expect(count).to.eql(0); 66 | }); 67 | }); 68 | 69 | it('should return count when defined in plugin', () => { 70 | utils.deleteMongooseModels(); 71 | 72 | const UserSchema = new mongoose.Schema({ 73 | name: String, 74 | age: Number, 75 | }); 76 | 77 | UserSchema.plugin(plugin, { countOnly: true }); 78 | 79 | const UserModelCountOnly = mongoose.model('User', UserSchema); 80 | 81 | return UserModelCountOnly.esCount({ 82 | bool: { 83 | must: { match_all: {} }, 84 | filter: { range: { age: { gte: 35 } } }, 85 | }, 86 | }).then(count => { 87 | expect(count).to.eql(2); 88 | }); 89 | }); 90 | 91 | it('should overwrite defined in plugin value', () => { 92 | utils.deleteMongooseModels(); 93 | 94 | const UserSchema = new mongoose.Schema({ 95 | name: String, 96 | age: Number, 97 | }); 98 | 99 | UserSchema.plugin(plugin, { countOnly: true }); 100 | 101 | const UserModelCountOnly = mongoose.model('User', UserSchema); 102 | 103 | return UserModelCountOnly.esCount( 104 | { 105 | bool: { 106 | must: { match_all: {} }, 107 | filter: { range: { age: { gte: 35 } } }, 108 | }, 109 | }, 110 | { countOnly: false } 111 | ).then(result => { 112 | expect(result.count).to.eql(2); 113 | }); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /test/es5/customIds.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const shortid = require('shortid'); 5 | const utils = require('../utils'); 6 | const plugin = require('../../').v5; 7 | 8 | describe('custom ids', () => { 9 | utils.setup(); 10 | let UserModel; 11 | let john; 12 | let jane; 13 | let bob; 14 | 15 | beforeEach(() => { 16 | const UserSchema = new mongoose.Schema({ 17 | _id: { 18 | type: String, 19 | default: shortid.generate, 20 | }, 21 | name: String, 22 | age: Number, 23 | }); 24 | 25 | UserSchema.plugin(plugin); 26 | 27 | UserModel = mongoose.model('User', UserSchema); 28 | 29 | john = new UserModel({ name: 'John', age: 35 }); 30 | jane = new UserModel({ name: 'Jane', age: 34 }); 31 | bob = new UserModel({ name: 'Bob', age: 36 }); 32 | 33 | return utils 34 | .deleteModelIndexes(UserModel) 35 | .then(() => { 36 | return UserModel.esCreateMapping(); 37 | }) 38 | .then(() => { 39 | return utils.Promise.all( 40 | [john, jane, bob].map(user => { 41 | return new utils.Promise(resolve => { 42 | user.on('es-indexed', resolve); 43 | user.save(); 44 | }); 45 | }) 46 | ); 47 | }) 48 | .then(() => { 49 | return UserModel.esRefresh(); 50 | }); 51 | }); 52 | 53 | it('should return ids', () => { 54 | return UserModel.esSearch( 55 | { 56 | query: { 57 | bool: { 58 | must: { match_all: {} }, 59 | filter: { range: { age: { gte: 35 } } }, 60 | }, 61 | }, 62 | sort: [{ age: { order: 'desc' } }], 63 | }, 64 | { idsOnly: true } 65 | ).then(ids => { 66 | expect(ids.length).to.eql(2); 67 | expect(ids).to.eql([bob._id, john._id]); 68 | }); 69 | }); 70 | 71 | it('should return people', () => { 72 | return UserModel.esSearch({ 73 | query: { 74 | bool: { 75 | must: { match_all: {} }, 76 | filter: { range: { age: { gte: 35 } } }, 77 | }, 78 | }, 79 | sort: [{ age: { order: 'desc' } }], 80 | }).then(result => { 81 | expect(result.hits.total).to.eql(2); 82 | expect(result.hits.hits[0]._source).to.eql({ name: 'Bob', age: 36 }); 83 | expect(result.hits.hits[1]._source).to.eql({ name: 'John', age: 35 }); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/es5/esCount.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('esCount', () => { 8 | utils.setup(); 9 | let UserModel; 10 | 11 | beforeEach(() => { 12 | const UserSchema = new mongoose.Schema({ 13 | name: String, 14 | age: Number, 15 | }); 16 | 17 | UserSchema.plugin(plugin); 18 | UserModel = mongoose.model('User', UserSchema); 19 | 20 | const john = new UserModel({ name: 'John', age: 35 }); 21 | const jane = new UserModel({ name: 'Jane', age: 34 }); 22 | const bob = new UserModel({ name: 'Bob', age: 36 }); 23 | 24 | return utils 25 | .deleteModelIndexes(UserModel) 26 | .then(() => { 27 | return UserModel.esCreateMapping(); 28 | }) 29 | .then(() => { 30 | const options = UserModel.esOptions(); 31 | const client = options.client; 32 | return client.bulk({ 33 | refresh: true, 34 | body: [ 35 | { 36 | index: { 37 | _index: options.index, 38 | _type: options.type, 39 | _id: john._id.toString(), 40 | }, 41 | }, 42 | { name: 'John', age: 35 }, 43 | { 44 | index: { 45 | _index: options.index, 46 | _type: options.type, 47 | _id: jane._id.toString(), 48 | }, 49 | }, 50 | { name: 'Jane', age: 34 }, 51 | { 52 | index: { 53 | _index: options.index, 54 | _type: options.type, 55 | _id: bob._id.toString(), 56 | }, 57 | }, 58 | { name: 'Bob', age: 36 }, 59 | ], 60 | }); 61 | }); 62 | }); 63 | 64 | it('should handle a lucene query', () => { 65 | return UserModel.esCount('name:jane').then(result => { 66 | expect(result.count).to.eql(1); 67 | }); 68 | }); 69 | 70 | it('should accept callback', done => { 71 | const returned = UserModel.esCount('name:jane', (err, result) => { 72 | if (err) { 73 | done(err); 74 | return; 75 | } 76 | expect(result.count).to.eql(1); 77 | expect(returned).to.be.undefined; 78 | done(); 79 | }); 80 | }); 81 | 82 | it('should handle a full query', () => { 83 | return UserModel.esCount({ 84 | bool: { 85 | must: { match_all: {} }, 86 | filter: { range: { age: { lt: 35 } } }, 87 | }, 88 | }).then(result => { 89 | expect(result.count).to.eql(1); 90 | }); 91 | }); 92 | 93 | it('should handle a short query', () => { 94 | return UserModel.esCount({ match: { age: 34 } }).then(result => { 95 | expect(result.count).to.eql(1); 96 | }); 97 | }); 98 | 99 | it('should handle 0 hit', () => { 100 | return UserModel.esCount({ match: { age: 100 } }).then(result => { 101 | expect(result.count).to.eql(0); 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /test/es5/esIndex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('esIndex', () => { 8 | utils.setup(); 9 | 10 | it('should be indexed', () => { 11 | const UserSchema = new mongoose.Schema({ 12 | name: String, 13 | age: Number, 14 | pos: { 15 | type: [Number], 16 | index: '2dsphere', 17 | es_type: 'geo_point', 18 | es_boost: 1.5, 19 | }, 20 | }); 21 | 22 | UserSchema.plugin(plugin); 23 | const UserModel = mongoose.model('User', UserSchema); 24 | 25 | const john = new UserModel({ name: 'John', age: 35, pos: [5.7333, 43.5] }); 26 | 27 | return utils 28 | .deleteModelIndexes(UserModel) 29 | .then(() => { 30 | return UserModel.esCreateMapping(); 31 | }) 32 | .then(() => { 33 | return john.esIndex(); 34 | }) 35 | .then(() => { 36 | return UserModel.esRefresh(); 37 | }) 38 | .then(() => { 39 | const options = UserModel.esOptions(); 40 | const client = options.client; 41 | return client.search({ 42 | index: options.index, 43 | type: options.type, 44 | body: { query: { match_all: {} } }, 45 | }); 46 | }) 47 | .then(resp => { 48 | expect(resp.hits.total).to.eql(1); 49 | const hit = resp.hits.hits[0]; 50 | expect(hit._id).to.eql(john._id.toString()); 51 | expect(hit._source).to.eql({ 52 | name: 'John', 53 | age: 35, 54 | pos: [5.7333, 43.5], 55 | }); 56 | }); 57 | }); 58 | 59 | it('should index ObjectId from object populated or not', () => { 60 | const CountrySchema = new mongoose.Schema({ 61 | name: String, 62 | }); 63 | 64 | const CitySchema = new mongoose.Schema({ 65 | name: String, 66 | }); 67 | 68 | const UserSchema = new mongoose.Schema({ 69 | name: String, 70 | city: { type: mongoose.Schema.Types.ObjectId, ref: 'City' }, 71 | country: { type: mongoose.Schema.Types.ObjectId, ref: 'Country' }, 72 | }); 73 | 74 | UserSchema.plugin(plugin); 75 | 76 | const UserModel = mongoose.model('User', UserSchema); 77 | const CityModel = mongoose.model('City', CitySchema); 78 | const CountryModel = mongoose.model('Country', CountrySchema); 79 | 80 | const country = new CountryModel({ name: 'France' }); 81 | const city = new CityModel({ name: 'Paris' }); 82 | const john = new UserModel({ 83 | name: 'John', 84 | city, 85 | country: country._id, 86 | }); 87 | 88 | return utils 89 | .deleteModelIndexes(UserModel) 90 | .then(() => { 91 | return UserModel.esCreateMapping(); 92 | }) 93 | .then(() => { 94 | return john.esIndex(); 95 | }) 96 | .then(() => { 97 | return UserModel.esRefresh(); 98 | }) 99 | .then(() => { 100 | const options = UserModel.esOptions(); 101 | const client = options.client; 102 | return client.search({ 103 | index: options.index, 104 | type: options.type, 105 | body: { query: { match_all: {} } }, 106 | }); 107 | }) 108 | .then(resp => { 109 | expect(resp.hits.total).to.eql(1); 110 | const hit = resp.hits.hits[0]; 111 | expect(hit._id).to.eql(john._id.toString()); 112 | expect(hit._source).to.eql({ 113 | name: 'John', 114 | city: city.id, 115 | country: country.id, 116 | }); 117 | }); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /test/es5/esRefresh.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('esRefresh', () => { 8 | utils.setup(); 9 | 10 | it('should handle callback', done => { 11 | const UserSchema = new mongoose.Schema({ 12 | name: String, 13 | }); 14 | 15 | UserSchema.plugin(plugin); 16 | const UserModel = mongoose.model('User', UserSchema); 17 | 18 | let start; 19 | 20 | utils 21 | .deleteModelIndexes(UserModel) 22 | .then(() => { 23 | return UserModel.esCreateMapping(); 24 | }) 25 | .then(() => { 26 | start = Date.now(); 27 | UserModel.esRefresh(err => { 28 | if (err) { 29 | done(err); 30 | return; 31 | } 32 | expect(Date.now() - start).to.be.lt(500); 33 | done(); 34 | }); 35 | }) 36 | .catch(err => { 37 | done(err); 38 | }); 39 | }); 40 | 41 | it('should handle callback and options', done => { 42 | const UserSchema = new mongoose.Schema({ 43 | name: String, 44 | }); 45 | 46 | UserSchema.plugin(plugin); 47 | 48 | const UserModel = mongoose.model('User', UserSchema); 49 | 50 | let start; 51 | 52 | utils 53 | .deleteModelIndexes(UserModel) 54 | .then(() => { 55 | return UserModel.esCreateMapping(); 56 | }) 57 | .then(() => { 58 | start = Date.now(); 59 | UserModel.esRefresh({ refreshDelay: 1000 }, err => { 60 | if (err) { 61 | done(err); 62 | return; 63 | } 64 | expect(Date.now() - start).to.be.gte(1000); 65 | done(); 66 | }); 67 | }) 68 | .catch(err => { 69 | done(err); 70 | }); 71 | }); 72 | 73 | it('should not be delayed', () => { 74 | const UserSchema = new mongoose.Schema({ 75 | name: String, 76 | }); 77 | 78 | UserSchema.plugin(plugin); 79 | 80 | const UserModel = mongoose.model('User', UserSchema); 81 | 82 | let start; 83 | 84 | return utils 85 | .deleteModelIndexes(UserModel) 86 | .then(() => { 87 | return UserModel.esCreateMapping(); 88 | }) 89 | .then(() => { 90 | start = Date.now(); 91 | return UserModel.esRefresh(); 92 | }) 93 | .then(() => { 94 | expect(Date.now() - start).to.be.lt(500); 95 | }); 96 | }); 97 | 98 | it('should be delayed', () => { 99 | const UserSchema = new mongoose.Schema({ 100 | name: String, 101 | }); 102 | 103 | UserSchema.plugin(plugin); 104 | 105 | const UserModel = mongoose.model('User', UserSchema); 106 | 107 | let start; 108 | 109 | return utils 110 | .deleteModelIndexes(UserModel) 111 | .then(() => { 112 | return UserModel.esCreateMapping(); 113 | }) 114 | .then(() => { 115 | start = Date.now(); 116 | return UserModel.esRefresh({ refreshDelay: 1000 }); 117 | }) 118 | .then(() => { 119 | expect(Date.now() - start).to.be.gte(1000); 120 | }); 121 | }); 122 | 123 | it('should be delayed when defined in plugin', () => { 124 | const UserSchema = new mongoose.Schema({ 125 | name: String, 126 | }); 127 | 128 | UserSchema.plugin(plugin, { refreshDelay: 1000 }); 129 | 130 | const UserModel = mongoose.model('User', UserSchema); 131 | 132 | let start; 133 | 134 | return utils 135 | .deleteModelIndexes(UserModel) 136 | .then(() => { 137 | return UserModel.esCreateMapping(); 138 | }) 139 | .then(() => { 140 | start = Date.now(); 141 | return UserModel.esRefresh(); 142 | }) 143 | .then(() => { 144 | expect(Date.now() - start).to.be.gte(1000); 145 | }); 146 | }); 147 | 148 | it('should overwrite defined in plugin value', () => { 149 | const UserSchema = new mongoose.Schema({ 150 | name: String, 151 | }); 152 | 153 | UserSchema.plugin(plugin, { refreshDelay: 1000 }); 154 | 155 | const UserModel = mongoose.model('User', UserSchema); 156 | 157 | let start; 158 | 159 | return utils 160 | .deleteModelIndexes(UserModel) 161 | .then(() => { 162 | return UserModel.esCreateMapping(); 163 | }) 164 | .then(() => { 165 | start = Date.now(); 166 | return UserModel.esRefresh({ refreshDelay: false }); 167 | }) 168 | .then(() => { 169 | expect(Date.now() - start).to.be.lt(500); 170 | }); 171 | }); 172 | }); 173 | -------------------------------------------------------------------------------- /test/es5/esRemove.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('esRemove', () => { 8 | utils.setup(); 9 | 10 | it('should be removed', () => { 11 | const UserSchema = new mongoose.Schema({ 12 | name: String, 13 | age: Number, 14 | }); 15 | 16 | UserSchema.plugin(plugin); 17 | 18 | const UserModel = mongoose.model('User', UserSchema); 19 | 20 | const john = new UserModel({ name: 'John', age: 35 }); 21 | const jane = new UserModel({ name: 'Jane', age: 34 }); 22 | const bob = new UserModel({ name: 'Bob', age: 36 }); 23 | 24 | return utils 25 | .deleteModelIndexes(UserModel) 26 | .then(() => { 27 | return UserModel.esCreateMapping(); 28 | }) 29 | .then(() => { 30 | const options = UserModel.esOptions(); 31 | const client = options.client; 32 | return client.bulk({ 33 | refresh: true, 34 | body: [ 35 | { 36 | index: { 37 | _index: options.index, 38 | _type: options.type, 39 | _id: john._id.toString(), 40 | }, 41 | }, 42 | { name: 'John', age: 35 }, 43 | { 44 | index: { 45 | _index: options.index, 46 | _type: options.type, 47 | _id: jane._id.toString(), 48 | }, 49 | }, 50 | { name: 'Jane', age: 34 }, 51 | { 52 | index: { 53 | _index: options.index, 54 | _type: options.type, 55 | _id: bob._id.toString(), 56 | }, 57 | }, 58 | { name: 'Bob', age: 36 }, 59 | ], 60 | }); 61 | }) 62 | .then(() => { 63 | return jane.esRemove(); 64 | }) 65 | .then(() => { 66 | return UserModel.esRefresh(); 67 | }) 68 | .then(() => { 69 | const options = UserModel.esOptions(); 70 | const client = options.client; 71 | return client.search({ 72 | index: options.index, 73 | type: options.type, 74 | body: { query: { match_all: {} } }, 75 | }); 76 | }) 77 | .then(resp => { 78 | const ids = resp.hits.hits.map(hit => { 79 | return hit._id; 80 | }); 81 | ids.sort(); 82 | 83 | const expectedIds = [john, bob].map(user => { 84 | return user._id.toString(); 85 | }); 86 | 87 | expect(ids).to.eql(expectedIds); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/es5/esSearch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('esSearch', () => { 8 | utils.setup(); 9 | let UserModel; 10 | let john; 11 | let jane; 12 | let bob; 13 | 14 | beforeEach(() => { 15 | const UserSchema = new mongoose.Schema({ 16 | name: String, 17 | age: Number, 18 | }); 19 | 20 | UserSchema.plugin(plugin); 21 | UserModel = mongoose.model('User', UserSchema); 22 | 23 | john = new UserModel({ name: 'John', age: 35 }); 24 | jane = new UserModel({ name: 'Jane', age: 34 }); 25 | bob = new UserModel({ name: 'Bob', age: 36 }); 26 | 27 | return utils 28 | .deleteModelIndexes(UserModel) 29 | .then(() => { 30 | return UserModel.esCreateMapping(); 31 | }) 32 | .then(() => { 33 | const options = UserModel.esOptions(); 34 | const client = options.client; 35 | return client.bulk({ 36 | refresh: true, 37 | body: [ 38 | { 39 | index: { 40 | _index: options.index, 41 | _type: options.type, 42 | _id: john._id.toString(), 43 | }, 44 | }, 45 | { name: 'John', age: 35 }, 46 | { 47 | index: { 48 | _index: options.index, 49 | _type: options.type, 50 | _id: jane._id.toString(), 51 | }, 52 | }, 53 | { name: 'Jane', age: 34 }, 54 | { 55 | index: { 56 | _index: options.index, 57 | _type: options.type, 58 | _id: bob._id.toString(), 59 | }, 60 | }, 61 | { name: 'Bob', age: 36 }, 62 | ], 63 | }); 64 | }); 65 | }); 66 | 67 | it('should handle a lucene query', () => { 68 | return UserModel.esSearch('name:jane').then(result => { 69 | expect(result.hits.total).to.eql(1); 70 | const hit = result.hits.hits[0]; 71 | expect(hit._id).to.eql(jane._id.toString()); 72 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 73 | }); 74 | }); 75 | 76 | it('should accept callback', done => { 77 | const returned = UserModel.esSearch('name:jane', {}, (err, result) => { 78 | if (err) { 79 | done(err); 80 | return; 81 | } 82 | expect(result.hits.total).to.eql(1); 83 | const hit = result.hits.hits[0]; 84 | expect(hit._id).to.eql(jane._id.toString()); 85 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 86 | expect(returned).to.be.undefined; 87 | done(); 88 | }); 89 | }); 90 | 91 | it('should accept callback without options', done => { 92 | const returned = UserModel.esSearch('name:jane', (err, result) => { 93 | if (err) { 94 | done(err); 95 | return; 96 | } 97 | expect(result.hits.total).to.eql(1); 98 | const hit = result.hits.hits[0]; 99 | expect(hit._id).to.eql(jane._id.toString()); 100 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 101 | expect(returned).to.be.undefined; 102 | done(); 103 | }); 104 | }); 105 | 106 | it('should handle a full query', () => { 107 | return UserModel.esSearch({ 108 | bool: { 109 | must: { match_all: {} }, 110 | filter: { range: { age: { lt: 35 } } }, 111 | }, 112 | }).then(result => { 113 | expect(result.hits.total).to.eql(1); 114 | const hit = result.hits.hits[0]; 115 | expect(hit._id).to.eql(jane._id.toString()); 116 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 117 | }); 118 | }); 119 | 120 | it('should handle a short query', () => { 121 | return UserModel.esSearch({ match: { age: 34 } }).then(result => { 122 | expect(result.hits.total).to.eql(1); 123 | const hit = result.hits.hits[0]; 124 | expect(hit._id).to.eql(jane._id.toString()); 125 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 126 | }); 127 | }); 128 | 129 | it('should handle 0 hit', () => { 130 | return UserModel.esSearch({ match: { age: 100 } }).then(result => { 131 | expect(result.hits.total).to.eql(0); 132 | }); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /test/es5/es_extend.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('es_extend', () => { 8 | utils.setup(); 9 | 10 | it('should add some fields', () => { 11 | let john; 12 | 13 | const UserSchema = new mongoose.Schema( 14 | { 15 | name: String, 16 | }, 17 | { 18 | es_extend: { 19 | num: { 20 | es_type: 'integer', 21 | es_value: 123, 22 | }, 23 | length: { 24 | es_type: 'integer', 25 | es_value(document) { 26 | expect(document === john).to.be.true; 27 | return document.name.length; 28 | }, 29 | }, 30 | }, 31 | } 32 | ); 33 | 34 | UserSchema.plugin(plugin); 35 | 36 | const UserModel = mongoose.model('User', UserSchema); 37 | 38 | john = new UserModel({ 39 | name: 'John', 40 | }); 41 | 42 | return utils 43 | .deleteModelIndexes(UserModel) 44 | .then(() => { 45 | return UserModel.esCreateMapping(); 46 | }) 47 | .then(() => { 48 | const options = UserModel.esOptions(); 49 | return options.client.indices.getMapping({ 50 | index: options.index, 51 | type: options.type, 52 | }); 53 | }) 54 | .then(mapping => { 55 | const properties = mapping.users.mappings.user.properties; 56 | expect(properties).to.have.all.keys('name', 'num', 'length'); 57 | expect(properties.name.type).to.be.equal('text'); 58 | expect(properties.num.type).to.be.equal('integer'); 59 | expect(properties.length.type).to.be.equal('integer'); 60 | }) 61 | .then(() => { 62 | return new utils.Promise(resolve => { 63 | john.on('es-indexed', () => { 64 | resolve(); 65 | }); 66 | john.save(); 67 | }); 68 | }) 69 | .then(() => { 70 | return UserModel.esRefresh(); 71 | }) 72 | .then(() => { 73 | return UserModel.esSearch({ 74 | query: { match_all: {} }, 75 | }); 76 | }) 77 | .then(result => { 78 | expect(result.hits.total).to.eql(1); 79 | expect(result.hits.hits[0]._source).to.eql({ 80 | name: 'John', 81 | num: 123, 82 | length: 4, 83 | }); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/es5/es_value.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('es_value', () => { 8 | utils.setup(); 9 | 10 | it('should handle es_value as a function', () => { 11 | let john; 12 | 13 | const Sub2Schema = new mongoose.Schema({ 14 | _id: false, 15 | value: { 16 | type: String, 17 | es_value(value, context) { 18 | expect(value).to.equal('x2'); 19 | expect(context.document === john).to.be.true; 20 | expect(context.container === john.sub.sub2).to.be.true; 21 | expect(context.field).to.eql('value'); 22 | return 'xyz'; 23 | }, 24 | }, 25 | }); 26 | 27 | const SubSchema = new mongoose.Schema({ 28 | _id: false, 29 | sub1: String, 30 | sub2: Sub2Schema, 31 | }); 32 | 33 | const TagSchema = new mongoose.Schema({ 34 | _id: false, 35 | value: String, 36 | }); 37 | 38 | const UserSchema = new mongoose.Schema({ 39 | name: String, 40 | sub: SubSchema, 41 | age: { 42 | type: Number, 43 | es_value(age, context) { 44 | expect(age).to.equal(35); 45 | expect(context.document === john).to.be.true; 46 | expect(context.container === john).to.be.true; 47 | expect(context.field).to.eql('age'); 48 | return age - (age % 10); 49 | }, 50 | }, 51 | tags: { 52 | type: [TagSchema], 53 | es_type: 'string', 54 | es_value(tags, context) { 55 | expect(tags === john.tags).to.be.true; 56 | expect(context.document === john).to.be.true; 57 | expect(context.container === john).to.be.true; 58 | expect(context.field).to.eql('tags'); 59 | return tags.map(tag => { 60 | return tag.value; 61 | }); 62 | }, 63 | }, 64 | }); 65 | 66 | UserSchema.plugin(plugin); 67 | 68 | const UserModel = mongoose.model('User', UserSchema); 69 | 70 | john = new UserModel({ 71 | name: 'John', 72 | age: 35, 73 | sub: { 74 | sub1: 'x1', 75 | sub2: { 76 | value: 'x2', 77 | nb: 7, 78 | }, 79 | }, 80 | tags: [{ value: 'cool' }, { value: 'green' }], 81 | }); 82 | 83 | return utils 84 | .deleteModelIndexes(UserModel) 85 | .then(() => { 86 | return UserModel.esCreateMapping(); 87 | }) 88 | .then(() => { 89 | return john.esIndex(); 90 | }) 91 | .then(() => { 92 | return UserModel.esRefresh(); 93 | }) 94 | .then(() => { 95 | const options = UserModel.esOptions(); 96 | const client = options.client; 97 | return client.search({ 98 | index: options.index, 99 | type: options.type, 100 | body: { query: { match_all: {} } }, 101 | }); 102 | }) 103 | .then(resp => { 104 | expect(resp.hits.total).to.eql(1); 105 | const hit = resp.hits.hits[0]; 106 | expect(hit._id).to.eql(john._id.toString()); 107 | expect(hit._source).to.eql({ 108 | name: 'John', 109 | age: 30, 110 | tags: ['cool', 'green'], 111 | sub: { sub1: 'x1', sub2: { value: 'xyz' } }, 112 | }); 113 | }); 114 | }); 115 | 116 | it('should handle es_value as a value', () => { 117 | const UserSchema = new mongoose.Schema({ 118 | name: { type: String, es_type: 'keyword' }, 119 | numberArray: { 120 | type: Number, 121 | es_value: [1, 2, 3], 122 | }, 123 | falsy: { 124 | type: Number, 125 | es_value: 0, 126 | }, 127 | stringArray: { 128 | type: [String], 129 | es_value: ['az', 'er', 'ty'], 130 | }, 131 | obj: { 132 | type: String, 133 | es_type: { 134 | a: { es_type: 'string' }, 135 | b: { es_type: 'integer' }, 136 | }, 137 | es_value: { 138 | a: 'azerty', 139 | b: 123, 140 | }, 141 | }, 142 | }); 143 | 144 | UserSchema.plugin(plugin); 145 | 146 | const UserModel = mongoose.model('User', UserSchema); 147 | 148 | const john = new UserModel({ 149 | name: 'John', 150 | numberArray: 35, 151 | falsy: 98, 152 | stringArray: ['GHJ'], 153 | obj: 'obj', 154 | }); 155 | 156 | const bob = new UserModel({ name: 'Bob' }); 157 | 158 | return utils 159 | .deleteModelIndexes(UserModel) 160 | .then(() => { 161 | return UserModel.esCreateMapping(); 162 | }) 163 | .then(() => { 164 | return john.esIndex(); 165 | }) 166 | .then(() => { 167 | return bob.esIndex(); 168 | }) 169 | .then(() => { 170 | return UserModel.esRefresh(); 171 | }) 172 | .then(() => { 173 | const options = UserModel.esOptions(); 174 | const client = options.client; 175 | return client.search({ 176 | index: options.index, 177 | type: options.type, 178 | body: { 179 | query: { match_all: {} }, 180 | sort: [{ name: { order: 'desc' } }], 181 | }, 182 | }); 183 | }) 184 | .then(resp => { 185 | expect(resp.hits.total).to.eql(2); 186 | let hit = resp.hits.hits[0]; 187 | expect(hit._id).to.eql(john._id.toString()); 188 | expect(hit._source).to.eql({ 189 | name: 'John', 190 | numberArray: [1, 2, 3], 191 | falsy: 0, 192 | stringArray: ['az', 'er', 'ty'], 193 | obj: { 194 | a: 'azerty', 195 | b: 123, 196 | }, 197 | }); 198 | hit = resp.hits.hits[1]; 199 | expect(hit._id).to.eql(bob._id.toString()); 200 | expect(hit._source).to.eql({ 201 | name: 'Bob', 202 | numberArray: [1, 2, 3], 203 | falsy: 0, 204 | stringArray: ['az', 'er', 'ty'], 205 | obj: { 206 | a: 'azerty', 207 | b: 123, 208 | }, 209 | }); 210 | }); 211 | }); 212 | }); 213 | -------------------------------------------------------------------------------- /test/es5/hydratation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('hydratation', () => { 8 | utils.setup(); 9 | 10 | let UserModel; 11 | let BookModel; 12 | let CityModel; 13 | let john; 14 | let jane; 15 | let bob; 16 | let city1; 17 | let city2; 18 | let book1; 19 | let book2; 20 | let author1; 21 | let author2; 22 | 23 | beforeEach(() => { 24 | const CitySchema = new mongoose.Schema({ 25 | name: String, 26 | }); 27 | 28 | const BookSchema = new mongoose.Schema({ 29 | title: String, 30 | author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, 31 | }); 32 | 33 | const UserSchema = new mongoose.Schema({ 34 | name: { type: String, es_indexed: true }, 35 | age: { type: Number, es_indexed: true }, 36 | city: { 37 | type: mongoose.Schema.Types.ObjectId, 38 | ref: 'City', 39 | es_indexed: false, 40 | }, 41 | books: { 42 | type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Book' }], 43 | es_indexed: false, 44 | }, 45 | }); 46 | 47 | UserSchema.plugin(plugin); 48 | 49 | UserModel = mongoose.model('User', UserSchema); 50 | BookModel = mongoose.model('Book', BookSchema); 51 | CityModel = mongoose.model('City', CitySchema); 52 | 53 | city1 = new CityModel({ name: 'New York' }); 54 | city2 = new CityModel({ name: 'Los Angeles' }); 55 | 56 | author1 = new UserModel({ name: 'Rudyard Kipling' }); 57 | author2 = new UserModel({ name: 'George Orwell' }); 58 | 59 | book1 = new BookModel({ title: 'The Jungle Book', author: author1 }); 60 | book2 = new BookModel({ title: '1984', author: author2 }); 61 | 62 | john = new UserModel({ 63 | name: 'John', 64 | age: 35, 65 | city: city1, 66 | books: [book1, book2], 67 | }); 68 | jane = new UserModel({ 69 | name: 'Jane', 70 | age: 34, 71 | city: city1, 72 | books: [book1], 73 | }); 74 | bob = new UserModel({ name: 'Bob', age: 36, city: city2, books: [book2] }); 75 | 76 | return utils 77 | .deleteModelIndexes(UserModel) 78 | .then(() => { 79 | return Promise.all([ 80 | city1.save(), 81 | city2.save(), 82 | book1.save(), 83 | book2.save(), 84 | ]); 85 | }) 86 | .then(() => { 87 | return UserModel.esCreateMapping(); 88 | }) 89 | .then(() => { 90 | return utils.Promise.all( 91 | [john, jane, bob, author1, author2].map(user => { 92 | return new utils.Promise(resolve => { 93 | user.on('es-indexed', resolve); 94 | user.save(); 95 | }); 96 | }) 97 | ); 98 | }) 99 | .then(() => { 100 | return UserModel.esRefresh(); 101 | }); 102 | }); 103 | 104 | it('should hydrate', () => { 105 | return UserModel.esSearch( 106 | { 107 | query: { 108 | bool: { 109 | must: { match_all: {} }, 110 | filter: { range: { age: { gte: 35 } } }, 111 | }, 112 | }, 113 | sort: [{ age: { order: 'desc' } }], 114 | }, 115 | { hydrate: true } 116 | ).then(result => { 117 | let hit; 118 | expect(result.hits.total).to.eql(2); 119 | 120 | hit = result.hits.hits[0]; 121 | expect(hit._source).to.be.undefined; 122 | expect(hit.doc).to.be.an.instanceof(UserModel); 123 | expect(hit.doc._id.toString()).to.eql(bob._id.toString()); 124 | expect(hit.doc.name).to.eql(bob.name); 125 | expect(hit.doc.age).to.eql(bob.age); 126 | 127 | hit = result.hits.hits[1]; 128 | expect(hit._source).to.be.undefined; 129 | expect(hit.doc).to.be.an.instanceof(UserModel); 130 | expect(hit.doc._id.toString()).to.eql(john._id.toString()); 131 | expect(hit.doc.name).to.eql(john.name); 132 | expect(hit.doc.age).to.eql(john.age); 133 | }); 134 | }); 135 | 136 | it('should hydrate returning only models', () => { 137 | return UserModel.esSearch( 138 | { 139 | query: { 140 | bool: { 141 | must: { match_all: {} }, 142 | filter: { range: { age: { gte: 35 } } }, 143 | }, 144 | }, 145 | sort: [{ age: { order: 'desc' } }], 146 | }, 147 | { hydrate: { docsOnly: true } } 148 | ).then(users => { 149 | let user; 150 | expect(users.length).to.eql(2); 151 | 152 | user = users[0]; 153 | expect(user._id.toString()).to.eql(bob._id.toString()); 154 | expect(user.name).to.eql(bob.name); 155 | expect(user.age).to.eql(bob.age); 156 | 157 | user = users[1]; 158 | expect(user._id.toString()).to.eql(john._id.toString()); 159 | expect(user.name).to.eql(john.name); 160 | expect(user.age).to.eql(john.age); 161 | }); 162 | }); 163 | 164 | it('should return an empty array when hydrating only models on 0 hit', () => { 165 | return UserModel.esSearch( 166 | { 167 | query: { 168 | bool: { 169 | must: { match_all: {} }, 170 | filter: { range: { age: { gte: 100 } } }, 171 | }, 172 | }, 173 | sort: [{ age: { order: 'desc' } }], 174 | }, 175 | { hydrate: { docsOnly: true } } 176 | ).then(users => { 177 | expect(users).to.eql([]); 178 | }); 179 | }); 180 | 181 | it('should hydrate using projection', () => { 182 | return UserModel.esSearch('name:jane', { 183 | hydrate: { select: 'name' }, 184 | }).then(result => { 185 | expect(result.hits.total).to.eql(1); 186 | 187 | const hit = result.hits.hits[0]; 188 | expect(hit._source).to.be.undefined; 189 | expect(hit.doc).to.be.an.instanceof(UserModel); 190 | expect(hit.doc._id.toString()).to.eql(jane._id.toString()); 191 | expect(hit.doc.name).to.eql(jane.name); 192 | expect(hit.doc.age).to.be.undefined; 193 | }); 194 | }); 195 | 196 | it('should hydrate using options', () => { 197 | return UserModel.esSearch('name:jane', { 198 | hydrate: { options: { lean: true } }, 199 | }).then(result => { 200 | expect(result.hits.total).to.eql(1); 201 | 202 | const hit = result.hits.hits[0]; 203 | expect(hit._source).to.be.undefined; 204 | expect(hit.doc).not.to.be.an.instanceof(UserModel); 205 | expect(hit.doc._id.toString()).to.eql(jane._id.toString()); 206 | expect(hit.doc.name).to.eql(jane.name); 207 | expect(hit.doc.age).to.eql(jane.age); 208 | }); 209 | }); 210 | 211 | it('should hydrate when defined in plugin', () => { 212 | utils.deleteMongooseModels(); 213 | 214 | const UserSchema = new mongoose.Schema({ 215 | name: String, 216 | age: Number, 217 | }); 218 | 219 | UserSchema.plugin(plugin, { hydrate: true }); 220 | 221 | const UserModelHydrate = mongoose.model('User', UserSchema); 222 | 223 | return UserModelHydrate.esSearch('name:jane').then(result => { 224 | expect(result.hits.total).to.eql(1); 225 | 226 | const hit = result.hits.hits[0]; 227 | expect(hit._source).to.be.undefined; 228 | expect(hit.doc).to.be.an.instanceof(UserModelHydrate); 229 | expect(hit.doc._id.toString()).to.eql(jane._id.toString()); 230 | expect(hit.doc.name).to.eql(jane.name); 231 | expect(hit.doc.age).to.eql(jane.age); 232 | }); 233 | }); 234 | 235 | it('should hydrate when defined in plugin returning only models', () => { 236 | utils.deleteMongooseModels(); 237 | 238 | const UserSchema = new mongoose.Schema({ 239 | name: String, 240 | age: Number, 241 | }); 242 | 243 | UserSchema.plugin(plugin, { hydrate: { docsOnly: true } }); 244 | 245 | const UserModelHydrate = mongoose.model('User', UserSchema); 246 | 247 | return UserModelHydrate.esSearch({ 248 | query: { 249 | bool: { 250 | must: { match_all: {} }, 251 | filter: { range: { age: { gte: 35 } } }, 252 | }, 253 | }, 254 | sort: [{ age: { order: 'desc' } }], 255 | }).then(users => { 256 | let user; 257 | expect(users.length).to.eql(2); 258 | 259 | user = users[0]; 260 | expect(user._id.toString()).to.eql(bob._id.toString()); 261 | expect(user.name).to.eql(bob.name); 262 | expect(user.age).to.eql(bob.age); 263 | 264 | user = users[1]; 265 | expect(user._id.toString()).to.eql(john._id.toString()); 266 | expect(user.name).to.eql(john.name); 267 | expect(user.age).to.eql(john.age); 268 | }); 269 | }); 270 | 271 | it('should hydrate when defined in plugin using projection', () => { 272 | utils.deleteMongooseModels(); 273 | 274 | const UserSchema = new mongoose.Schema({ 275 | name: String, 276 | age: Number, 277 | }); 278 | 279 | UserSchema.plugin(plugin, { hydrate: { select: 'name' } }); 280 | 281 | const UserModelHydrate = mongoose.model('User', UserSchema); 282 | 283 | return UserModelHydrate.esSearch('name:jane').then(result => { 284 | expect(result.hits.total).to.eql(1); 285 | 286 | const hit = result.hits.hits[0]; 287 | expect(hit._source).to.be.undefined; 288 | expect(hit.doc).to.be.an.instanceof(UserModelHydrate); 289 | expect(hit.doc._id.toString()).to.eql(jane._id.toString()); 290 | expect(hit.doc.name).to.eql(jane.name); 291 | expect(hit.doc.age).to.be.undefined; 292 | }); 293 | }); 294 | 295 | it('should hydrate when defined in plugin using options', () => { 296 | utils.deleteMongooseModels(); 297 | 298 | const UserSchema = new mongoose.Schema({ 299 | name: String, 300 | age: Number, 301 | }); 302 | 303 | UserSchema.plugin(plugin, { hydrate: { options: { lean: true } } }); 304 | 305 | const UserModelHydrate = mongoose.model('User', UserSchema); 306 | 307 | return UserModelHydrate.esSearch('name:jane').then(result => { 308 | expect(result.hits.total).to.eql(1); 309 | 310 | const hit = result.hits.hits[0]; 311 | expect(hit._source).to.be.undefined; 312 | expect(hit.doc).not.to.be.an.instanceof(UserModelHydrate); 313 | expect(hit.doc._id.toString()).to.eql(jane._id.toString()); 314 | expect(hit.doc.name).to.eql(jane.name); 315 | expect(hit.doc.age).to.eql(jane.age); 316 | }); 317 | }); 318 | 319 | it('should hydrate overwriting defined in plugin using options', () => { 320 | utils.deleteMongooseModels(); 321 | 322 | const UserSchema = new mongoose.Schema({ 323 | name: String, 324 | age: Number, 325 | }); 326 | 327 | UserSchema.plugin(plugin, { hydrate: { options: { lean: true } } }); 328 | 329 | const UserModelHydrate = mongoose.model('User', UserSchema); 330 | 331 | return UserModelHydrate.esSearch( 332 | 'name:jane', 333 | { hydrate: { select: 'name' } } // not lean 334 | ).then(result => { 335 | expect(result.hits.total).to.eql(1); 336 | 337 | const hit = result.hits.hits[0]; 338 | expect(hit._source).to.be.undefined; 339 | expect(hit.doc).to.be.an.instanceof(UserModelHydrate); 340 | expect(hit.doc._id.toString()).to.eql(jane._id.toString()); 341 | expect(hit.doc.name).to.eql(jane.name); 342 | expect(hit.doc.age).to.be.undefined; 343 | }); 344 | }); 345 | 346 | it('should not hydrate overwriting defined in plugin', () => { 347 | utils.deleteMongooseModels(); 348 | 349 | const UserSchema = new mongoose.Schema({ 350 | name: String, 351 | age: Number, 352 | }); 353 | 354 | UserSchema.plugin(plugin, { hydrate: { options: { lean: true } } }); 355 | 356 | const UserModelHydrate = mongoose.model('User', UserSchema); 357 | 358 | return UserModelHydrate.esSearch( 359 | 'name:jane', 360 | { hydrate: false } // not lean 361 | ).then(result => { 362 | expect(result.hits.total).to.eql(1); 363 | 364 | const hit = result.hits.hits[0]; 365 | expect(hit.doc).to.be.undefined; 366 | expect(hit._source).not.to.be.undefined; 367 | expect(hit._source.name).to.eql(jane.name); 368 | expect(hit._source.age).to.eql(34); 369 | }); 370 | }); 371 | 372 | it('should hydrate with a simple populate', () => { 373 | return UserModel.esSearch( 374 | { 375 | query: { 376 | bool: { 377 | must: { match_all: {} }, 378 | filter: { range: { age: { gte: 35 } } }, 379 | }, 380 | }, 381 | sort: [{ age: { order: 'desc' } }], 382 | }, 383 | { 384 | hydrate: { 385 | populate: { 386 | path: 'city', 387 | }, 388 | }, 389 | } 390 | ).then(result => { 391 | let hit; 392 | expect(result.hits.total).to.eql(2); 393 | 394 | hit = result.hits.hits[0]; 395 | 396 | expect(hit._source).to.be.undefined; 397 | expect(hit.doc).to.be.an.instanceof(UserModel); 398 | expect(hit.doc._id.toString()).to.eql(bob._id.toString()); 399 | expect(hit.doc.name).to.eql(bob.name); 400 | expect(hit.doc.age).to.eql(bob.age); 401 | expect(hit.doc.city._id.toString()).to.eql(city2._id.toString()); 402 | expect(hit.doc.city.name).to.eql(city2.name); 403 | // book should not be populated 404 | expect(hit.doc.books.length).to.eql(1); 405 | expect(hit.doc.books[0].toJSON()).to.eql(book2._id.toString()); 406 | 407 | hit = result.hits.hits[1]; 408 | expect(hit._source).to.be.undefined; 409 | expect(hit.doc).to.be.an.instanceof(UserModel); 410 | expect(hit.doc._id.toString()).to.eql(john._id.toString()); 411 | expect(hit.doc.name).to.eql(john.name); 412 | expect(hit.doc.age).to.eql(john.age); 413 | expect(hit.doc.city._id.toString()).to.eql(city1._id.toString()); 414 | expect(hit.doc.city.name).to.eql(city1.name); 415 | // book should not be populated 416 | expect(hit.doc.books.length).to.eql(2); 417 | expect(hit.doc.books[0].toJSON()).to.eql(book1._id.toString()); 418 | expect(hit.doc.books[1].toJSON()).to.eql(book2._id.toString()); 419 | }); 420 | }); 421 | 422 | it('should hydrate with an array of population including a complex one', () => { 423 | return UserModel.esSearch( 424 | { 425 | query: { 426 | bool: { 427 | must: { match_all: {} }, 428 | filter: { range: { age: { gte: 35 } } }, 429 | }, 430 | }, 431 | sort: [{ age: { order: 'desc' } }], 432 | }, 433 | { 434 | hydrate: { 435 | populate: [ 436 | { 437 | path: 'city', 438 | }, 439 | { 440 | path: 'books', 441 | populate: { 442 | path: 'author', 443 | }, 444 | }, 445 | ], 446 | }, 447 | } 448 | ).then(result => { 449 | let hit; 450 | expect(result.hits.total).to.eql(2); 451 | 452 | hit = result.hits.hits[0]; 453 | 454 | expect(hit._source).to.be.undefined; 455 | expect(hit.doc).to.be.an.instanceof(UserModel); 456 | expect(hit.doc._id.toString()).to.eql(bob._id.toString()); 457 | expect(hit.doc.name).to.eql(bob.name); 458 | expect(hit.doc.age).to.eql(bob.age); 459 | 460 | expect(hit.doc.city._id.toString()).to.eql(city2._id.toString()); 461 | expect(hit.doc.city.name).to.eql(city2.name); 462 | 463 | expect(hit.doc.books.length).to.eql(1); 464 | expect(hit.doc.books[0].toJSON()).to.eql(book2.toJSON()); 465 | expect(hit.doc.books[0].author.toJSON()).to.eql(author2.toJSON()); 466 | 467 | hit = result.hits.hits[1]; 468 | expect(hit._source).to.be.undefined; 469 | expect(hit.doc).to.be.an.instanceof(UserModel); 470 | expect(hit.doc._id.toString()).to.eql(john._id.toString()); 471 | expect(hit.doc.name).to.eql(john.name); 472 | expect(hit.doc.age).to.eql(john.age); 473 | 474 | expect(hit.doc.city._id.toString()).to.eql(city1._id.toString()); 475 | expect(hit.doc.city.name).to.eql(city1.name); 476 | 477 | expect(hit.doc.books.length).to.eql(2); 478 | expect(hit.doc.books[0].toJSON()).to.eql(book1.toJSON()); 479 | expect(hit.doc.books[0].author.toJSON()).to.eql(author1.toJSON()); 480 | expect(hit.doc.books[1].toJSON()).to.eql(book2.toJSON()); 481 | expect(hit.doc.books[1].author.toJSON()).to.eql(author2.toJSON()); 482 | }); 483 | }); 484 | }); 485 | -------------------------------------------------------------------------------- /test/es5/idsOnly.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('idsOnly', () => { 8 | utils.setup(); 9 | let UserModel; 10 | let john; 11 | let jane; 12 | let bob; 13 | 14 | beforeEach(() => { 15 | const UserSchema = new mongoose.Schema({ 16 | name: String, 17 | age: Number, 18 | }); 19 | 20 | UserSchema.plugin(plugin); 21 | 22 | UserModel = mongoose.model('User', UserSchema); 23 | 24 | john = new UserModel({ name: 'John', age: 35 }); 25 | jane = new UserModel({ name: 'Jane', age: 34 }); 26 | bob = new UserModel({ name: 'Bob', age: 36 }); 27 | 28 | return utils 29 | .deleteModelIndexes(UserModel) 30 | .then(() => { 31 | return UserModel.esCreateMapping(); 32 | }) 33 | .then(() => { 34 | return utils.Promise.all( 35 | [john, jane, bob].map(user => { 36 | return new utils.Promise(resolve => { 37 | user.on('es-indexed', resolve); 38 | user.save(); 39 | }); 40 | }) 41 | ); 42 | }) 43 | .then(() => { 44 | return UserModel.esRefresh(); 45 | }); 46 | }); 47 | 48 | it('should return ids', () => { 49 | return UserModel.esSearch( 50 | { 51 | query: { 52 | bool: { 53 | must: { match_all: {} }, 54 | filter: { range: { age: { gte: 35 } } }, 55 | }, 56 | }, 57 | sort: [{ age: { order: 'desc' } }], 58 | }, 59 | { idsOnly: true } 60 | ).then(ids => { 61 | expect(ids.length).to.eql(2); 62 | const idstrings = ids.map(id => { 63 | expect(id).to.be.an.instanceof(mongoose.Types.ObjectId); 64 | return id.toString(); 65 | }); 66 | expect(idstrings).to.eql([bob._id.toString(), john._id.toString()]); 67 | }); 68 | }); 69 | 70 | it('should an empty array', () => { 71 | return UserModel.esSearch( 72 | { 73 | query: { 74 | bool: { 75 | must: { match_all: {} }, 76 | filter: { range: { age: { gte: 100 } } }, 77 | }, 78 | }, 79 | sort: [{ age: { order: 'desc' } }], 80 | }, 81 | { idsOnly: true } 82 | ).then(ids => { 83 | expect(ids).to.eql([]); 84 | }); 85 | }); 86 | 87 | it('should return ids when defined in plugin', () => { 88 | utils.deleteMongooseModels(); 89 | 90 | const UserSchema = new mongoose.Schema({ 91 | name: String, 92 | age: Number, 93 | }); 94 | 95 | UserSchema.plugin(plugin, { idsOnly: true }); 96 | 97 | const UserModelIdsOnly = mongoose.model('User', UserSchema); 98 | 99 | return UserModelIdsOnly.esSearch({ 100 | query: { 101 | bool: { 102 | must: { match_all: {} }, 103 | filter: { range: { age: { gte: 35 } } }, 104 | }, 105 | }, 106 | sort: [{ age: { order: 'desc' } }], 107 | }).then(ids => { 108 | expect(ids.length).to.eql(2); 109 | const idstrings = ids.map(id => { 110 | expect(id).to.be.an.instanceof(mongoose.Types.ObjectId); 111 | return id.toString(); 112 | }); 113 | expect(idstrings).to.eql([bob._id.toString(), john._id.toString()]); 114 | }); 115 | }); 116 | 117 | it('should overwrite defined in plugin value', () => { 118 | utils.deleteMongooseModels(); 119 | 120 | const UserSchema = new mongoose.Schema({ 121 | name: String, 122 | age: Number, 123 | }); 124 | 125 | UserSchema.plugin(plugin, { idsOnly: true }); 126 | 127 | const UserModelIdsOnly = mongoose.model('User', UserSchema); 128 | 129 | return UserModelIdsOnly.esSearch( 130 | { 131 | query: { 132 | bool: { 133 | must: { match_all: {} }, 134 | filter: { range: { age: { gte: 35 } } }, 135 | }, 136 | }, 137 | sort: [{ age: { order: 'desc' } }], 138 | }, 139 | { idsOnly: false } 140 | ).then(result => { 141 | expect(result.hits.total).to.eql(2); 142 | let hit = result.hits.hits[0]; 143 | expect(hit._id).to.eql(bob._id.toString()); 144 | expect(hit._source).to.eql({ name: 'Bob', age: 36 }); 145 | 146 | hit = result.hits.hits[1]; 147 | expect(hit._id).to.eql(john._id.toString()); 148 | expect(hit._source).to.eql({ name: 'John', age: 35 }); 149 | }); 150 | }); 151 | }); 152 | -------------------------------------------------------------------------------- /test/es5/promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | 6 | const plugin = require('../../').v5; 7 | 8 | describe('promise', () => { 9 | utils.setup(); 10 | 11 | it('should return promise', () => { 12 | const UserSchema = new mongoose.Schema({ 13 | name: String, 14 | }); 15 | 16 | UserSchema.plugin(plugin); 17 | 18 | const UserModel = mongoose.model('User', UserSchema); 19 | const promise = UserModel.esCreateMapping(); 20 | expect(promise).to.be.an.instanceof(Promise); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/es5/withDiscriminators.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('with discriminators', () => { 8 | utils.setup(); 9 | 10 | it('check types and mappings', () => { 11 | const BaseSchema = new mongoose.Schema({ 12 | name: String, 13 | }); 14 | 15 | const UserSchema = new mongoose.Schema({ 16 | age: Number, 17 | }); 18 | 19 | const AdminSchema = new mongoose.Schema({ 20 | access: Boolean, 21 | }); 22 | 23 | BaseSchema.plugin(plugin, { 24 | index: 'user', 25 | type: kind => { 26 | if (kind === 'User') return 'userType'; 27 | if (kind === 'Admin') return 'adminType'; 28 | return 'otherType'; 29 | }, 30 | }); 31 | 32 | const BaseModel = mongoose.model('Base', BaseSchema); 33 | const UserModel = BaseModel.discriminator('User', UserSchema); 34 | const AdminModel = BaseModel.discriminator('Admin', AdminSchema); 35 | 36 | // check types and mappings on models 37 | const userOpts = UserModel.esOptions(); 38 | const adminOpts = AdminModel.esOptions(); 39 | expect(userOpts.type).to.equal('userType'); 40 | expect(adminOpts.type).to.equal('adminType'); 41 | expect(userOpts.mapping).to.deep.equal({ 42 | properties: { 43 | __t: { 44 | type: 'text', 45 | }, 46 | age: { 47 | type: 'double', 48 | }, 49 | name: { 50 | type: 'text', 51 | }, 52 | }, 53 | }); 54 | expect(adminOpts.mapping).to.deep.equal({ 55 | properties: { 56 | __t: { 57 | type: 'text', 58 | }, 59 | access: { 60 | type: 'boolean', 61 | }, 62 | name: { 63 | type: 'text', 64 | }, 65 | }, 66 | }); 67 | 68 | // check types and mappings on docs 69 | UserModel.create({ 70 | name: 'John', 71 | age: 34, 72 | }).then(doc => { 73 | const opts = doc.esOptions(); 74 | expect(opts.type).to.equal('userType'); 75 | expect(opts.mapping).to.deep.equal({ 76 | properties: { 77 | __t: { 78 | type: 'text', 79 | }, 80 | age: { 81 | type: 'double', 82 | }, 83 | name: { 84 | type: 'text', 85 | }, 86 | }, 87 | }); 88 | }); 89 | 90 | AdminModel.create({ 91 | name: 'Steve', 92 | access: true, 93 | }).then(doc => { 94 | const opts = doc.esOptions(); 95 | expect(opts.type).to.equal('adminType'); 96 | expect(opts.mapping).to.deep.equal({ 97 | properties: { 98 | __t: { 99 | type: 'text', 100 | }, 101 | access: { 102 | type: 'boolean', 103 | }, 104 | name: { 105 | type: 'text', 106 | }, 107 | }, 108 | }); 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/es6/countOnly.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('countOnly', () => { 8 | utils.setup(); 9 | let UserModel; 10 | 11 | beforeEach(() => { 12 | const UserSchema = new mongoose.Schema({ 13 | name: String, 14 | age: Number, 15 | }); 16 | 17 | UserSchema.plugin(plugin); 18 | UserModel = mongoose.model('User', UserSchema); 19 | 20 | const john = new UserModel({ name: 'John', age: 35 }); 21 | const jane = new UserModel({ name: 'Jane', age: 34 }); 22 | const bob = new UserModel({ name: 'Bob', age: 36 }); 23 | 24 | return utils 25 | .deleteModelIndexes(UserModel) 26 | .then(() => UserModel.esCreateMapping()) 27 | .then(() => 28 | utils.Promise.all( 29 | [john, jane, bob].map( 30 | user => 31 | new utils.Promise(resolve => { 32 | user.on('es-indexed', resolve); 33 | user.save(); 34 | }) 35 | ) 36 | ) 37 | ) 38 | .then(() => UserModel.esRefresh()); 39 | }); 40 | 41 | it('should return count', () => { 42 | return UserModel.esCount( 43 | { 44 | bool: { 45 | must: { match_all: {} }, 46 | filter: { range: { age: { gte: 35 } } }, 47 | }, 48 | }, 49 | { countOnly: true } 50 | ).then(count => { 51 | expect(count).to.eql(2); 52 | }); 53 | }); 54 | 55 | it('should return 0', () => { 56 | return UserModel.esCount( 57 | { 58 | bool: { 59 | must: { match_all: {} }, 60 | filter: { range: { age: { gte: 100 } } }, 61 | }, 62 | }, 63 | { countOnly: true } 64 | ).then(count => { 65 | expect(count).to.eql(0); 66 | }); 67 | }); 68 | 69 | it('should return count when defined in plugin', () => { 70 | utils.deleteMongooseModels(); 71 | 72 | const UserSchema = new mongoose.Schema({ 73 | name: String, 74 | age: Number, 75 | }); 76 | 77 | UserSchema.plugin(plugin, { countOnly: true }); 78 | 79 | const UserModelCountOnly = mongoose.model('User', UserSchema); 80 | 81 | return UserModelCountOnly.esCount({ 82 | bool: { 83 | must: { match_all: {} }, 84 | filter: { range: { age: { gte: 35 } } }, 85 | }, 86 | }).then(count => { 87 | expect(count).to.eql(2); 88 | }); 89 | }); 90 | 91 | it('should overwrite defined in plugin value', () => { 92 | utils.deleteMongooseModels(); 93 | 94 | const UserSchema = new mongoose.Schema({ 95 | name: String, 96 | age: Number, 97 | }); 98 | 99 | UserSchema.plugin(plugin, { countOnly: true }); 100 | 101 | const UserModelCountOnly = mongoose.model('User', UserSchema); 102 | 103 | return UserModelCountOnly.esCount( 104 | { 105 | bool: { 106 | must: { match_all: {} }, 107 | filter: { range: { age: { gte: 35 } } }, 108 | }, 109 | }, 110 | { countOnly: false } 111 | ).then(result => { 112 | expect(result.count).to.eql(2); 113 | }); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /test/es6/customIds.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const shortid = require('shortid'); 5 | const utils = require('../utils'); 6 | const plugin = require('../../').v5; 7 | 8 | describe('custom ids', () => { 9 | utils.setup(); 10 | let UserModel; 11 | let john; 12 | let jane; 13 | let bob; 14 | 15 | beforeEach(() => { 16 | const UserSchema = new mongoose.Schema({ 17 | _id: { 18 | type: String, 19 | default: shortid.generate, 20 | }, 21 | name: String, 22 | age: Number, 23 | }); 24 | 25 | UserSchema.plugin(plugin); 26 | 27 | UserModel = mongoose.model('User', UserSchema); 28 | 29 | john = new UserModel({ name: 'John', age: 35 }); 30 | jane = new UserModel({ name: 'Jane', age: 34 }); 31 | bob = new UserModel({ name: 'Bob', age: 36 }); 32 | 33 | return utils 34 | .deleteModelIndexes(UserModel) 35 | .then(() => { 36 | return UserModel.esCreateMapping(); 37 | }) 38 | .then(() => { 39 | return utils.Promise.all( 40 | [john, jane, bob].map(user => { 41 | return new utils.Promise(resolve => { 42 | user.on('es-indexed', resolve); 43 | user.save(); 44 | }); 45 | }) 46 | ); 47 | }) 48 | .then(() => { 49 | return UserModel.esRefresh(); 50 | }); 51 | }); 52 | 53 | it('should return ids', () => { 54 | return UserModel.esSearch( 55 | { 56 | query: { 57 | bool: { 58 | must: { match_all: {} }, 59 | filter: { range: { age: { gte: 35 } } }, 60 | }, 61 | }, 62 | sort: [{ age: { order: 'desc' } }], 63 | }, 64 | { idsOnly: true } 65 | ).then(ids => { 66 | expect(ids.length).to.eql(2); 67 | expect(ids).to.eql([bob._id, john._id]); 68 | }); 69 | }); 70 | 71 | it('should return people', () => { 72 | return UserModel.esSearch({ 73 | query: { 74 | bool: { 75 | must: { match_all: {} }, 76 | filter: { range: { age: { gte: 35 } } }, 77 | }, 78 | }, 79 | sort: [{ age: { order: 'desc' } }], 80 | }).then(result => { 81 | expect(result.hits.total).to.eql(2); 82 | expect(result.hits.hits[0]._source).to.eql({ name: 'Bob', age: 36 }); 83 | expect(result.hits.hits[1]._source).to.eql({ name: 'John', age: 35 }); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/es6/esCount.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('esCount', () => { 8 | utils.setup(); 9 | let UserModel; 10 | 11 | beforeEach(() => { 12 | const UserSchema = new mongoose.Schema({ 13 | name: String, 14 | age: Number, 15 | }); 16 | 17 | UserSchema.plugin(plugin); 18 | UserModel = mongoose.model('User', UserSchema); 19 | 20 | const john = new UserModel({ name: 'John', age: 35 }); 21 | const jane = new UserModel({ name: 'Jane', age: 34 }); 22 | const bob = new UserModel({ name: 'Bob', age: 36 }); 23 | 24 | return utils 25 | .deleteModelIndexes(UserModel) 26 | .then(() => { 27 | return UserModel.esCreateMapping(); 28 | }) 29 | .then(() => { 30 | const options = UserModel.esOptions(); 31 | const client = options.client; 32 | return client.bulk({ 33 | refresh: true, 34 | body: [ 35 | { 36 | index: { 37 | _index: options.index, 38 | _type: options.type, 39 | _id: john._id.toString(), 40 | }, 41 | }, 42 | { name: 'John', age: 35 }, 43 | { 44 | index: { 45 | _index: options.index, 46 | _type: options.type, 47 | _id: jane._id.toString(), 48 | }, 49 | }, 50 | { name: 'Jane', age: 34 }, 51 | { 52 | index: { 53 | _index: options.index, 54 | _type: options.type, 55 | _id: bob._id.toString(), 56 | }, 57 | }, 58 | { name: 'Bob', age: 36 }, 59 | ], 60 | }); 61 | }); 62 | }); 63 | 64 | it('should handle a lucene query', () => { 65 | return UserModel.esCount('name:jane').then(result => { 66 | expect(result.count).to.eql(1); 67 | }); 68 | }); 69 | 70 | it('should accept callback', done => { 71 | const returned = UserModel.esCount('name:jane', (err, result) => { 72 | if (err) { 73 | done(err); 74 | return; 75 | } 76 | expect(result.count).to.eql(1); 77 | expect(returned).to.be.undefined; 78 | done(); 79 | }); 80 | }); 81 | 82 | it('should handle a full query', () => { 83 | return UserModel.esCount({ 84 | bool: { 85 | must: { match_all: {} }, 86 | filter: { range: { age: { lt: 35 } } }, 87 | }, 88 | }).then(result => { 89 | expect(result.count).to.eql(1); 90 | }); 91 | }); 92 | 93 | it('should handle a short query', () => { 94 | return UserModel.esCount({ match: { age: 34 } }).then(result => { 95 | expect(result.count).to.eql(1); 96 | }); 97 | }); 98 | 99 | it('should handle 0 hit', () => { 100 | return UserModel.esCount({ match: { age: 100 } }).then(result => { 101 | expect(result.count).to.eql(0); 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /test/es6/esIndex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('esIndex', () => { 8 | utils.setup(); 9 | 10 | it('should be indexed', () => { 11 | const UserSchema = new mongoose.Schema({ 12 | name: String, 13 | age: Number, 14 | pos: { 15 | type: [Number], 16 | index: '2dsphere', 17 | es_type: 'geo_point', 18 | es_boost: 1.5, 19 | }, 20 | }); 21 | 22 | UserSchema.plugin(plugin); 23 | const UserModel = mongoose.model('User', UserSchema); 24 | 25 | const john = new UserModel({ name: 'John', age: 35, pos: [5.7333, 43.5] }); 26 | 27 | return utils 28 | .deleteModelIndexes(UserModel) 29 | .then(() => { 30 | return UserModel.esCreateMapping(); 31 | }) 32 | .then(() => { 33 | return john.esIndex(); 34 | }) 35 | .then(() => { 36 | return UserModel.esRefresh(); 37 | }) 38 | .then(() => { 39 | const options = UserModel.esOptions(); 40 | const client = options.client; 41 | return client.search({ 42 | index: options.index, 43 | type: options.type, 44 | body: { query: { match_all: {} } }, 45 | }); 46 | }) 47 | .then(resp => { 48 | expect(resp.hits.total).to.eql(1); 49 | const hit = resp.hits.hits[0]; 50 | expect(hit._id).to.eql(john._id.toString()); 51 | expect(hit._source).to.eql({ 52 | name: 'John', 53 | age: 35, 54 | pos: [5.7333, 43.5], 55 | }); 56 | }); 57 | }); 58 | 59 | it('should index ObjectId from object populated or not', () => { 60 | const CountrySchema = new mongoose.Schema({ 61 | name: String, 62 | }); 63 | 64 | const CitySchema = new mongoose.Schema({ 65 | name: String, 66 | }); 67 | 68 | const UserSchema = new mongoose.Schema({ 69 | name: String, 70 | city: { type: mongoose.Schema.Types.ObjectId, ref: 'City' }, 71 | country: { type: mongoose.Schema.Types.ObjectId, ref: 'Country' }, 72 | }); 73 | 74 | UserSchema.plugin(plugin); 75 | 76 | const UserModel = mongoose.model('User', UserSchema); 77 | const CityModel = mongoose.model('City', CitySchema); 78 | const CountryModel = mongoose.model('Country', CountrySchema); 79 | 80 | const country = new CountryModel({ name: 'France' }); 81 | const city = new CityModel({ name: 'Paris' }); 82 | const john = new UserModel({ 83 | name: 'John', 84 | city, 85 | country: country._id, 86 | }); 87 | 88 | return utils 89 | .deleteModelIndexes(UserModel) 90 | .then(() => { 91 | return UserModel.esCreateMapping(); 92 | }) 93 | .then(() => { 94 | return john.esIndex(); 95 | }) 96 | .then(() => { 97 | return UserModel.esRefresh(); 98 | }) 99 | .then(() => { 100 | const options = UserModel.esOptions(); 101 | const client = options.client; 102 | return client.search({ 103 | index: options.index, 104 | type: options.type, 105 | body: { query: { match_all: {} } }, 106 | }); 107 | }) 108 | .then(resp => { 109 | expect(resp.hits.total).to.eql(1); 110 | const hit = resp.hits.hits[0]; 111 | expect(hit._id).to.eql(john._id.toString()); 112 | expect(hit._source).to.eql({ 113 | name: 'John', 114 | city: city.id, 115 | country: country.id, 116 | }); 117 | }); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /test/es6/esRefresh.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('esRefresh', () => { 8 | utils.setup(); 9 | 10 | it('should handle callback', done => { 11 | const UserSchema = new mongoose.Schema({ 12 | name: String, 13 | }); 14 | 15 | UserSchema.plugin(plugin); 16 | const UserModel = mongoose.model('User', UserSchema); 17 | 18 | let start; 19 | 20 | utils 21 | .deleteModelIndexes(UserModel) 22 | .then(() => { 23 | return UserModel.esCreateMapping(); 24 | }) 25 | .then(() => { 26 | start = Date.now(); 27 | UserModel.esRefresh(err => { 28 | if (err) { 29 | done(err); 30 | return; 31 | } 32 | expect(Date.now() - start).to.be.lt(500); 33 | done(); 34 | }); 35 | }) 36 | .catch(err => { 37 | done(err); 38 | }); 39 | }); 40 | 41 | it('should handle callback and options', done => { 42 | const UserSchema = new mongoose.Schema({ 43 | name: String, 44 | }); 45 | 46 | UserSchema.plugin(plugin); 47 | 48 | const UserModel = mongoose.model('User', UserSchema); 49 | 50 | let start; 51 | 52 | utils 53 | .deleteModelIndexes(UserModel) 54 | .then(() => { 55 | return UserModel.esCreateMapping(); 56 | }) 57 | .then(() => { 58 | start = Date.now(); 59 | UserModel.esRefresh({ refreshDelay: 1000 }, err => { 60 | if (err) { 61 | done(err); 62 | return; 63 | } 64 | expect(Date.now() - start).to.be.gte(1000); 65 | done(); 66 | }); 67 | }) 68 | .catch(err => { 69 | done(err); 70 | }); 71 | }); 72 | 73 | it('should not be delayed', () => { 74 | const UserSchema = new mongoose.Schema({ 75 | name: String, 76 | }); 77 | 78 | UserSchema.plugin(plugin); 79 | 80 | const UserModel = mongoose.model('User', UserSchema); 81 | 82 | let start; 83 | 84 | return utils 85 | .deleteModelIndexes(UserModel) 86 | .then(() => { 87 | return UserModel.esCreateMapping(); 88 | }) 89 | .then(() => { 90 | start = Date.now(); 91 | return UserModel.esRefresh(); 92 | }) 93 | .then(() => { 94 | expect(Date.now() - start).to.be.lt(500); 95 | }); 96 | }); 97 | 98 | it('should be delayed', () => { 99 | const UserSchema = new mongoose.Schema({ 100 | name: String, 101 | }); 102 | 103 | UserSchema.plugin(plugin); 104 | 105 | const UserModel = mongoose.model('User', UserSchema); 106 | 107 | let start; 108 | 109 | return utils 110 | .deleteModelIndexes(UserModel) 111 | .then(() => { 112 | return UserModel.esCreateMapping(); 113 | }) 114 | .then(() => { 115 | start = Date.now(); 116 | return UserModel.esRefresh({ refreshDelay: 1000 }); 117 | }) 118 | .then(() => { 119 | expect(Date.now() - start).to.be.gte(1000); 120 | }); 121 | }); 122 | 123 | it('should be delayed when defined in plugin', () => { 124 | const UserSchema = new mongoose.Schema({ 125 | name: String, 126 | }); 127 | 128 | UserSchema.plugin(plugin, { refreshDelay: 1000 }); 129 | 130 | const UserModel = mongoose.model('User', UserSchema); 131 | 132 | let start; 133 | 134 | return utils 135 | .deleteModelIndexes(UserModel) 136 | .then(() => { 137 | return UserModel.esCreateMapping(); 138 | }) 139 | .then(() => { 140 | start = Date.now(); 141 | return UserModel.esRefresh(); 142 | }) 143 | .then(() => { 144 | expect(Date.now() - start).to.be.gte(1000); 145 | }); 146 | }); 147 | 148 | it('should overwrite defined in plugin value', () => { 149 | const UserSchema = new mongoose.Schema({ 150 | name: String, 151 | }); 152 | 153 | UserSchema.plugin(plugin, { refreshDelay: 1000 }); 154 | 155 | const UserModel = mongoose.model('User', UserSchema); 156 | 157 | let start; 158 | 159 | return utils 160 | .deleteModelIndexes(UserModel) 161 | .then(() => { 162 | return UserModel.esCreateMapping(); 163 | }) 164 | .then(() => { 165 | start = Date.now(); 166 | return UserModel.esRefresh({ refreshDelay: false }); 167 | }) 168 | .then(() => { 169 | expect(Date.now() - start).to.be.lt(500); 170 | }); 171 | }); 172 | }); 173 | -------------------------------------------------------------------------------- /test/es6/esRemove.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('esRemove', () => { 8 | utils.setup(); 9 | 10 | it('should be removed', () => { 11 | const UserSchema = new mongoose.Schema({ 12 | name: String, 13 | age: Number, 14 | }); 15 | 16 | UserSchema.plugin(plugin); 17 | 18 | const UserModel = mongoose.model('User', UserSchema); 19 | 20 | const john = new UserModel({ name: 'John', age: 35 }); 21 | const jane = new UserModel({ name: 'Jane', age: 34 }); 22 | const bob = new UserModel({ name: 'Bob', age: 36 }); 23 | 24 | return utils 25 | .deleteModelIndexes(UserModel) 26 | .then(() => { 27 | return UserModel.esCreateMapping(); 28 | }) 29 | .then(() => { 30 | const options = UserModel.esOptions(); 31 | const client = options.client; 32 | return client.bulk({ 33 | refresh: true, 34 | body: [ 35 | { 36 | index: { 37 | _index: options.index, 38 | _type: options.type, 39 | _id: john._id.toString(), 40 | }, 41 | }, 42 | { name: 'John', age: 35 }, 43 | { 44 | index: { 45 | _index: options.index, 46 | _type: options.type, 47 | _id: jane._id.toString(), 48 | }, 49 | }, 50 | { name: 'Jane', age: 34 }, 51 | { 52 | index: { 53 | _index: options.index, 54 | _type: options.type, 55 | _id: bob._id.toString(), 56 | }, 57 | }, 58 | { name: 'Bob', age: 36 }, 59 | ], 60 | }); 61 | }) 62 | .then(() => { 63 | return jane.esRemove(); 64 | }) 65 | .then(() => { 66 | return UserModel.esRefresh(); 67 | }) 68 | .then(() => { 69 | const options = UserModel.esOptions(); 70 | const client = options.client; 71 | return client.search({ 72 | index: options.index, 73 | type: options.type, 74 | body: { query: { match_all: {} } }, 75 | }); 76 | }) 77 | .then(resp => { 78 | const ids = resp.hits.hits.map(hit => { 79 | return hit._id; 80 | }); 81 | ids.sort(); 82 | 83 | const expectedIds = [john, bob].map(user => { 84 | return user._id.toString(); 85 | }); 86 | 87 | expect(ids).to.eql(expectedIds); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/es6/esSearch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('esSearch', () => { 8 | utils.setup(); 9 | let UserModel; 10 | let john; 11 | let jane; 12 | let bob; 13 | 14 | beforeEach(() => { 15 | const UserSchema = new mongoose.Schema({ 16 | name: String, 17 | age: Number, 18 | }); 19 | 20 | UserSchema.plugin(plugin); 21 | UserModel = mongoose.model('User', UserSchema); 22 | 23 | john = new UserModel({ name: 'John', age: 35 }); 24 | jane = new UserModel({ name: 'Jane', age: 34 }); 25 | bob = new UserModel({ name: 'Bob', age: 36 }); 26 | 27 | return utils 28 | .deleteModelIndexes(UserModel) 29 | .then(() => { 30 | return UserModel.esCreateMapping(); 31 | }) 32 | .then(() => { 33 | const options = UserModel.esOptions(); 34 | const client = options.client; 35 | return client.bulk({ 36 | refresh: true, 37 | body: [ 38 | { 39 | index: { 40 | _index: options.index, 41 | _type: options.type, 42 | _id: john._id.toString(), 43 | }, 44 | }, 45 | { name: 'John', age: 35 }, 46 | { 47 | index: { 48 | _index: options.index, 49 | _type: options.type, 50 | _id: jane._id.toString(), 51 | }, 52 | }, 53 | { name: 'Jane', age: 34 }, 54 | { 55 | index: { 56 | _index: options.index, 57 | _type: options.type, 58 | _id: bob._id.toString(), 59 | }, 60 | }, 61 | { name: 'Bob', age: 36 }, 62 | ], 63 | }); 64 | }); 65 | }); 66 | 67 | it('should handle a lucene query', () => { 68 | return UserModel.esSearch('name:jane').then(result => { 69 | expect(result.hits.total).to.eql(1); 70 | const hit = result.hits.hits[0]; 71 | expect(hit._id).to.eql(jane._id.toString()); 72 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 73 | }); 74 | }); 75 | 76 | it('should accept callback', done => { 77 | const returned = UserModel.esSearch('name:jane', {}, (err, result) => { 78 | if (err) { 79 | done(err); 80 | return; 81 | } 82 | expect(result.hits.total).to.eql(1); 83 | const hit = result.hits.hits[0]; 84 | expect(hit._id).to.eql(jane._id.toString()); 85 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 86 | expect(returned).to.be.undefined; 87 | done(); 88 | }); 89 | }); 90 | 91 | it('should accept callback without options', done => { 92 | const returned = UserModel.esSearch('name:jane', (err, result) => { 93 | if (err) { 94 | done(err); 95 | return; 96 | } 97 | expect(result.hits.total).to.eql(1); 98 | const hit = result.hits.hits[0]; 99 | expect(hit._id).to.eql(jane._id.toString()); 100 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 101 | expect(returned).to.be.undefined; 102 | done(); 103 | }); 104 | }); 105 | 106 | it('should handle a full query', () => { 107 | return UserModel.esSearch({ 108 | bool: { 109 | must: { match_all: {} }, 110 | filter: { range: { age: { lt: 35 } } }, 111 | }, 112 | }).then(result => { 113 | expect(result.hits.total).to.eql(1); 114 | const hit = result.hits.hits[0]; 115 | expect(hit._id).to.eql(jane._id.toString()); 116 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 117 | }); 118 | }); 119 | 120 | it('should handle a short query', () => { 121 | return UserModel.esSearch({ match: { age: 34 } }).then(result => { 122 | expect(result.hits.total).to.eql(1); 123 | const hit = result.hits.hits[0]; 124 | expect(hit._id).to.eql(jane._id.toString()); 125 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 126 | }); 127 | }); 128 | 129 | it('should handle 0 hit', () => { 130 | return UserModel.esSearch({ match: { age: 100 } }).then(result => { 131 | expect(result.hits.total).to.eql(0); 132 | }); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /test/es6/es_extend.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('es_extend', () => { 8 | utils.setup(); 9 | 10 | it('should add some fields', () => { 11 | let john; 12 | 13 | const UserSchema = new mongoose.Schema( 14 | { 15 | name: String, 16 | }, 17 | { 18 | es_extend: { 19 | num: { 20 | es_type: 'integer', 21 | es_value: 123, 22 | }, 23 | length: { 24 | es_type: 'integer', 25 | es_value(document) { 26 | expect(document === john).to.be.true; 27 | return document.name.length; 28 | }, 29 | }, 30 | }, 31 | } 32 | ); 33 | 34 | UserSchema.plugin(plugin); 35 | 36 | const UserModel = mongoose.model('User', UserSchema); 37 | 38 | john = new UserModel({ 39 | name: 'John', 40 | }); 41 | 42 | return utils 43 | .deleteModelIndexes(UserModel) 44 | .then(() => { 45 | return UserModel.esCreateMapping(); 46 | }) 47 | .then(() => { 48 | const options = UserModel.esOptions(); 49 | return options.client.indices.getMapping({ 50 | index: options.index, 51 | type: options.type, 52 | }); 53 | }) 54 | .then(mapping => { 55 | const properties = mapping.users.mappings.user.properties; 56 | expect(properties).to.have.all.keys('name', 'num', 'length'); 57 | expect(properties.name.type).to.be.equal('text'); 58 | expect(properties.num.type).to.be.equal('integer'); 59 | expect(properties.length.type).to.be.equal('integer'); 60 | }) 61 | .then(() => { 62 | return new utils.Promise(resolve => { 63 | john.on('es-indexed', () => { 64 | resolve(); 65 | }); 66 | john.save(); 67 | }); 68 | }) 69 | .then(() => { 70 | return UserModel.esRefresh(); 71 | }) 72 | .then(() => { 73 | return UserModel.esSearch({ 74 | query: { match_all: {} }, 75 | }); 76 | }) 77 | .then(result => { 78 | expect(result.hits.total).to.eql(1); 79 | expect(result.hits.hits[0]._source).to.eql({ 80 | name: 'John', 81 | num: 123, 82 | length: 4, 83 | }); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/es6/es_value.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('es_value', () => { 8 | utils.setup(); 9 | 10 | it('should handle es_value as a function', () => { 11 | let john; 12 | 13 | const Sub2Schema = new mongoose.Schema({ 14 | _id: false, 15 | value: { 16 | type: String, 17 | es_value(value, context) { 18 | expect(value).to.equal('x2'); 19 | expect(context.document === john).to.be.true; 20 | expect(context.container === john.sub.sub2).to.be.true; 21 | expect(context.field).to.eql('value'); 22 | return 'xyz'; 23 | }, 24 | }, 25 | }); 26 | 27 | const SubSchema = new mongoose.Schema({ 28 | _id: false, 29 | sub1: String, 30 | sub2: Sub2Schema, 31 | }); 32 | 33 | const TagSchema = new mongoose.Schema({ 34 | _id: false, 35 | value: String, 36 | }); 37 | 38 | const UserSchema = new mongoose.Schema({ 39 | name: String, 40 | sub: SubSchema, 41 | age: { 42 | type: Number, 43 | es_value(age, context) { 44 | expect(age).to.equal(35); 45 | expect(context.document === john).to.be.true; 46 | expect(context.container === john).to.be.true; 47 | expect(context.field).to.eql('age'); 48 | return age - (age % 10); 49 | }, 50 | }, 51 | tags: { 52 | type: [TagSchema], 53 | es_type: 'text', 54 | es_value(tags, context) { 55 | expect(tags === john.tags).to.be.true; 56 | expect(context.document === john).to.be.true; 57 | expect(context.container === john).to.be.true; 58 | expect(context.field).to.eql('tags'); 59 | return tags.map(tag => { 60 | return tag.value; 61 | }); 62 | }, 63 | }, 64 | }); 65 | 66 | UserSchema.plugin(plugin); 67 | 68 | const UserModel = mongoose.model('User', UserSchema); 69 | 70 | john = new UserModel({ 71 | name: 'John', 72 | age: 35, 73 | sub: { 74 | sub1: 'x1', 75 | sub2: { 76 | value: 'x2', 77 | nb: 7, 78 | }, 79 | }, 80 | tags: [{ value: 'cool' }, { value: 'green' }], 81 | }); 82 | 83 | return utils 84 | .deleteModelIndexes(UserModel) 85 | .then(() => { 86 | return UserModel.esCreateMapping(); 87 | }) 88 | .then(() => { 89 | return john.esIndex(); 90 | }) 91 | .then(() => { 92 | return UserModel.esRefresh(); 93 | }) 94 | .then(() => { 95 | const options = UserModel.esOptions(); 96 | const client = options.client; 97 | return client.search({ 98 | index: options.index, 99 | type: options.type, 100 | body: { query: { match_all: {} } }, 101 | }); 102 | }) 103 | .then(resp => { 104 | expect(resp.hits.total).to.eql(1); 105 | const hit = resp.hits.hits[0]; 106 | expect(hit._id).to.eql(john._id.toString()); 107 | expect(hit._source).to.eql({ 108 | name: 'John', 109 | age: 30, 110 | tags: ['cool', 'green'], 111 | sub: { sub1: 'x1', sub2: { value: 'xyz' } }, 112 | }); 113 | }); 114 | }); 115 | 116 | it('should handle es_value as a value', () => { 117 | const UserSchema = new mongoose.Schema({ 118 | name: { type: String, es_type: 'keyword' }, 119 | numberArray: { 120 | type: Number, 121 | es_value: [1, 2, 3], 122 | }, 123 | falsy: { 124 | type: Number, 125 | es_value: 0, 126 | }, 127 | stringArray: { 128 | type: [String], 129 | es_value: ['az', 'er', 'ty'], 130 | }, 131 | obj: { 132 | type: String, 133 | es_type: { 134 | a: { es_type: 'text' }, 135 | b: { es_type: 'integer' }, 136 | }, 137 | es_value: { 138 | a: 'azerty', 139 | b: 123, 140 | }, 141 | }, 142 | }); 143 | 144 | UserSchema.plugin(plugin); 145 | 146 | const UserModel = mongoose.model('User', UserSchema); 147 | 148 | const john = new UserModel({ 149 | name: 'John', 150 | numberArray: 35, 151 | falsy: 98, 152 | stringArray: ['GHJ'], 153 | obj: 'obj', 154 | }); 155 | 156 | const bob = new UserModel({ name: 'Bob' }); 157 | 158 | return utils 159 | .deleteModelIndexes(UserModel) 160 | .then(() => { 161 | return UserModel.esCreateMapping(); 162 | }) 163 | .then(() => { 164 | return john.esIndex(); 165 | }) 166 | .then(() => { 167 | return bob.esIndex(); 168 | }) 169 | .then(() => { 170 | return UserModel.esRefresh(); 171 | }) 172 | .then(() => { 173 | const options = UserModel.esOptions(); 174 | const client = options.client; 175 | return client.search({ 176 | index: options.index, 177 | type: options.type, 178 | body: { 179 | query: { match_all: {} }, 180 | sort: [{ name: { order: 'desc' } }], 181 | }, 182 | }); 183 | }) 184 | .then(resp => { 185 | expect(resp.hits.total).to.eql(2); 186 | let hit = resp.hits.hits[0]; 187 | expect(hit._id).to.eql(john._id.toString()); 188 | expect(hit._source).to.eql({ 189 | name: 'John', 190 | numberArray: [1, 2, 3], 191 | falsy: 0, 192 | stringArray: ['az', 'er', 'ty'], 193 | obj: { 194 | a: 'azerty', 195 | b: 123, 196 | }, 197 | }); 198 | hit = resp.hits.hits[1]; 199 | expect(hit._id).to.eql(bob._id.toString()); 200 | expect(hit._source).to.eql({ 201 | name: 'Bob', 202 | numberArray: [1, 2, 3], 203 | falsy: 0, 204 | stringArray: ['az', 'er', 'ty'], 205 | obj: { 206 | a: 'azerty', 207 | b: 123, 208 | }, 209 | }); 210 | }); 211 | }); 212 | }); 213 | -------------------------------------------------------------------------------- /test/es6/idsOnly.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('idsOnly', () => { 8 | utils.setup(); 9 | let UserModel; 10 | let john; 11 | let jane; 12 | let bob; 13 | 14 | beforeEach(() => { 15 | const UserSchema = new mongoose.Schema({ 16 | name: String, 17 | age: Number, 18 | }); 19 | 20 | UserSchema.plugin(plugin); 21 | 22 | UserModel = mongoose.model('User', UserSchema); 23 | 24 | john = new UserModel({ name: 'John', age: 35 }); 25 | jane = new UserModel({ name: 'Jane', age: 34 }); 26 | bob = new UserModel({ name: 'Bob', age: 36 }); 27 | 28 | return utils 29 | .deleteModelIndexes(UserModel) 30 | .then(() => { 31 | return UserModel.esCreateMapping(); 32 | }) 33 | .then(() => { 34 | return utils.Promise.all( 35 | [john, jane, bob].map(user => { 36 | return new utils.Promise(resolve => { 37 | user.on('es-indexed', resolve); 38 | user.save(); 39 | }); 40 | }) 41 | ); 42 | }) 43 | .then(() => { 44 | return UserModel.esRefresh(); 45 | }); 46 | }); 47 | 48 | it('should return ids', () => { 49 | return UserModel.esSearch( 50 | { 51 | query: { 52 | bool: { 53 | must: { match_all: {} }, 54 | filter: { range: { age: { gte: 35 } } }, 55 | }, 56 | }, 57 | sort: [{ age: { order: 'desc' } }], 58 | }, 59 | { idsOnly: true } 60 | ).then(ids => { 61 | expect(ids.length).to.eql(2); 62 | const idstrings = ids.map(id => { 63 | expect(id).to.be.an.instanceof(mongoose.Types.ObjectId); 64 | return id.toString(); 65 | }); 66 | expect(idstrings).to.eql([bob._id.toString(), john._id.toString()]); 67 | }); 68 | }); 69 | 70 | it('should an empty array', () => { 71 | return UserModel.esSearch( 72 | { 73 | query: { 74 | bool: { 75 | must: { match_all: {} }, 76 | filter: { range: { age: { gte: 100 } } }, 77 | }, 78 | }, 79 | sort: [{ age: { order: 'desc' } }], 80 | }, 81 | { idsOnly: true } 82 | ).then(ids => { 83 | expect(ids).to.eql([]); 84 | }); 85 | }); 86 | 87 | it('should return ids when defined in plugin', () => { 88 | utils.deleteMongooseModels(); 89 | 90 | const UserSchema = new mongoose.Schema({ 91 | name: String, 92 | age: Number, 93 | }); 94 | 95 | UserSchema.plugin(plugin, { idsOnly: true }); 96 | 97 | const UserModelIdsOnly = mongoose.model('User', UserSchema); 98 | 99 | return UserModelIdsOnly.esSearch({ 100 | query: { 101 | bool: { 102 | must: { match_all: {} }, 103 | filter: { range: { age: { gte: 35 } } }, 104 | }, 105 | }, 106 | sort: [{ age: { order: 'desc' } }], 107 | }).then(ids => { 108 | expect(ids.length).to.eql(2); 109 | const idstrings = ids.map(id => { 110 | expect(id).to.be.an.instanceof(mongoose.Types.ObjectId); 111 | return id.toString(); 112 | }); 113 | expect(idstrings).to.eql([bob._id.toString(), john._id.toString()]); 114 | }); 115 | }); 116 | 117 | it('should overwrite defined in plugin value', () => { 118 | utils.deleteMongooseModels(); 119 | 120 | const UserSchema = new mongoose.Schema({ 121 | name: String, 122 | age: Number, 123 | }); 124 | 125 | UserSchema.plugin(plugin, { idsOnly: true }); 126 | 127 | const UserModelIdsOnly = mongoose.model('User', UserSchema); 128 | 129 | return UserModelIdsOnly.esSearch( 130 | { 131 | query: { 132 | bool: { 133 | must: { match_all: {} }, 134 | filter: { range: { age: { gte: 35 } } }, 135 | }, 136 | }, 137 | sort: [{ age: { order: 'desc' } }], 138 | }, 139 | { idsOnly: false } 140 | ).then(result => { 141 | expect(result.hits.total).to.eql(2); 142 | let hit = result.hits.hits[0]; 143 | expect(hit._id).to.eql(bob._id.toString()); 144 | expect(hit._source).to.eql({ name: 'Bob', age: 36 }); 145 | 146 | hit = result.hits.hits[1]; 147 | expect(hit._id).to.eql(john._id.toString()); 148 | expect(hit._source).to.eql({ name: 'John', age: 35 }); 149 | }); 150 | }); 151 | }); 152 | -------------------------------------------------------------------------------- /test/es6/promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | 6 | const plugin = require('../../').v5; 7 | 8 | describe('promise', () => { 9 | utils.setup(); 10 | 11 | it('should return promise', () => { 12 | const UserSchema = new mongoose.Schema({ 13 | name: String, 14 | }); 15 | 16 | UserSchema.plugin(plugin); 17 | 18 | const UserModel = mongoose.model('User', UserSchema); 19 | const promise = UserModel.esCreateMapping(); 20 | expect(promise).to.be.an.instanceof(Promise); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/es6/withDiscriminators.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v5; 6 | 7 | describe('with discriminators', () => { 8 | utils.setup(); 9 | 10 | it('check types and mappings', () => { 11 | const BaseSchema = new mongoose.Schema({ 12 | name: String, 13 | }); 14 | 15 | const UserSchema = new mongoose.Schema({ 16 | age: Number, 17 | }); 18 | 19 | const AdminSchema = new mongoose.Schema({ 20 | access: Boolean, 21 | }); 22 | 23 | BaseSchema.plugin(plugin, { 24 | index: 'user', 25 | type: kind => { 26 | if (kind === 'User') return 'userType'; 27 | if (kind === 'Admin') return 'adminType'; 28 | return 'otherType'; 29 | }, 30 | }); 31 | 32 | const BaseModel = mongoose.model('Base', BaseSchema); 33 | const UserModel = BaseModel.discriminator('User', UserSchema); 34 | const AdminModel = BaseModel.discriminator('Admin', AdminSchema); 35 | 36 | // check types and mappings on models 37 | const userOpts = UserModel.esOptions(); 38 | const adminOpts = AdminModel.esOptions(); 39 | expect(userOpts.type).to.equal('userType'); 40 | expect(adminOpts.type).to.equal('adminType'); 41 | expect(userOpts.mapping).to.deep.equal({ 42 | properties: { 43 | __t: { 44 | type: 'text', 45 | }, 46 | age: { 47 | type: 'double', 48 | }, 49 | name: { 50 | type: 'text', 51 | }, 52 | }, 53 | }); 54 | expect(adminOpts.mapping).to.deep.equal({ 55 | properties: { 56 | __t: { 57 | type: 'text', 58 | }, 59 | access: { 60 | type: 'boolean', 61 | }, 62 | name: { 63 | type: 'text', 64 | }, 65 | }, 66 | }); 67 | 68 | // check types and mappings on docs 69 | UserModel.create({ 70 | name: 'John', 71 | age: 34, 72 | }).then(doc => { 73 | const opts = doc.esOptions(); 74 | expect(opts.type).to.equal('userType'); 75 | expect(opts.mapping).to.deep.equal({ 76 | properties: { 77 | __t: { 78 | type: 'text', 79 | }, 80 | age: { 81 | type: 'double', 82 | }, 83 | name: { 84 | type: 'text', 85 | }, 86 | }, 87 | }); 88 | }); 89 | 90 | AdminModel.create({ 91 | name: 'Steve', 92 | access: true, 93 | }).then(doc => { 94 | const opts = doc.esOptions(); 95 | expect(opts.type).to.equal('adminType'); 96 | expect(opts.mapping).to.deep.equal({ 97 | properties: { 98 | __t: { 99 | type: 'text', 100 | }, 101 | access: { 102 | type: 'boolean', 103 | }, 104 | name: { 105 | type: 'text', 106 | }, 107 | }, 108 | }); 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/es7/countOnly.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v7; 6 | 7 | describe('countOnly', () => { 8 | utils.setup(); 9 | let UserModel; 10 | 11 | beforeEach(() => { 12 | const UserSchema = new mongoose.Schema({ 13 | name: String, 14 | age: Number, 15 | }); 16 | 17 | UserSchema.plugin(plugin); 18 | UserModel = mongoose.model('User', UserSchema); 19 | 20 | const john = new UserModel({ name: 'John', age: 35 }); 21 | const jane = new UserModel({ name: 'Jane', age: 34 }); 22 | const bob = new UserModel({ name: 'Bob', age: 36 }); 23 | 24 | return utils 25 | .deleteModelIndexes(UserModel) 26 | .then(() => UserModel.esCreateMapping()) 27 | .then(() => 28 | utils.Promise.all( 29 | [john, jane, bob].map( 30 | user => 31 | new utils.Promise(resolve => { 32 | user.on('es-indexed', resolve); 33 | user.save(); 34 | }) 35 | ) 36 | ) 37 | ) 38 | .then(() => UserModel.esRefresh()); 39 | }); 40 | 41 | it('should return count', () => { 42 | return UserModel.esCount( 43 | { 44 | bool: { 45 | must: { match_all: {} }, 46 | filter: { range: { age: { gte: 35 } } }, 47 | }, 48 | }, 49 | { countOnly: true } 50 | ).then(count => { 51 | expect(count).to.eql(2); 52 | }); 53 | }); 54 | 55 | it('should return 0', () => { 56 | return UserModel.esCount( 57 | { 58 | bool: { 59 | must: { match_all: {} }, 60 | filter: { range: { age: { gte: 100 } } }, 61 | }, 62 | }, 63 | { countOnly: true } 64 | ).then(count => { 65 | expect(count).to.eql(0); 66 | }); 67 | }); 68 | 69 | it('should return count when defined in plugin', () => { 70 | utils.deleteMongooseModels(); 71 | 72 | const UserSchema = new mongoose.Schema({ 73 | name: String, 74 | age: Number, 75 | }); 76 | 77 | UserSchema.plugin(plugin, { countOnly: true }); 78 | 79 | const UserModelCountOnly = mongoose.model('User', UserSchema); 80 | 81 | return UserModelCountOnly.esCount({ 82 | bool: { 83 | must: { match_all: {} }, 84 | filter: { range: { age: { gte: 35 } } }, 85 | }, 86 | }).then(count => { 87 | expect(count).to.eql(2); 88 | }); 89 | }); 90 | 91 | it('should overwrite defined in plugin value', () => { 92 | utils.deleteMongooseModels(); 93 | 94 | const UserSchema = new mongoose.Schema({ 95 | name: String, 96 | age: Number, 97 | }); 98 | 99 | UserSchema.plugin(plugin, { countOnly: true }); 100 | 101 | const UserModelCountOnly = mongoose.model('User', UserSchema); 102 | 103 | return UserModelCountOnly.esCount( 104 | { 105 | bool: { 106 | must: { match_all: {} }, 107 | filter: { range: { age: { gte: 35 } } }, 108 | }, 109 | }, 110 | { countOnly: false } 111 | ).then(result => { 112 | expect(result.count).to.eql(2); 113 | }); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /test/es7/customIds.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const shortid = require('shortid'); 5 | const utils = require('../utils'); 6 | const plugin = require('../../').v7; 7 | 8 | describe('custom ids', () => { 9 | utils.setup(); 10 | let UserModel; 11 | let john; 12 | let jane; 13 | let bob; 14 | 15 | beforeEach(() => { 16 | const UserSchema = new mongoose.Schema({ 17 | _id: { 18 | type: String, 19 | default: shortid.generate, 20 | }, 21 | name: String, 22 | age: Number, 23 | }); 24 | 25 | UserSchema.plugin(plugin); 26 | 27 | UserModel = mongoose.model('User', UserSchema); 28 | 29 | john = new UserModel({ name: 'John', age: 35 }); 30 | jane = new UserModel({ name: 'Jane', age: 34 }); 31 | bob = new UserModel({ name: 'Bob', age: 36 }); 32 | 33 | return utils 34 | .deleteModelIndexes(UserModel) 35 | .then(() => { 36 | return UserModel.esCreateMapping(); 37 | }) 38 | .then(() => { 39 | return utils.Promise.all( 40 | [john, jane, bob].map(user => { 41 | return new utils.Promise(resolve => { 42 | user.on('es-indexed', resolve); 43 | user.save(); 44 | }); 45 | }) 46 | ); 47 | }) 48 | .then(() => { 49 | return UserModel.esRefresh(); 50 | }); 51 | }); 52 | 53 | it('should return ids', () => { 54 | return UserModel.esSearch( 55 | { 56 | query: { 57 | bool: { 58 | must: { match_all: {} }, 59 | filter: { range: { age: { gte: 35 } } }, 60 | }, 61 | }, 62 | sort: [{ age: { order: 'desc' } }], 63 | }, 64 | { idsOnly: true } 65 | ).then(ids => { 66 | expect(ids.length).to.eql(2); 67 | expect(ids).to.eql([bob._id, john._id]); 68 | }); 69 | }); 70 | 71 | it('should return people', () => { 72 | return UserModel.esSearch({ 73 | query: { 74 | bool: { 75 | must: { match_all: {} }, 76 | filter: { range: { age: { gte: 35 } } }, 77 | }, 78 | }, 79 | sort: [{ age: { order: 'desc' } }], 80 | }).then(result => { 81 | expect(result.hits.total.value).to.eql(2); 82 | expect(result.hits.hits[0]._source).to.eql({ name: 'Bob', age: 36 }); 83 | expect(result.hits.hits[1]._source).to.eql({ name: 'John', age: 35 }); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/es7/esCount.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v7; 6 | 7 | describe('esCount', () => { 8 | utils.setup(); 9 | let UserModel; 10 | 11 | beforeEach(() => { 12 | const UserSchema = new mongoose.Schema({ 13 | name: String, 14 | age: Number, 15 | }); 16 | 17 | UserSchema.plugin(plugin); 18 | UserModel = mongoose.model('User', UserSchema); 19 | 20 | const john = new UserModel({ name: 'John', age: 35 }); 21 | const jane = new UserModel({ name: 'Jane', age: 34 }); 22 | const bob = new UserModel({ name: 'Bob', age: 36 }); 23 | 24 | return utils 25 | .deleteModelIndexes(UserModel) 26 | .then(() => { 27 | return UserModel.esCreateMapping(); 28 | }) 29 | .then(() => { 30 | const options = UserModel.esOptions(); 31 | const client = options.client; 32 | return client.bulk({ 33 | refresh: true, 34 | body: [ 35 | { 36 | index: { 37 | _index: options.index, 38 | _type: options.type, 39 | _id: john._id.toString(), 40 | }, 41 | }, 42 | { name: 'John', age: 35 }, 43 | { 44 | index: { 45 | _index: options.index, 46 | _type: options.type, 47 | _id: jane._id.toString(), 48 | }, 49 | }, 50 | { name: 'Jane', age: 34 }, 51 | { 52 | index: { 53 | _index: options.index, 54 | _type: options.type, 55 | _id: bob._id.toString(), 56 | }, 57 | }, 58 | { name: 'Bob', age: 36 }, 59 | ], 60 | }); 61 | }); 62 | }); 63 | 64 | it('should handle a lucene query', () => { 65 | return UserModel.esCount('name:jane').then(result => { 66 | expect(result.count).to.eql(1); 67 | }); 68 | }); 69 | 70 | it('should accept callback', done => { 71 | const returned = UserModel.esCount('name:jane', (err, result) => { 72 | if (err) { 73 | done(err); 74 | return; 75 | } 76 | expect(result.count).to.eql(1); 77 | expect(returned).to.be.undefined; 78 | done(); 79 | }); 80 | }); 81 | 82 | it('should handle a full query', () => { 83 | return UserModel.esCount({ 84 | bool: { 85 | must: { match_all: {} }, 86 | filter: { range: { age: { lt: 35 } } }, 87 | }, 88 | }).then(result => { 89 | expect(result.count).to.eql(1); 90 | }); 91 | }); 92 | 93 | it('should handle a short query', () => { 94 | return UserModel.esCount({ match: { age: 34 } }).then(result => { 95 | expect(result.count).to.eql(1); 96 | }); 97 | }); 98 | 99 | it('should handle 0 hit', () => { 100 | return UserModel.esCount({ match: { age: 100 } }).then(result => { 101 | expect(result.count).to.eql(0); 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /test/es7/esIndex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v7; 6 | 7 | describe('esIndex', () => { 8 | utils.setup(); 9 | 10 | it('should be indexed', () => { 11 | const UserSchema = new mongoose.Schema({ 12 | name: String, 13 | age: Number, 14 | pos: { 15 | type: [Number], 16 | index: '2dsphere', 17 | es_type: 'geo_point', 18 | es_boost: 1.5, 19 | }, 20 | doNotIndexMe: Boolean, 21 | }); 22 | 23 | UserSchema.plugin(plugin, { 24 | transform: document => { 25 | delete document.doNotIndexMe; 26 | return document; 27 | }, 28 | }); 29 | const UserModel = mongoose.model('User', UserSchema); 30 | 31 | const john = new UserModel({ 32 | name: 'John', 33 | age: 35, 34 | pos: [5.7333, 43.5], 35 | doNotIndexMe: true, 36 | }); 37 | 38 | return utils 39 | .deleteModelIndexes(UserModel) 40 | .then(() => { 41 | return UserModel.esCreateMapping(); 42 | }) 43 | .then(() => { 44 | return john.esIndex(); 45 | }) 46 | .then(() => { 47 | return UserModel.esRefresh(); 48 | }) 49 | .then(() => { 50 | const options = UserModel.esOptions(); 51 | const client = options.client; 52 | return client.search({ 53 | index: options.index, 54 | type: options.type, 55 | body: { query: { match_all: {} } }, 56 | }); 57 | }) 58 | .then(resp => { 59 | expect(resp.hits.total.value).to.eql(1); 60 | const hit = resp.hits.hits[0]; 61 | expect(hit._id).to.eql(john._id.toString()); 62 | expect(hit._source).to.eql({ 63 | name: 'John', 64 | age: 35, 65 | pos: [5.7333, 43.5], 66 | }); 67 | }); 68 | }); 69 | 70 | it('should index ObjectId from object populated or not', () => { 71 | const CountrySchema = new mongoose.Schema({ 72 | name: String, 73 | }); 74 | 75 | const CitySchema = new mongoose.Schema({ 76 | name: String, 77 | }); 78 | 79 | const UserSchema = new mongoose.Schema({ 80 | name: String, 81 | city: { type: mongoose.Schema.Types.ObjectId, ref: 'City' }, 82 | country: { type: mongoose.Schema.Types.ObjectId, ref: 'Country' }, 83 | }); 84 | 85 | UserSchema.plugin(plugin); 86 | 87 | const UserModel = mongoose.model('User', UserSchema); 88 | const CityModel = mongoose.model('City', CitySchema); 89 | const CountryModel = mongoose.model('Country', CountrySchema); 90 | 91 | const country = new CountryModel({ name: 'France' }); 92 | const city = new CityModel({ name: 'Paris' }); 93 | const john = new UserModel({ 94 | name: 'John', 95 | city, 96 | country: country._id, 97 | }); 98 | 99 | return utils 100 | .deleteModelIndexes(UserModel) 101 | .then(() => { 102 | return UserModel.esCreateMapping(); 103 | }) 104 | .then(() => { 105 | return john.esIndex(); 106 | }) 107 | .then(() => { 108 | return UserModel.esRefresh(); 109 | }) 110 | .then(() => { 111 | const options = UserModel.esOptions(); 112 | const client = options.client; 113 | return client.search({ 114 | index: options.index, 115 | type: options.type, 116 | body: { query: { match_all: {} } }, 117 | }); 118 | }) 119 | .then(resp => { 120 | expect(resp.hits.total.value).to.eql(1); 121 | const hit = resp.hits.hits[0]; 122 | expect(hit._id).to.eql(john._id.toString()); 123 | expect(hit._source).to.eql({ 124 | name: 'John', 125 | city: city.id, 126 | country: country.id, 127 | }); 128 | }); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /test/es7/esRefresh.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v7; 6 | 7 | describe('esRefresh', () => { 8 | utils.setup(); 9 | 10 | it('should handle callback', done => { 11 | const UserSchema = new mongoose.Schema({ 12 | name: String, 13 | }); 14 | 15 | UserSchema.plugin(plugin); 16 | const UserModel = mongoose.model('User', UserSchema); 17 | 18 | let start; 19 | 20 | utils 21 | .deleteModelIndexes(UserModel) 22 | .then(() => { 23 | return UserModel.esCreateMapping(); 24 | }) 25 | .then(() => { 26 | start = Date.now(); 27 | UserModel.esRefresh(err => { 28 | if (err) { 29 | done(err); 30 | return; 31 | } 32 | expect(Date.now() - start).to.be.lt(500); 33 | done(); 34 | }); 35 | }) 36 | .catch(err => { 37 | done(err); 38 | }); 39 | }); 40 | 41 | it('should handle callback and options', done => { 42 | const UserSchema = new mongoose.Schema({ 43 | name: String, 44 | }); 45 | 46 | UserSchema.plugin(plugin); 47 | 48 | const UserModel = mongoose.model('User', UserSchema); 49 | 50 | let start; 51 | 52 | utils 53 | .deleteModelIndexes(UserModel) 54 | .then(() => { 55 | return UserModel.esCreateMapping(); 56 | }) 57 | .then(() => { 58 | start = Date.now(); 59 | UserModel.esRefresh({ refreshDelay: 1000 }, err => { 60 | if (err) { 61 | done(err); 62 | return; 63 | } 64 | expect(Date.now() - start).to.be.gte(1000); 65 | done(); 66 | }); 67 | }) 68 | .catch(err => { 69 | done(err); 70 | }); 71 | }); 72 | 73 | it('should not be delayed', () => { 74 | const UserSchema = new mongoose.Schema({ 75 | name: String, 76 | }); 77 | 78 | UserSchema.plugin(plugin); 79 | 80 | const UserModel = mongoose.model('User', UserSchema); 81 | 82 | let start; 83 | 84 | return utils 85 | .deleteModelIndexes(UserModel) 86 | .then(() => { 87 | return UserModel.esCreateMapping(); 88 | }) 89 | .then(() => { 90 | start = Date.now(); 91 | return UserModel.esRefresh(); 92 | }) 93 | .then(() => { 94 | expect(Date.now() - start).to.be.lt(500); 95 | }); 96 | }); 97 | 98 | it('should be delayed', () => { 99 | const UserSchema = new mongoose.Schema({ 100 | name: String, 101 | }); 102 | 103 | UserSchema.plugin(plugin); 104 | 105 | const UserModel = mongoose.model('User', UserSchema); 106 | 107 | let start; 108 | 109 | return utils 110 | .deleteModelIndexes(UserModel) 111 | .then(() => { 112 | return UserModel.esCreateMapping(); 113 | }) 114 | .then(() => { 115 | start = Date.now(); 116 | return UserModel.esRefresh({ refreshDelay: 1000 }); 117 | }) 118 | .then(() => { 119 | expect(Date.now() - start).to.be.gte(1000); 120 | }); 121 | }); 122 | 123 | it('should be delayed when defined in plugin', () => { 124 | const UserSchema = new mongoose.Schema({ 125 | name: String, 126 | }); 127 | 128 | UserSchema.plugin(plugin, { refreshDelay: 1000 }); 129 | 130 | const UserModel = mongoose.model('User', UserSchema); 131 | 132 | let start; 133 | 134 | return utils 135 | .deleteModelIndexes(UserModel) 136 | .then(() => { 137 | return UserModel.esCreateMapping(); 138 | }) 139 | .then(() => { 140 | start = Date.now(); 141 | return UserModel.esRefresh(); 142 | }) 143 | .then(() => { 144 | expect(Date.now() - start).to.be.gte(1000); 145 | }); 146 | }); 147 | 148 | it('should overwrite defined in plugin value', () => { 149 | const UserSchema = new mongoose.Schema({ 150 | name: String, 151 | }); 152 | 153 | UserSchema.plugin(plugin, { refreshDelay: 1000 }); 154 | 155 | const UserModel = mongoose.model('User', UserSchema); 156 | 157 | let start; 158 | 159 | return utils 160 | .deleteModelIndexes(UserModel) 161 | .then(() => { 162 | return UserModel.esCreateMapping(); 163 | }) 164 | .then(() => { 165 | start = Date.now(); 166 | return UserModel.esRefresh({ refreshDelay: false }); 167 | }) 168 | .then(() => { 169 | expect(Date.now() - start).to.be.lt(500); 170 | }); 171 | }); 172 | }); 173 | -------------------------------------------------------------------------------- /test/es7/esRemove.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v7; 6 | 7 | describe('esRemove', () => { 8 | utils.setup(); 9 | 10 | it('should be removed', () => { 11 | const UserSchema = new mongoose.Schema({ 12 | name: String, 13 | age: Number, 14 | }); 15 | 16 | UserSchema.plugin(plugin); 17 | 18 | const UserModel = mongoose.model('User', UserSchema); 19 | 20 | const john = new UserModel({ name: 'John', age: 35 }); 21 | const jane = new UserModel({ name: 'Jane', age: 34 }); 22 | const bob = new UserModel({ name: 'Bob', age: 36 }); 23 | 24 | return utils 25 | .deleteModelIndexes(UserModel) 26 | .then(() => { 27 | return UserModel.esCreateMapping(); 28 | }) 29 | .then(() => { 30 | const options = UserModel.esOptions(); 31 | const client = options.client; 32 | return client.bulk({ 33 | refresh: true, 34 | body: [ 35 | { 36 | index: { 37 | _index: options.index, 38 | _type: options.type, 39 | _id: john._id.toString(), 40 | }, 41 | }, 42 | { name: 'John', age: 35 }, 43 | { 44 | index: { 45 | _index: options.index, 46 | _type: options.type, 47 | _id: jane._id.toString(), 48 | }, 49 | }, 50 | { name: 'Jane', age: 34 }, 51 | { 52 | index: { 53 | _index: options.index, 54 | _type: options.type, 55 | _id: bob._id.toString(), 56 | }, 57 | }, 58 | { name: 'Bob', age: 36 }, 59 | ], 60 | }); 61 | }) 62 | .then(() => { 63 | return jane.esRemove(); 64 | }) 65 | .then(() => { 66 | return UserModel.esRefresh(); 67 | }) 68 | .then(() => { 69 | const options = UserModel.esOptions(); 70 | const client = options.client; 71 | return client.search({ 72 | index: options.index, 73 | type: options.type, 74 | body: { query: { match_all: {} } }, 75 | }); 76 | }) 77 | .then(resp => { 78 | const ids = resp.hits.hits.map(hit => { 79 | return hit._id; 80 | }); 81 | ids.sort(); 82 | 83 | const expectedIds = [john, bob].map(user => { 84 | return user._id.toString(); 85 | }); 86 | 87 | expect(ids).to.eql(expectedIds); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/es7/esSearch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v7; 6 | 7 | describe('esSearch', () => { 8 | utils.setup(); 9 | let UserModel; 10 | let john; 11 | let jane; 12 | let bob; 13 | 14 | beforeEach(() => { 15 | const UserSchema = new mongoose.Schema({ 16 | name: String, 17 | age: Number, 18 | }); 19 | 20 | UserSchema.plugin(plugin); 21 | UserModel = mongoose.model('User', UserSchema); 22 | 23 | john = new UserModel({ name: 'John', age: 35 }); 24 | jane = new UserModel({ name: 'Jane', age: 34 }); 25 | bob = new UserModel({ name: 'Bob', age: 36 }); 26 | 27 | return utils 28 | .deleteModelIndexes(UserModel) 29 | .then(() => { 30 | return UserModel.esCreateMapping(); 31 | }) 32 | .then(() => { 33 | const options = UserModel.esOptions(); 34 | const client = options.client; 35 | return client.bulk({ 36 | refresh: true, 37 | body: [ 38 | { 39 | index: { 40 | _index: options.index, 41 | _type: options.type, 42 | _id: john._id.toString(), 43 | }, 44 | }, 45 | { name: 'John', age: 35 }, 46 | { 47 | index: { 48 | _index: options.index, 49 | _type: options.type, 50 | _id: jane._id.toString(), 51 | }, 52 | }, 53 | { name: 'Jane', age: 34 }, 54 | { 55 | index: { 56 | _index: options.index, 57 | _type: options.type, 58 | _id: bob._id.toString(), 59 | }, 60 | }, 61 | { name: 'Bob', age: 36 }, 62 | ], 63 | }); 64 | }); 65 | }); 66 | 67 | it('should handle a lucene query', () => { 68 | return UserModel.esSearch('name:jane').then(result => { 69 | expect(result.hits.total.value).to.eql(1); 70 | const hit = result.hits.hits[0]; 71 | expect(hit._id).to.eql(jane._id.toString()); 72 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 73 | }); 74 | }); 75 | 76 | it('should accept callback', done => { 77 | const returned = UserModel.esSearch('name:jane', {}, (err, result) => { 78 | if (err) { 79 | done(err); 80 | return; 81 | } 82 | expect(result.hits.total.value).to.eql(1); 83 | const hit = result.hits.hits[0]; 84 | expect(hit._id).to.eql(jane._id.toString()); 85 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 86 | expect(returned).to.be.undefined; 87 | done(); 88 | }); 89 | }); 90 | 91 | it('should accept callback without options', done => { 92 | const returned = UserModel.esSearch('name:jane', (err, result) => { 93 | if (err) { 94 | done(err); 95 | return; 96 | } 97 | expect(result.hits.total.value).to.eql(1); 98 | const hit = result.hits.hits[0]; 99 | expect(hit._id).to.eql(jane._id.toString()); 100 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 101 | expect(returned).to.be.undefined; 102 | done(); 103 | }); 104 | }); 105 | 106 | it('should handle a full query', () => { 107 | return UserModel.esSearch({ 108 | bool: { 109 | must: { match_all: {} }, 110 | filter: { range: { age: { lt: 35 } } }, 111 | }, 112 | }).then(result => { 113 | expect(result.hits.total.value).to.eql(1); 114 | const hit = result.hits.hits[0]; 115 | expect(hit._id).to.eql(jane._id.toString()); 116 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 117 | }); 118 | }); 119 | 120 | it('should handle a short query', () => { 121 | return UserModel.esSearch({ match: { age: 34 } }).then(result => { 122 | expect(result.hits.total.value).to.eql(1); 123 | const hit = result.hits.hits[0]; 124 | expect(hit._id).to.eql(jane._id.toString()); 125 | expect(hit._source).to.eql({ name: 'Jane', age: 34 }); 126 | }); 127 | }); 128 | 129 | it('should handle 0 hit', () => { 130 | return UserModel.esSearch({ match: { age: 100 } }).then(result => { 131 | expect(result.hits.total.value).to.eql(0); 132 | }); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /test/es7/es_extend.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v7; 6 | 7 | describe('es_extend', () => { 8 | utils.setup(); 9 | 10 | it('should add some fields', () => { 11 | let john; 12 | 13 | const UserSchema = new mongoose.Schema( 14 | { 15 | name: String, 16 | }, 17 | { 18 | es_extend: { 19 | num: { 20 | es_type: 'integer', 21 | es_value: 123, 22 | }, 23 | length: { 24 | es_type: 'integer', 25 | es_value(document) { 26 | expect(document === john).to.be.true; 27 | return document.name.length; 28 | }, 29 | }, 30 | }, 31 | } 32 | ); 33 | 34 | UserSchema.plugin(plugin); 35 | 36 | const UserModel = mongoose.model('User', UserSchema); 37 | 38 | john = new UserModel({ 39 | name: 'John', 40 | }); 41 | 42 | return utils 43 | .deleteModelIndexes(UserModel) 44 | .then(() => { 45 | return UserModel.esCreateMapping(); 46 | }) 47 | .then(() => { 48 | const options = UserModel.esOptions(); 49 | return options.client.indices.getMapping({ 50 | include_type_name: true, 51 | index: options.index, 52 | type: options.type, 53 | }); 54 | }) 55 | .then(mapping => { 56 | const properties = mapping.users.mappings.user.properties; 57 | expect(properties).to.have.all.keys('name', 'num', 'length'); 58 | expect(properties.name.type).to.be.equal('text'); 59 | expect(properties.num.type).to.be.equal('integer'); 60 | expect(properties.length.type).to.be.equal('integer'); 61 | }) 62 | .then(() => { 63 | return new utils.Promise(resolve => { 64 | john.on('es-indexed', () => { 65 | resolve(); 66 | }); 67 | john.save(); 68 | }); 69 | }) 70 | .then(() => { 71 | return UserModel.esRefresh(); 72 | }) 73 | .then(() => { 74 | return UserModel.esSearch({ 75 | query: { match_all: {} }, 76 | }); 77 | }) 78 | .then(result => { 79 | expect(result.hits.total.value).to.eql(1); 80 | expect(result.hits.hits[0]._source).to.eql({ 81 | name: 'John', 82 | num: 123, 83 | length: 4, 84 | }); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /test/es7/es_value.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v7; 6 | 7 | describe('es_value', () => { 8 | utils.setup(); 9 | 10 | it('should handle es_value as a function', () => { 11 | let john; 12 | 13 | const Sub2Schema = new mongoose.Schema({ 14 | _id: false, 15 | value: { 16 | type: String, 17 | es_value(value, context) { 18 | expect(value).to.equal('x2'); 19 | expect(context.document === john).to.be.true; 20 | expect(context.container === john.sub.sub2).to.be.true; 21 | expect(context.field).to.eql('value'); 22 | return 'xyz'; 23 | }, 24 | }, 25 | }); 26 | 27 | const SubSchema = new mongoose.Schema({ 28 | _id: false, 29 | sub1: String, 30 | sub2: Sub2Schema, 31 | }); 32 | 33 | const TagSchema = new mongoose.Schema({ 34 | _id: false, 35 | value: String, 36 | }); 37 | 38 | const UserSchema = new mongoose.Schema({ 39 | name: String, 40 | sub: SubSchema, 41 | age: { 42 | type: Number, 43 | es_value(age, context) { 44 | expect(age).to.equal(35); 45 | expect(context.document === john).to.be.true; 46 | expect(context.container === john).to.be.true; 47 | expect(context.field).to.eql('age'); 48 | return age - (age % 10); 49 | }, 50 | }, 51 | tags: { 52 | type: [TagSchema], 53 | es_type: 'text', 54 | es_value(tags, context) { 55 | expect(tags === john.tags).to.be.true; 56 | expect(context.document === john).to.be.true; 57 | expect(context.container === john).to.be.true; 58 | expect(context.field).to.eql('tags'); 59 | return tags.map(tag => { 60 | return tag.value; 61 | }); 62 | }, 63 | }, 64 | }); 65 | 66 | UserSchema.plugin(plugin); 67 | 68 | const UserModel = mongoose.model('User', UserSchema); 69 | 70 | john = new UserModel({ 71 | name: 'John', 72 | age: 35, 73 | sub: { 74 | sub1: 'x1', 75 | sub2: { 76 | value: 'x2', 77 | nb: 7, 78 | }, 79 | }, 80 | tags: [{ value: 'cool' }, { value: 'green' }], 81 | }); 82 | 83 | return utils 84 | .deleteModelIndexes(UserModel) 85 | .then(() => { 86 | return UserModel.esCreateMapping(); 87 | }) 88 | .then(() => { 89 | return john.esIndex(); 90 | }) 91 | .then(() => { 92 | return UserModel.esRefresh(); 93 | }) 94 | .then(() => { 95 | const options = UserModel.esOptions(); 96 | const client = options.client; 97 | return client.search({ 98 | index: options.index, 99 | type: options.type, 100 | body: { query: { match_all: {} } }, 101 | }); 102 | }) 103 | .then(resp => { 104 | expect(resp.hits.total.value).to.eql(1); 105 | const hit = resp.hits.hits[0]; 106 | expect(hit._id).to.eql(john._id.toString()); 107 | expect(hit._source).to.eql({ 108 | name: 'John', 109 | age: 30, 110 | tags: ['cool', 'green'], 111 | sub: { sub1: 'x1', sub2: { value: 'xyz' } }, 112 | }); 113 | }); 114 | }); 115 | 116 | it('should handle es_value as a value', () => { 117 | const UserSchema = new mongoose.Schema({ 118 | name: { type: String, es_type: 'keyword' }, 119 | numberArray: { 120 | type: Number, 121 | es_value: [1, 2, 3], 122 | }, 123 | falsy: { 124 | type: Number, 125 | es_value: 0, 126 | }, 127 | stringArray: { 128 | type: [String], 129 | es_value: ['az', 'er', 'ty'], 130 | }, 131 | obj: { 132 | type: String, 133 | es_type: { 134 | a: { es_type: 'text' }, 135 | b: { es_type: 'integer' }, 136 | }, 137 | es_value: { 138 | a: 'azerty', 139 | b: 123, 140 | }, 141 | }, 142 | }); 143 | 144 | UserSchema.plugin(plugin); 145 | 146 | const UserModel = mongoose.model('User', UserSchema); 147 | 148 | const john = new UserModel({ 149 | name: 'John', 150 | numberArray: 35, 151 | falsy: 98, 152 | stringArray: ['GHJ'], 153 | obj: 'obj', 154 | }); 155 | 156 | const bob = new UserModel({ name: 'Bob' }); 157 | 158 | return utils 159 | .deleteModelIndexes(UserModel) 160 | .then(() => { 161 | return UserModel.esCreateMapping(); 162 | }) 163 | .then(() => { 164 | return john.esIndex(); 165 | }) 166 | .then(() => { 167 | return bob.esIndex(); 168 | }) 169 | .then(() => { 170 | return UserModel.esRefresh(); 171 | }) 172 | .then(() => { 173 | const options = UserModel.esOptions(); 174 | const client = options.client; 175 | return client.search({ 176 | index: options.index, 177 | type: options.type, 178 | body: { 179 | query: { match_all: {} }, 180 | sort: [{ name: { order: 'desc' } }], 181 | }, 182 | }); 183 | }) 184 | .then(resp => { 185 | expect(resp.hits.total.value).to.eql(2); 186 | let hit = resp.hits.hits[0]; 187 | expect(hit._id).to.eql(john._id.toString()); 188 | expect(hit._source).to.eql({ 189 | name: 'John', 190 | numberArray: [1, 2, 3], 191 | falsy: 0, 192 | stringArray: ['az', 'er', 'ty'], 193 | obj: { 194 | a: 'azerty', 195 | b: 123, 196 | }, 197 | }); 198 | hit = resp.hits.hits[1]; 199 | expect(hit._id).to.eql(bob._id.toString()); 200 | expect(hit._source).to.eql({ 201 | name: 'Bob', 202 | numberArray: [1, 2, 3], 203 | falsy: 0, 204 | stringArray: ['az', 'er', 'ty'], 205 | obj: { 206 | a: 'azerty', 207 | b: 123, 208 | }, 209 | }); 210 | }); 211 | }); 212 | }); 213 | -------------------------------------------------------------------------------- /test/es7/idsOnly.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v7; 6 | 7 | describe('idsOnly', () => { 8 | utils.setup(); 9 | let UserModel; 10 | let john; 11 | let jane; 12 | let bob; 13 | 14 | beforeEach(() => { 15 | const UserSchema = new mongoose.Schema({ 16 | name: String, 17 | age: Number, 18 | }); 19 | 20 | UserSchema.plugin(plugin); 21 | 22 | UserModel = mongoose.model('User', UserSchema); 23 | 24 | john = new UserModel({ name: 'John', age: 35 }); 25 | jane = new UserModel({ name: 'Jane', age: 34 }); 26 | bob = new UserModel({ name: 'Bob', age: 36 }); 27 | 28 | return utils 29 | .deleteModelIndexes(UserModel) 30 | .then(() => { 31 | return UserModel.esCreateMapping(); 32 | }) 33 | .then(() => { 34 | return utils.Promise.all( 35 | [john, jane, bob].map(user => { 36 | return new utils.Promise(resolve => { 37 | user.on('es-indexed', resolve); 38 | user.save(); 39 | }); 40 | }) 41 | ); 42 | }) 43 | .then(() => { 44 | return UserModel.esRefresh(); 45 | }); 46 | }); 47 | 48 | it('should return ids', () => { 49 | return UserModel.esSearch( 50 | { 51 | query: { 52 | bool: { 53 | must: { match_all: {} }, 54 | filter: { range: { age: { gte: 35 } } }, 55 | }, 56 | }, 57 | sort: [{ age: { order: 'desc' } }], 58 | }, 59 | { idsOnly: true } 60 | ).then(ids => { 61 | expect(ids.length).to.eql(2); 62 | const idstrings = ids.map(id => { 63 | expect(id).to.be.an.instanceof(mongoose.Types.ObjectId); 64 | return id.toString(); 65 | }); 66 | expect(idstrings).to.eql([bob._id.toString(), john._id.toString()]); 67 | }); 68 | }); 69 | 70 | it('should an empty array', () => { 71 | return UserModel.esSearch( 72 | { 73 | query: { 74 | bool: { 75 | must: { match_all: {} }, 76 | filter: { range: { age: { gte: 100 } } }, 77 | }, 78 | }, 79 | sort: [{ age: { order: 'desc' } }], 80 | }, 81 | { idsOnly: true } 82 | ).then(ids => { 83 | expect(ids).to.eql([]); 84 | }); 85 | }); 86 | 87 | it('should return ids when defined in plugin', () => { 88 | utils.deleteMongooseModels(); 89 | 90 | const UserSchema = new mongoose.Schema({ 91 | name: String, 92 | age: Number, 93 | }); 94 | 95 | UserSchema.plugin(plugin, { idsOnly: true }); 96 | 97 | const UserModelIdsOnly = mongoose.model('User', UserSchema); 98 | 99 | return UserModelIdsOnly.esSearch({ 100 | query: { 101 | bool: { 102 | must: { match_all: {} }, 103 | filter: { range: { age: { gte: 35 } } }, 104 | }, 105 | }, 106 | sort: [{ age: { order: 'desc' } }], 107 | }).then(ids => { 108 | expect(ids.length).to.eql(2); 109 | const idstrings = ids.map(id => { 110 | expect(id).to.be.an.instanceof(mongoose.Types.ObjectId); 111 | return id.toString(); 112 | }); 113 | expect(idstrings).to.eql([bob._id.toString(), john._id.toString()]); 114 | }); 115 | }); 116 | 117 | it('should overwrite defined in plugin value', () => { 118 | utils.deleteMongooseModels(); 119 | 120 | const UserSchema = new mongoose.Schema({ 121 | name: String, 122 | age: Number, 123 | }); 124 | 125 | UserSchema.plugin(plugin, { idsOnly: true }); 126 | 127 | const UserModelIdsOnly = mongoose.model('User', UserSchema); 128 | 129 | return UserModelIdsOnly.esSearch( 130 | { 131 | query: { 132 | bool: { 133 | must: { match_all: {} }, 134 | filter: { range: { age: { gte: 35 } } }, 135 | }, 136 | }, 137 | sort: [{ age: { order: 'desc' } }], 138 | }, 139 | { idsOnly: false } 140 | ).then(result => { 141 | expect(result.hits.total.value).to.eql(2); 142 | let hit = result.hits.hits[0]; 143 | expect(hit._id).to.eql(bob._id.toString()); 144 | expect(hit._source).to.eql({ name: 'Bob', age: 36 }); 145 | 146 | hit = result.hits.hits[1]; 147 | expect(hit._id).to.eql(john._id.toString()); 148 | expect(hit._source).to.eql({ name: 'John', age: 35 }); 149 | }); 150 | }); 151 | }); 152 | -------------------------------------------------------------------------------- /test/es7/promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | 6 | const plugin = require('../../').v7; 7 | 8 | describe('promise', () => { 9 | utils.setup(); 10 | 11 | it('should return promise', () => { 12 | const UserSchema = new mongoose.Schema({ 13 | name: String, 14 | }); 15 | 16 | UserSchema.plugin(plugin); 17 | 18 | const UserModel = mongoose.model('User', UserSchema); 19 | const promise = UserModel.esCreateMapping(); 20 | expect(promise).to.be.an.instanceof(Promise); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/es7/withDiscriminators.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const utils = require('../utils'); 5 | const plugin = require('../../').v7; 6 | 7 | describe('with discriminators', () => { 8 | utils.setup(); 9 | 10 | it('check types and mappings', () => { 11 | const BaseSchema = new mongoose.Schema({ 12 | name: String, 13 | }); 14 | 15 | const UserSchema = new mongoose.Schema({ 16 | age: Number, 17 | }); 18 | 19 | const AdminSchema = new mongoose.Schema({ 20 | access: Boolean, 21 | }); 22 | 23 | BaseSchema.plugin(plugin, { 24 | index: 'user', 25 | type: kind => { 26 | if (kind === 'User') return 'userType'; 27 | if (kind === 'Admin') return 'adminType'; 28 | return 'otherType'; 29 | }, 30 | }); 31 | 32 | const BaseModel = mongoose.model('Base', BaseSchema); 33 | const UserModel = BaseModel.discriminator('User', UserSchema); 34 | const AdminModel = BaseModel.discriminator('Admin', AdminSchema); 35 | 36 | // check types and mappings on models 37 | const userOpts = UserModel.esOptions(); 38 | const adminOpts = AdminModel.esOptions(); 39 | expect(userOpts.type).to.equal('userType'); 40 | expect(adminOpts.type).to.equal('adminType'); 41 | expect(userOpts.mapping).to.deep.equal({ 42 | properties: { 43 | __t: { 44 | type: 'text', 45 | }, 46 | age: { 47 | type: 'long', 48 | }, 49 | name: { 50 | type: 'text', 51 | }, 52 | }, 53 | }); 54 | expect(adminOpts.mapping).to.deep.equal({ 55 | properties: { 56 | __t: { 57 | type: 'text', 58 | }, 59 | access: { 60 | type: 'boolean', 61 | }, 62 | name: { 63 | type: 'text', 64 | }, 65 | }, 66 | }); 67 | 68 | // check types and mappings on docs 69 | UserModel.create({ 70 | name: 'John', 71 | age: 34, 72 | }).then(doc => { 73 | /** 74 | * These pass but the Elastic7 log say: 75 | * {"type": "server", "timestamp": "2019-05-08T15:09:00,289+0000", "level": "DEBUG", "component": "o.e.a.b.TransportShardBulkAction", "cluster.name": "elasticsearch_xp", "node.name": "es01", "cluster.uuid": "_g29fS-XRXCCEnkZWlr66A", "node.id": "DhuxhmaORjq7rYNNCHh_fQ", "message": "[user][0] failed to execute bulk item (index) index {[user][userType][5cd2f10ce12850a22363f951], source[{\"age\":34,\"name\":\"John\",\"__t\":\"User\"}]}" , 76 | * "stacktrace": ["java.lang.IllegalArgumentException: Rejecting mapping update to [user] as the final mapping would have more than 1 type: [adminType, userType]", 77 | */ 78 | const opts = doc.esOptions(); 79 | expect(opts.type).to.equal('userType'); 80 | expect(opts.mapping).to.deep.equal({ 81 | properties: { 82 | __t: { 83 | type: 'text', 84 | }, 85 | age: { 86 | type: 'long', 87 | }, 88 | name: { 89 | type: 'text', 90 | }, 91 | }, 92 | }); 93 | }); 94 | 95 | AdminModel.create({ 96 | name: 'Steve', 97 | access: true, 98 | }).then(doc => { 99 | const opts = doc.esOptions(); 100 | expect(opts.type).to.equal('adminType'); 101 | expect(opts.mapping).to.deep.equal({ 102 | properties: { 103 | __t: { 104 | type: 'text', 105 | }, 106 | access: { 107 | type: 'boolean', 108 | }, 109 | name: { 110 | type: 'text', 111 | }, 112 | }, 113 | }); 114 | }); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /test/utils/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | 5 | mongoose.Promise = Promise; 6 | 7 | function array(mixed) { 8 | return Array.isArray(mixed) ? mixed : [mixed]; 9 | } 10 | 11 | function setup() { 12 | before(done => { 13 | global.expect = require('chai').expect; // eslint-disable-line 14 | mongoose.connect('mongodb://localhost/test', err => { 15 | if (err) { 16 | done(err); 17 | } else { 18 | done(); 19 | } 20 | }); 21 | }); 22 | 23 | beforeEach(() => { 24 | deleteMongooseModels(); 25 | }); 26 | 27 | after(done => { 28 | mongoose.disconnect(() => { 29 | done(); 30 | }); 31 | }); 32 | } 33 | 34 | function deleteModelIndexes(models) { 35 | return Promise.all( 36 | array(models).map(model => { 37 | return new Promise(resolve => { 38 | const options = model.esOptions(); 39 | const client = options.client; 40 | client.indices.delete({ index: options.index }, () => { 41 | resolve(); 42 | }); 43 | }); 44 | }) 45 | ).then(() => { 46 | // do nothing, just remove the results to allows to use .then(done) 47 | }); 48 | } 49 | 50 | function deleteMongooseModels() { 51 | Object.keys(mongoose.models).forEach(name => { 52 | delete mongoose.models[name]; 53 | delete mongoose.modelSchemas[name]; 54 | }); 55 | } 56 | 57 | module.exports = { 58 | Promise, 59 | setup, 60 | deleteModelIndexes, 61 | deleteMongooseModels, 62 | }; 63 | --------------------------------------------------------------------------------