diff --git a/.gitignore b/.gitignore index 1669a13..819dce1 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,6 @@ dist .tern-port .DS_Store + +scripts/elasticsearch +scripts/elasticsearch.tar.gz diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..becc004 --- /dev/null +++ b/.npmignore @@ -0,0 +1,72 @@ +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# coverage output +coverage.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +# mac files +.DS_Store + +# vim swap files +*.swp + +package-lock.json + +# elasticsearch repo or binary files +elasticsearch* + +# Generated typings, we don't commit them +# because we should copy them in the main .d.ts file +api/generated.d.ts + +# Ignore doc folder +docs + +# Ignore test folder +test + +# Ignore scripts folder +scripts + +# ci configuration +.ci +.travis.yml +certs +.github +CODE_OF_CONDUCT.md +CONTRIBUTING.md diff --git a/README.md b/README.md index ffe7417..0f19ff5 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,33 @@ mock.add({ method: ['GET', 'POST'], path: ['/_search', '/:index/_search'] }, () => { - return { status: 'ok' } + return { + hits: { + total: { value: 0, relation: 'eq' }, + hits: [] + } + } +}) +``` + +Finally, you can specify the API to mock with the `api` key, but be aware that if you specify the `api` instead of +`path` and `method`, you will not be able to differentiate between dynamic paths and API with multiple methods, +unless you use the parameters provided in the resolver function. + +**Note:** When using this mock pattern, only Elasticsearch v7 is supported. + +```js +// This mock will catch every search request against any index +mock.add({ + api: 'search' +}, params => { + console.log(params) + return { + hits: { + total: { value: 0, relation: 'eq' }, + hits: [] + } + } }) ``` @@ -105,12 +131,20 @@ const client = new Client({ A pattern is an object that describes an http query to Elasticsearch, and it looks like this: ```ts -interface MockPattern { - method: string - path: string +interface MockPatternHTTP { + method: string | string[] + path: string | string[] + querystring?: Record + body?: Record +} + +interface MockPatternAPI { + api: string querystring?: Record body?: Record } + +type MockPattern = MockPatternHTTP | MockPatternAPI ``` The more field you specify, the more the mock will be strict, for example: diff --git a/index.d.ts b/index.d.ts index 03c4e2d..130a33d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -7,17 +7,25 @@ import { Connection } from '@elastic/elasticsearch' declare class ClientMock { constructor() add(pattern: MockPattern, resolver: ResolverFn): ClientMock - get(pattern: MockPattern): ResolverFn | null + get(pattern: MockPatternHTTP): ResolverFn | null getConnection(): typeof Connection } -export declare type ResolverFn = (params: MockPattern) => Record | string +export declare type ResolverFn = (params: MockPatternHTTP) => Record | string -export interface MockPattern { +export interface MockPatternHTTP { method: string | string[] path: string | string[] querystring?: Record - body?: Record | Record[] + body?: Record } +export interface MockPatternAPI { + api: string + querystring?: Record + body?: Record +} + +export type MockPattern = MockPatternHTTP | MockPatternAPI + export default ClientMock \ No newline at end of file diff --git a/index.js b/index.js index 60ab4ca..682bdcc 100644 --- a/index.js +++ b/index.js @@ -10,7 +10,9 @@ const { Connection, errors } = require('@elastic/elasticsearch') const Router = require('find-my-way') const intoStream = require('into-stream') const equal = require('fast-deep-equal') + const kRouter = Symbol('elasticsearch-mock-router') +const pathsDb = require('./paths.json') /* istanbul ignore next */ const noop = () => {} @@ -28,6 +30,15 @@ class Mocker { } add (pattern, fn) { + if (pattern.api) { + const api = pathsDb[pattern.api] + if (!api) throw new ConfigurationError(`The api '${pattern.api}' does not exist`) + const apiPattern = { path: api.path, method: api.method } + if (pattern.body) apiPattern.body = pattern.body + if (pattern.querystring) apiPattern.querystring = pattern.querystring + return this.add(apiPattern, fn) + } + for (const key of ['method', 'path']) { if (Array.isArray(pattern[key])) { for (const value of pattern[key]) { diff --git a/index.test-d.ts b/index.test-d.ts index 4cda0b2..1176a13 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -4,7 +4,7 @@ import { expectType, expectError } from 'tsd' import { Client } from '@elastic/elasticsearch' -import Mock, { MockPattern } from './' +import Mock, { MockPatternHTTP } from './' const mock = new Mock() const client = new Client({ @@ -16,7 +16,7 @@ mock.add({ method: 'GET', path: '/' }, params => { - expectType(params) + expectType(params) return { status: 'ok' } }) @@ -24,7 +24,7 @@ mock.add({ method: ['GET', 'POST'], path: ['/_search', '/:index/_search'] }, params => { - expectType(params) + expectType(params) return { status: 'ok' } }) @@ -33,7 +33,7 @@ mock.add({ path: '/', querystring: { pretty: 'true' } }, params => { - expectType(params) + expectType(params) return { status: 'ok' } }) @@ -43,7 +43,7 @@ mock.add({ querystring: { pretty: 'true' }, body: { foo: 'bar' } }, params => { - expectType(params) + expectType(params) return { status: 'ok' } }) @@ -52,7 +52,7 @@ mock.add({ path: '/_bulk', body: [{ foo: 'bar' }] }, params => { - expectType(params) + expectType(params) return { status: 'ok' } }) @@ -60,7 +60,30 @@ mock.add({ method: 'GET', path: '/' }, params => { - expectType(params) + expectType(params) + return 'ok' +}) + +mock.add({ + api: 'info' +}, params => { + expectType(params) + return 'ok' +}) + +mock.add({ + api: 'info', + querystring: { pretty: 'true' } +}, params => { + expectType(params) + return 'ok' +}) + +mock.add({ + api: 'search', + body: { query: { match: { foo: 'bar' } } } +}, params => { + expectType(params) return 'ok' }) diff --git a/package.json b/package.json index ef6aac1..1b26c20 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,12 @@ "devDependencies": { "@elastic/elasticsearch": "^7.7.0-rc.2", "ava": "^3.6.0", + "gunzip-maybe": "^1.4.1", + "minimist": "^1.2.5", "nyc": "^15.0.1", + "simple-get": "^3.1.0", "standard": "^14.3.3", + "tar": "^6.0.1", "tsd": "^0.11.0" }, "dependencies": { diff --git a/paths.json b/paths.json new file mode 100644 index 0000000..c475e57 --- /dev/null +++ b/paths.json @@ -0,0 +1 @@ +{"bulk":{"path":["/_bulk","/:index/_bulk","/:index/:type/_bulk"],"method":["POST","PUT"]},"cat.aliases":{"path":["/_cat/aliases","/_cat/aliases/:name"],"method":["GET"]},"cat.allocation":{"path":["/_cat/allocation","/_cat/allocation/:node_id"],"method":["GET"]},"cat.count":{"path":["/_cat/count","/_cat/count/:index"],"method":["GET"]},"cat.fielddata":{"path":["/_cat/fielddata","/_cat/fielddata/:fields"],"method":["GET"]},"cat.health":{"path":["/_cat/health"],"method":["GET"]},"cat.help":{"path":["/_cat"],"method":["GET"]},"cat.indices":{"path":["/_cat/indices","/_cat/indices/:index"],"method":["GET"]},"cat.master":{"path":["/_cat/master"],"method":["GET"]},"cat.nodeattrs":{"path":["/_cat/nodeattrs"],"method":["GET"]},"cat.nodes":{"path":["/_cat/nodes"],"method":["GET"]},"cat.pending_tasks":{"path":["/_cat/pending_tasks"],"method":["GET"]},"cat.plugins":{"path":["/_cat/plugins"],"method":["GET"]},"cat.recovery":{"path":["/_cat/recovery","/_cat/recovery/:index"],"method":["GET"]},"cat.repositories":{"path":["/_cat/repositories"],"method":["GET"]},"cat.segments":{"path":["/_cat/segments","/_cat/segments/:index"],"method":["GET"]},"cat.shards":{"path":["/_cat/shards","/_cat/shards/:index"],"method":["GET"]},"cat.snapshots":{"path":["/_cat/snapshots","/_cat/snapshots/:repository"],"method":["GET"]},"cat.tasks":{"path":["/_cat/tasks"],"method":["GET"]},"cat.templates":{"path":["/_cat/templates","/_cat/templates/:name"],"method":["GET"]},"cat.thread_pool":{"path":["/_cat/thread_pool","/_cat/thread_pool/:thread_pool_patterns"],"method":["GET"]},"clear_scroll":{"path":["/_search/scroll","/_search/scroll/:scroll_id"],"method":["DELETE"]},"cluster.allocation_explain":{"path":["/_cluster/allocation/explain"],"method":["GET","POST"]},"cluster.delete_component_template":{"path":["/_component_template/:name"],"method":["DELETE"]},"cluster.exists_component_template":{"path":["/_component_template/:name"],"method":["HEAD"]},"cluster.get_component_template":{"path":["/_component_template","/_component_template/:name"],"method":["GET"]},"cluster.get_settings":{"path":["/_cluster/settings"],"method":["GET"]},"cluster.health":{"path":["/_cluster/health","/_cluster/health/:index"],"method":["GET"]},"cluster.pending_tasks":{"path":["/_cluster/pending_tasks"],"method":["GET"]},"cluster.put_component_template":{"path":["/_component_template/:name"],"method":["PUT","POST"]},"cluster.put_settings":{"path":["/_cluster/settings"],"method":["PUT"]},"cluster.remote_info":{"path":["/_remote/info"],"method":["GET"]},"cluster.reroute":{"path":["/_cluster/reroute"],"method":["POST"]},"cluster.state":{"path":["/_cluster/state","/_cluster/state/:metric","/_cluster/state/:metric/:index"],"method":["GET"]},"cluster.stats":{"path":["/_cluster/stats","/_cluster/stats/nodes/:node_id"],"method":["GET"]},"count":{"path":["/_count","/:index/_count"],"method":["POST","GET"]},"create":{"path":["/:index/_create/:id","/:index/:type/:id/_create"],"method":["PUT","POST"]},"delete":{"path":["/:index/_doc/:id","/:index/:type/:id"],"method":["DELETE"]},"delete_by_query":{"path":["/:index/_delete_by_query"],"method":["POST"]},"delete_by_query_rethrottle":{"path":["/_delete_by_query/:task_id/_rethrottle"],"method":["POST"]},"delete_script":{"path":["/_scripts/:id"],"method":["DELETE"]},"exists":{"path":["/:index/_doc/:id"],"method":["HEAD"]},"exists_source":{"path":["/:index/_source/:id","/:index/:type/:id/_source"],"method":["HEAD"]},"explain":{"path":["/:index/_explain/:id"],"method":["GET","POST"]},"field_caps":{"path":["/_field_caps","/:index/_field_caps"],"method":["GET","POST"]},"get":{"path":["/:index/_doc/:id"],"method":["GET"]},"get_script":{"path":["/_scripts/:id"],"method":["GET"]},"get_script_context":{"path":["/_script_context"],"method":["GET"]},"get_script_languages":{"path":["/_script_language"],"method":["GET"]},"get_source":{"path":["/:index/_source/:id"],"method":["GET"]},"index":{"path":["/:index/_doc/:id","/:index/_doc"],"method":["PUT","POST"]},"indices.analyze":{"path":["/_analyze","/:index/_analyze"],"method":["GET","POST"]},"indices.clear_cache":{"path":["/_cache/clear","/:index/_cache/clear"],"method":["POST"]},"indices.clone":{"path":["/:index/_clone/:target"],"method":["PUT","POST"]},"indices.close":{"path":["/:index/_close"],"method":["POST"]},"indices.create":{"path":["/:index"],"method":["PUT"]},"indices.create_data_stream":{"path":["/_data_stream/:name"],"method":["PUT"]},"indices.delete":{"path":["/:index"],"method":["DELETE"]},"indices.delete_alias":{"path":["/:index/_alias/:name","/:index/_aliases/:name"],"method":["DELETE"]},"indices.delete_data_stream":{"path":["/_data_stream/:name"],"method":["DELETE"]},"indices.delete_index_template":{"path":["/_index_template/:name"],"method":["DELETE"]},"indices.delete_template":{"path":["/_template/:name"],"method":["DELETE"]},"indices.exists":{"path":["/:index"],"method":["HEAD"]},"indices.exists_alias":{"path":["/_alias/:name","/:index/_alias/:name"],"method":["HEAD"]},"indices.exists_index_template":{"path":["/_index_template/:name"],"method":["HEAD"]},"indices.exists_template":{"path":["/_template/:name"],"method":["HEAD"]},"indices.exists_type":{"path":["/:index/_mapping/:type"],"method":["HEAD"]},"indices.flush":{"path":["/_flush","/:index/_flush"],"method":["POST","GET"]},"indices.forcemerge":{"path":["/_forcemerge","/:index/_forcemerge"],"method":["POST"]},"indices.get":{"path":["/:index"],"method":["GET"]},"indices.get_alias":{"path":["/_alias","/_alias/:name","/:index/_alias/:name","/:index/_alias"],"method":["GET"]},"indices.get_data_streams":{"path":["/_data_streams","/_data_streams/:name"],"method":["GET"]},"indices.get_field_mapping":{"path":["/_mapping/field/:fields","/:index/_mapping/field/:fields"],"method":["GET"]},"indices.get_index_template":{"path":["/_index_template","/_index_template/:name"],"method":["GET"]},"indices.get_mapping":{"path":["/_mapping","/:index/_mapping"],"method":["GET"]},"indices.get_settings":{"path":["/_settings","/:index/_settings","/:index/_settings/:name","/_settings/:name"],"method":["GET"]},"indices.get_template":{"path":["/_template","/_template/:name"],"method":["GET"]},"indices.get_upgrade":{"path":["/_upgrade","/:index/_upgrade"],"method":["GET"]},"indices.open":{"path":["/:index/_open"],"method":["POST"]},"indices.put_alias":{"path":["/:index/_alias/:name","/:index/_aliases/:name"],"method":["PUT","POST"]},"indices.put_index_template":{"path":["/_index_template/:name"],"method":["PUT","POST"]},"indices.put_mapping":{"path":["/:index/_mapping"],"method":["PUT","POST"]},"indices.put_settings":{"path":["/_settings","/:index/_settings"],"method":["PUT"]},"indices.put_template":{"path":["/_template/:name"],"method":["PUT","POST"]},"indices.recovery":{"path":["/_recovery","/:index/_recovery"],"method":["GET"]},"indices.refresh":{"path":["/_refresh","/:index/_refresh"],"method":["POST","GET"]},"indices.rollover":{"path":["/:alias/_rollover","/:alias/_rollover/:new_index"],"method":["POST"]},"indices.segments":{"path":["/_segments","/:index/_segments"],"method":["GET"]},"indices.shard_stores":{"path":["/_shard_stores","/:index/_shard_stores"],"method":["GET"]},"indices.shrink":{"path":["/:index/_shrink/:target"],"method":["PUT","POST"]},"indices.split":{"path":["/:index/_split/:target"],"method":["PUT","POST"]},"indices.stats":{"path":["/_stats","/_stats/:metric","/:index/_stats","/:index/_stats/:metric"],"method":["GET"]},"indices.update_aliases":{"path":["/_aliases"],"method":["POST"]},"indices.upgrade":{"path":["/_upgrade","/:index/_upgrade"],"method":["POST"]},"indices.validate_query":{"path":["/_validate/query","/:index/_validate/query","/:index/:type/_validate/query"],"method":["GET","POST"]},"info":{"path":["/"],"method":["GET"]},"ingest.delete_pipeline":{"path":["/_ingest/pipeline/:id"],"method":["DELETE"]},"ingest.get_pipeline":{"path":["/_ingest/pipeline","/_ingest/pipeline/:id"],"method":["GET"]},"ingest.processor_grok":{"path":["/_ingest/processor/grok"],"method":["GET"]},"ingest.put_pipeline":{"path":["/_ingest/pipeline/:id"],"method":["PUT"]},"ingest.simulate":{"path":["/_ingest/pipeline/_simulate","/_ingest/pipeline/:id/_simulate"],"method":["GET","POST"]},"mget":{"path":["/_mget","/:index/_mget"],"method":["GET","POST"]},"msearch":{"path":["/_msearch","/:index/_msearch"],"method":["GET","POST"]},"msearch_template":{"path":["/_msearch/template","/:index/_msearch/template"],"method":["GET","POST"]},"mtermvectors":{"path":["/_mtermvectors","/:index/_mtermvectors"],"method":["GET","POST"]},"nodes.hot_threads":{"path":["/_nodes/hot_threads","/_nodes/:node_id/hot_threads","/_cluster/nodes/hotthreads","/_cluster/nodes/:node_id/hotthreads","/_nodes/hotthreads","/_nodes/:node_id/hotthreads","/_cluster/nodes/hot_threads","/_cluster/nodes/:node_id/hot_threads"],"method":["GET"]},"nodes.info":{"path":["/_nodes","/_nodes/:node_id","/_nodes/:metric","/_nodes/:node_id/:metric"],"method":["GET"]},"nodes.reload_secure_settings":{"path":["/_nodes/reload_secure_settings","/_nodes/:node_id/reload_secure_settings"],"method":["POST"]},"nodes.stats":{"path":["/_nodes/stats","/_nodes/:node_id/stats","/_nodes/stats/:metric","/_nodes/:node_id/stats/:metric","/_nodes/stats/:metric/:index_metric","/_nodes/:node_id/stats/:metric/:index_metric"],"method":["GET"]},"nodes.usage":{"path":["/_nodes/usage","/_nodes/:node_id/usage","/_nodes/usage/:metric","/_nodes/:node_id/usage/:metric"],"method":["GET"]},"ping":{"path":["/"],"method":["HEAD"]},"put_script":{"path":["/_scripts/:id","/_scripts/:id/:context"],"method":["PUT","POST"]},"rank_eval":{"path":["/_rank_eval","/:index/_rank_eval"],"method":["GET","POST"]},"reindex":{"path":["/_reindex"],"method":["POST"]},"reindex_rethrottle":{"path":["/_reindex/:task_id/_rethrottle"],"method":["POST"]},"render_search_template":{"path":["/_render/template","/_render/template/:id"],"method":["GET","POST"]},"scripts_painless_execute":{"path":["/_scripts/painless/_execute"],"method":["GET","POST"]},"scroll":{"path":["/_search/scroll","/_search/scroll/:scroll_id"],"method":["GET","POST"]},"search":{"path":["/_search","/:index/_search"],"method":["GET","POST"]},"search_shards":{"path":["/_search_shards","/:index/_search_shards"],"method":["GET","POST"]},"search_template":{"path":["/_search/template","/:index/_search/template"],"method":["GET","POST"]},"snapshot.cleanup_repository":{"path":["/_snapshot/:repository/_cleanup"],"method":["POST"]},"snapshot.create":{"path":["/_snapshot/:repository/:snapshot"],"method":["PUT","POST"]},"snapshot.create_repository":{"path":["/_snapshot/:repository"],"method":["PUT","POST"]},"snapshot.delete":{"path":["/_snapshot/:repository/:snapshot"],"method":["DELETE"]},"snapshot.delete_repository":{"path":["/_snapshot/:repository"],"method":["DELETE"]},"snapshot.get":{"path":["/_snapshot/:repository/:snapshot"],"method":["GET"]},"snapshot.get_repository":{"path":["/_snapshot","/_snapshot/:repository"],"method":["GET"]},"snapshot.restore":{"path":["/_snapshot/:repository/:snapshot/_restore"],"method":["POST"]},"snapshot.status":{"path":["/_snapshot/_status","/_snapshot/:repository/_status","/_snapshot/:repository/:snapshot/_status"],"method":["GET"]},"snapshot.verify_repository":{"path":["/_snapshot/:repository/_verify"],"method":["POST"]},"tasks.cancel":{"path":["/_tasks/_cancel","/_tasks/:task_id/_cancel"],"method":["POST"]},"tasks.get":{"path":["/_tasks/:task_id"],"method":["GET"]},"tasks.list":{"path":["/_tasks"],"method":["GET"]},"termvectors":{"path":["/:index/_termvectors/:id","/:index/_termvectors"],"method":["GET","POST"]},"update":{"path":["/:index/_update/:id","/:index/:type/:id/_update"],"method":["POST"]},"update_by_query":{"path":["/:index/_update_by_query"],"method":["POST"]},"update_by_query_rethrottle":{"path":["/_update_by_query/:task_id/_rethrottle"],"method":["POST"]},"async_search.delete":{"path":["/_async_search/:id"],"method":["DELETE"]},"async_search.get":{"path":["/_async_search/:id"],"method":["GET"]},"async_search.submit":{"path":["/_async_search","/:index/_async_search"],"method":["POST"]},"autoscaling.delete_autoscaling_policy":{"path":["/_autoscaling/policy/:name"],"method":["DELETE"]},"autoscaling.get_autoscaling_decision":{"path":["/_autoscaling/decision"],"method":["GET"]},"autoscaling.get_autoscaling_policy":{"path":["/_autoscaling/policy/:name"],"method":["GET"]},"autoscaling.put_autoscaling_policy":{"path":["/_autoscaling/policy/:name"],"method":["PUT"]},"cat.ml_data_frame_analytics":{"path":["/_cat/ml/data_frame/analytics","/_cat/ml/data_frame/analytics/:id"],"method":["GET"]},"cat.ml_datafeeds":{"path":["/_cat/ml/datafeeds","/_cat/ml/datafeeds/:datafeed_id"],"method":["GET"]},"cat.ml_jobs":{"path":["/_cat/ml/anomaly_detectors","/_cat/ml/anomaly_detectors/:job_id"],"method":["GET"]},"cat.ml_trained_models":{"path":["/_cat/ml/trained_models","/_cat/ml/trained_models/:model_id"],"method":["GET"]},"cat.transforms":{"path":["/_cat/transforms","/_cat/transforms/:transform_id"],"method":["GET"]},"ccr.delete_auto_follow_pattern":{"path":["/_ccr/auto_follow/:name"],"method":["DELETE"]},"ccr.follow":{"path":["/:index/_ccr/follow"],"method":["PUT"]},"ccr.follow_info":{"path":["/:index/_ccr/info"],"method":["GET"]},"ccr.follow_stats":{"path":["/:index/_ccr/stats"],"method":["GET"]},"ccr.forget_follower":{"path":["/:index/_ccr/forget_follower"],"method":["POST"]},"ccr.get_auto_follow_pattern":{"path":["/_ccr/auto_follow","/_ccr/auto_follow/:name"],"method":["GET"]},"ccr.pause_auto_follow_pattern":{"path":["/_ccr/auto_follow/:name/pause"],"method":["POST"]},"ccr.pause_follow":{"path":["/:index/_ccr/pause_follow"],"method":["POST"]},"ccr.put_auto_follow_pattern":{"path":["/_ccr/auto_follow/:name"],"method":["PUT"]},"ccr.resume_auto_follow_pattern":{"path":["/_ccr/auto_follow/:name/resume"],"method":["POST"]},"ccr.resume_follow":{"path":["/:index/_ccr/resume_follow"],"method":["POST"]},"ccr.stats":{"path":["/_ccr/stats"],"method":["GET"]},"ccr.unfollow":{"path":["/:index/_ccr/unfollow"],"method":["POST"]},"data_frame_transform_deprecated.delete_transform":{"path":["/_data_frame/transforms/:transform_id"],"method":["DELETE"]},"data_frame_transform_deprecated.get_transform":{"path":["/_data_frame/transforms/:transform_id","/_data_frame/transforms"],"method":["GET"]},"data_frame_transform_deprecated.get_transform_stats":{"path":["/_data_frame/transforms/:transform_id/_stats"],"method":["GET"]},"data_frame_transform_deprecated.preview_transform":{"path":["/_data_frame/transforms/_preview"],"method":["POST"]},"data_frame_transform_deprecated.put_transform":{"path":["/_data_frame/transforms/:transform_id"],"method":["PUT"]},"data_frame_transform_deprecated.start_transform":{"path":["/_data_frame/transforms/:transform_id/_start"],"method":["POST"]},"data_frame_transform_deprecated.stop_transform":{"path":["/_data_frame/transforms/:transform_id/_stop"],"method":["POST"]},"data_frame_transform_deprecated.update_transform":{"path":["/_data_frame/transforms/:transform_id/_update"],"method":["POST"]},"enrich.delete_policy":{"path":["/_enrich/policy/:name"],"method":["DELETE"]},"enrich.execute_policy":{"path":["/_enrich/policy/:name/_execute"],"method":["PUT"]},"enrich.get_policy":{"path":["/_enrich/policy/:name","/_enrich/policy"],"method":["GET"]},"enrich.put_policy":{"path":["/_enrich/policy/:name"],"method":["PUT"]},"enrich.stats":{"path":["/_enrich/_stats"],"method":["GET"]},"eql.search":{"path":["/:index/_eql/search"],"method":["GET","POST"]},"graph.explore":{"path":["/:index/_graph/explore"],"method":["GET","POST"]},"ilm.delete_lifecycle":{"path":["/_ilm/policy/:policy"],"method":["DELETE"]},"ilm.explain_lifecycle":{"path":["/:index/_ilm/explain"],"method":["GET"]},"ilm.get_lifecycle":{"path":["/_ilm/policy/:policy","/_ilm/policy"],"method":["GET"]},"ilm.get_status":{"path":["/_ilm/status"],"method":["GET"]},"ilm.move_to_step":{"path":["/_ilm/move/:index"],"method":["POST"]},"ilm.put_lifecycle":{"path":["/_ilm/policy/:policy"],"method":["PUT"]},"ilm.remove_policy":{"path":["/:index/_ilm/remove"],"method":["POST"]},"ilm.retry":{"path":["/:index/_ilm/retry"],"method":["POST"]},"ilm.start":{"path":["/_ilm/start"],"method":["POST"]},"ilm.stop":{"path":["/_ilm/stop"],"method":["POST"]},"indices.freeze":{"path":["/:index/_freeze"],"method":["POST"]},"indices.reload_search_analyzers":{"path":["/:index/_reload_search_analyzers"],"method":["GET","POST"]},"indices.unfreeze":{"path":["/:index/_unfreeze"],"method":["POST"]},"license.delete":{"path":["/_license"],"method":["DELETE"]},"license.get":{"path":["/_license"],"method":["GET"]},"license.get_basic_status":{"path":["/_license/basic_status"],"method":["GET"]},"license.get_trial_status":{"path":["/_license/trial_status"],"method":["GET"]},"license.post":{"path":["/_license"],"method":["PUT","POST"]},"license.post_start_basic":{"path":["/_license/start_basic"],"method":["POST"]},"license.post_start_trial":{"path":["/_license/start_trial"],"method":["POST"]},"migration.deprecations":{"path":["/_migration/deprecations","/:index/_migration/deprecations"],"method":["GET"]},"ml.close_job":{"path":["/_ml/anomaly_detectors/:job_id/_close"],"method":["POST"]},"ml.delete_calendar":{"path":["/_ml/calendars/:calendar_id"],"method":["DELETE"]},"ml.delete_calendar_event":{"path":["/_ml/calendars/:calendar_id/events/:event_id"],"method":["DELETE"]},"ml.delete_calendar_job":{"path":["/_ml/calendars/:calendar_id/jobs/:job_id"],"method":["DELETE"]},"ml.delete_data_frame_analytics":{"path":["/_ml/data_frame/analytics/:id"],"method":["DELETE"]},"ml.delete_datafeed":{"path":["/_ml/datafeeds/:datafeed_id"],"method":["DELETE"]},"ml.delete_expired_data":{"path":["/_ml/_delete_expired_data"],"method":["DELETE"]},"ml.delete_filter":{"path":["/_ml/filters/:filter_id"],"method":["DELETE"]},"ml.delete_forecast":{"path":["/_ml/anomaly_detectors/:job_id/_forecast","/_ml/anomaly_detectors/:job_id/_forecast/:forecast_id"],"method":["DELETE"]},"ml.delete_job":{"path":["/_ml/anomaly_detectors/:job_id"],"method":["DELETE"]},"ml.delete_model_snapshot":{"path":["/_ml/anomaly_detectors/:job_id/model_snapshots/:snapshot_id"],"method":["DELETE"]},"ml.delete_trained_model":{"path":["/_ml/inference/:model_id"],"method":["DELETE"]},"ml.estimate_model_memory":{"path":["/_ml/anomaly_detectors/_estimate_model_memory"],"method":["POST"]},"ml.evaluate_data_frame":{"path":["/_ml/data_frame/_evaluate"],"method":["POST"]},"ml.explain_data_frame_analytics":{"path":["/_ml/data_frame/analytics/_explain","/_ml/data_frame/analytics/:id/_explain"],"method":["GET","POST"]},"ml.find_file_structure":{"path":["/_ml/find_file_structure"],"method":["POST"]},"ml.flush_job":{"path":["/_ml/anomaly_detectors/:job_id/_flush"],"method":["POST"]},"ml.forecast":{"path":["/_ml/anomaly_detectors/:job_id/_forecast"],"method":["POST"]},"ml.get_buckets":{"path":["/_ml/anomaly_detectors/:job_id/results/buckets/:timestamp","/_ml/anomaly_detectors/:job_id/results/buckets"],"method":["GET","POST"]},"ml.get_calendar_events":{"path":["/_ml/calendars/:calendar_id/events"],"method":["GET"]},"ml.get_calendars":{"path":["/_ml/calendars","/_ml/calendars/:calendar_id"],"method":["GET","POST"]},"ml.get_categories":{"path":["/_ml/anomaly_detectors/:job_id/results/categories/:category_id","/_ml/anomaly_detectors/:job_id/results/categories/"],"method":["GET","POST"]},"ml.get_data_frame_analytics":{"path":["/_ml/data_frame/analytics/:id","/_ml/data_frame/analytics"],"method":["GET"]},"ml.get_data_frame_analytics_stats":{"path":["/_ml/data_frame/analytics/_stats","/_ml/data_frame/analytics/:id/_stats"],"method":["GET"]},"ml.get_datafeed_stats":{"path":["/_ml/datafeeds/:datafeed_id/_stats","/_ml/datafeeds/_stats"],"method":["GET"]},"ml.get_datafeeds":{"path":["/_ml/datafeeds/:datafeed_id","/_ml/datafeeds"],"method":["GET"]},"ml.get_filters":{"path":["/_ml/filters","/_ml/filters/:filter_id"],"method":["GET"]},"ml.get_influencers":{"path":["/_ml/anomaly_detectors/:job_id/results/influencers"],"method":["GET","POST"]},"ml.get_job_stats":{"path":["/_ml/anomaly_detectors/_stats","/_ml/anomaly_detectors/:job_id/_stats"],"method":["GET"]},"ml.get_jobs":{"path":["/_ml/anomaly_detectors/:job_id","/_ml/anomaly_detectors"],"method":["GET"]},"ml.get_model_snapshots":{"path":["/_ml/anomaly_detectors/:job_id/model_snapshots/:snapshot_id","/_ml/anomaly_detectors/:job_id/model_snapshots"],"method":["GET","POST"]},"ml.get_overall_buckets":{"path":["/_ml/anomaly_detectors/:job_id/results/overall_buckets"],"method":["GET","POST"]},"ml.get_records":{"path":["/_ml/anomaly_detectors/:job_id/results/records"],"method":["GET","POST"]},"ml.get_trained_models":{"path":["/_ml/inference/:model_id","/_ml/inference"],"method":["GET"]},"ml.get_trained_models_stats":{"path":["/_ml/inference/:model_id/_stats","/_ml/inference/_stats"],"method":["GET"]},"ml.info":{"path":["/_ml/info"],"method":["GET"]},"ml.open_job":{"path":["/_ml/anomaly_detectors/:job_id/_open"],"method":["POST"]},"ml.post_calendar_events":{"path":["/_ml/calendars/:calendar_id/events"],"method":["POST"]},"ml.post_data":{"path":["/_ml/anomaly_detectors/:job_id/_data"],"method":["POST"]},"ml.preview_datafeed":{"path":["/_ml/datafeeds/:datafeed_id/_preview"],"method":["GET"]},"ml.put_calendar":{"path":["/_ml/calendars/:calendar_id"],"method":["PUT"]},"ml.put_calendar_job":{"path":["/_ml/calendars/:calendar_id/jobs/:job_id"],"method":["PUT"]},"ml.put_data_frame_analytics":{"path":["/_ml/data_frame/analytics/:id"],"method":["PUT"]},"ml.put_datafeed":{"path":["/_ml/datafeeds/:datafeed_id"],"method":["PUT"]},"ml.put_filter":{"path":["/_ml/filters/:filter_id"],"method":["PUT"]},"ml.put_job":{"path":["/_ml/anomaly_detectors/:job_id"],"method":["PUT"]},"ml.put_trained_model":{"path":["/_ml/inference/:model_id"],"method":["PUT"]},"ml.revert_model_snapshot":{"path":["/_ml/anomaly_detectors/:job_id/model_snapshots/:snapshot_id/_revert"],"method":["POST"]},"ml.set_upgrade_mode":{"path":["/_ml/set_upgrade_mode"],"method":["POST"]},"ml.start_data_frame_analytics":{"path":["/_ml/data_frame/analytics/:id/_start"],"method":["POST"]},"ml.start_datafeed":{"path":["/_ml/datafeeds/:datafeed_id/_start"],"method":["POST"]},"ml.stop_data_frame_analytics":{"path":["/_ml/data_frame/analytics/:id/_stop"],"method":["POST"]},"ml.stop_datafeed":{"path":["/_ml/datafeeds/:datafeed_id/_stop"],"method":["POST"]},"ml.update_datafeed":{"path":["/_ml/datafeeds/:datafeed_id/_update"],"method":["POST"]},"ml.update_filter":{"path":["/_ml/filters/:filter_id/_update"],"method":["POST"]},"ml.update_job":{"path":["/_ml/anomaly_detectors/:job_id/_update"],"method":["POST"]},"ml.update_model_snapshot":{"path":["/_ml/anomaly_detectors/:job_id/model_snapshots/:snapshot_id/_update"],"method":["POST"]},"ml.validate":{"path":["/_ml/anomaly_detectors/_validate"],"method":["POST"]},"ml.validate_detector":{"path":["/_ml/anomaly_detectors/_validate/detector"],"method":["POST"]},"monitoring.bulk":{"path":["/_monitoring/bulk","/_monitoring/:type/bulk"],"method":["POST","PUT"]},"rollup.delete_job":{"path":["/_rollup/job/:id"],"method":["DELETE"]},"rollup.get_jobs":{"path":["/_rollup/job/:id","/_rollup/job/"],"method":["GET"]},"rollup.get_rollup_caps":{"path":["/_rollup/data/:id","/_rollup/data/"],"method":["GET"]},"rollup.get_rollup_index_caps":{"path":["/:index/_rollup/data"],"method":["GET"]},"rollup.put_job":{"path":["/_rollup/job/:id"],"method":["PUT"]},"rollup.rollup_search":{"path":["/:index/_rollup_search","/:index/:type/_rollup_search"],"method":["GET","POST"]},"rollup.start_job":{"path":["/_rollup/job/:id/_start"],"method":["POST"]},"rollup.stop_job":{"path":["/_rollup/job/:id/_stop"],"method":["POST"]},"searchable_snapshots.clear_cache":{"path":["/_searchable_snapshots/cache/clear","/:index/_searchable_snapshots/cache/clear"],"method":["POST"]},"searchable_snapshots.mount":{"path":["/_snapshot/:repository/:snapshot/_mount"],"method":["POST"]},"searchable_snapshots.stats":{"path":["/_searchable_snapshots/stats","/:index/_searchable_snapshots/stats"],"method":["GET"]},"security.authenticate":{"path":["/_security/_authenticate"],"method":["GET"]},"security.change_password":{"path":["/_security/user/:username/_password","/_security/user/_password"],"method":["PUT","POST"]},"security.clear_cached_realms":{"path":["/_security/realm/:realms/_clear_cache"],"method":["POST"]},"security.clear_cached_roles":{"path":["/_security/role/:name/_clear_cache"],"method":["POST"]},"security.create_api_key":{"path":["/_security/api_key"],"method":["PUT","POST"]},"security.delete_privileges":{"path":["/_security/privilege/:application/:name"],"method":["DELETE"]},"security.delete_role":{"path":["/_security/role/:name"],"method":["DELETE"]},"security.delete_role_mapping":{"path":["/_security/role_mapping/:name"],"method":["DELETE"]},"security.delete_user":{"path":["/_security/user/:username"],"method":["DELETE"]},"security.disable_user":{"path":["/_security/user/:username/_disable"],"method":["PUT","POST"]},"security.enable_user":{"path":["/_security/user/:username/_enable"],"method":["PUT","POST"]},"security.get_api_key":{"path":["/_security/api_key"],"method":["GET"]},"security.get_builtin_privileges":{"path":["/_security/privilege/_builtin"],"method":["GET"]},"security.get_privileges":{"path":["/_security/privilege","/_security/privilege/:application","/_security/privilege/:application/:name"],"method":["GET"]},"security.get_role":{"path":["/_security/role/:name","/_security/role"],"method":["GET"]},"security.get_role_mapping":{"path":["/_security/role_mapping/:name","/_security/role_mapping"],"method":["GET"]},"security.get_token":{"path":["/_security/oauth2/token"],"method":["POST"]},"security.get_user":{"path":["/_security/user/:username","/_security/user"],"method":["GET"]},"security.get_user_privileges":{"path":["/_security/user/_privileges"],"method":["GET"]},"security.has_privileges":{"path":["/_security/user/_has_privileges","/_security/user/:user/_has_privileges"],"method":["GET","POST"]},"security.invalidate_api_key":{"path":["/_security/api_key"],"method":["DELETE"]},"security.invalidate_token":{"path":["/_security/oauth2/token"],"method":["DELETE"]},"security.put_privileges":{"path":["/_security/privilege/"],"method":["PUT","POST"]},"security.put_role":{"path":["/_security/role/:name"],"method":["PUT","POST"]},"security.put_role_mapping":{"path":["/_security/role_mapping/:name"],"method":["PUT","POST"]},"security.put_user":{"path":["/_security/user/:username"],"method":["PUT","POST"]},"slm.delete_lifecycle":{"path":["/_slm/policy/:policy_id"],"method":["DELETE"]},"slm.execute_lifecycle":{"path":["/_slm/policy/:policy_id/_execute"],"method":["PUT"]},"slm.execute_retention":{"path":["/_slm/_execute_retention"],"method":["POST"]},"slm.get_lifecycle":{"path":["/_slm/policy/:policy_id","/_slm/policy"],"method":["GET"]},"slm.get_stats":{"path":["/_slm/stats"],"method":["GET"]},"slm.get_status":{"path":["/_slm/status"],"method":["GET"]},"slm.put_lifecycle":{"path":["/_slm/policy/:policy_id"],"method":["PUT"]},"slm.start":{"path":["/_slm/start"],"method":["POST"]},"slm.stop":{"path":["/_slm/stop"],"method":["POST"]},"sql.clear_cursor":{"path":["/_sql/close"],"method":["POST"]},"sql.query":{"path":["/_sql"],"method":["POST","GET"]},"sql.translate":{"path":["/_sql/translate"],"method":["POST","GET"]},"ssl.certificates":{"path":["/_ssl/certificates"],"method":["GET"]},"transform.delete_transform":{"path":["/_transform/:transform_id"],"method":["DELETE"]},"transform.get_transform":{"path":["/_transform/:transform_id","/_transform"],"method":["GET"]},"transform.get_transform_stats":{"path":["/_transform/:transform_id/_stats"],"method":["GET"]},"transform.preview_transform":{"path":["/_transform/_preview"],"method":["POST"]},"transform.put_transform":{"path":["/_transform/:transform_id"],"method":["PUT"]},"transform.start_transform":{"path":["/_transform/:transform_id/_start"],"method":["POST"]},"transform.stop_transform":{"path":["/_transform/:transform_id/_stop"],"method":["POST"]},"transform.update_transform":{"path":["/_transform/:transform_id/_update"],"method":["POST"]},"watcher.ack_watch":{"path":["/_watcher/watch/:watch_id/_ack","/_watcher/watch/:watch_id/_ack/:action_id"],"method":["PUT","POST"]},"watcher.activate_watch":{"path":["/_watcher/watch/:watch_id/_activate"],"method":["PUT","POST"]},"watcher.deactivate_watch":{"path":["/_watcher/watch/:watch_id/_deactivate"],"method":["PUT","POST"]},"watcher.delete_watch":{"path":["/_watcher/watch/:id"],"method":["DELETE"]},"watcher.execute_watch":{"path":["/_watcher/watch/:id/_execute","/_watcher/watch/_execute"],"method":["PUT","POST"]},"watcher.get_watch":{"path":["/_watcher/watch/:id"],"method":["GET"]},"watcher.put_watch":{"path":["/_watcher/watch/:id"],"method":["PUT","POST"]},"watcher.start":{"path":["/_watcher/_start"],"method":["POST"]},"watcher.stats":{"path":["/_watcher/stats","/_watcher/stats/:metric"],"method":["GET"]},"watcher.stop":{"path":["/_watcher/_stop"],"method":["POST"]},"xpack.info":{"path":["/_xpack"],"method":["GET"]},"xpack.usage":{"path":["/_xpack/usage"],"method":["GET"]}} \ No newline at end of file diff --git a/scripts/generate.js b/scripts/generate.js new file mode 100644 index 0000000..2515962 --- /dev/null +++ b/scripts/generate.js @@ -0,0 +1,109 @@ +'use strict' + +const { + existsSync, + readdirSync, + renameSync, + createReadStream, + createWriteStream, + writeFileSync +} = require('fs') +const { pipeline } = require('stream') +const { join } = require('path') +const get = require('simple-get') +const minimist = require('minimist') +const tar = require('tar') +const gunzip = require('gunzip-maybe') + +const esFolder = join(__dirname, 'elasticsearch') +const apiFolder = join(esFolder, 'rest-api-spec', 'src', 'main', 'resources', 'rest-api-spec', 'api') +const xPackFolder = join(esFolder, 'x-pack', 'plugin', 'src', 'test', 'resources', 'rest-api-spec', 'api') + +async function generate (opts) { + if (!existsSync(esFolder)) { + await downloadElasticsearch(opts.tag || opts.branch) + await unTarElasticsearch() + } + + const oss = buildPaths(apiFolder) + const xpack = buildPaths(xPackFolder) + const pathsDb = { ...oss, ...xpack } + + writeFileSync( + join(__dirname, '..', 'paths.json'), + opts.pretty ? JSON.stringify(pathsDb, null, 2) : JSON.stringify(pathsDb), + 'utf8' + ) +} + +function buildPaths (folder) { + const pathsDb = {} + const folderContents = readdirSync(folder) + for (const file of folderContents) { + if (file === '_common.json') continue + const spec = require(join(folder, file)) + const api = Object.keys(spec)[0] + const { paths } = spec[api].url + pathsDb[api] = { + path: paths.map(p => formatPath(p.path)), + method: [...new Set(paths.flatMap(p => p.methods))] + } + } + return pathsDb +} + +function downloadElasticsearch (ref = 'master') { + console.log('starting download of Elasticsearch ..') + return new Promise((resolve, reject) => { + const opts = { + url: `https://api.github.com/repos/elastic/elasticsearch/tarball/${ref}`, + headers: { + 'user-agent': 'elastic/elasticsearch-js-mock' + } + } + get(opts, (err, res) => { + if (err) return reject(err) + if (res.statusCode >= 400) { + return reject(new Error(`Something went wrong: ${res.statusCode}`)) + } + const stream = createWriteStream(join(__dirname, 'elasticsearch.tar.gz')) + pipeline(res, stream, err => { + console.log('download of Elasticsearch completed.') + if (err) return reject(err) + resolve() + }) + }) + }) +} + +function unTarElasticsearch () { + console.log('starting untar of Elasticsearch ..') + return new Promise((resolve, reject) => { + const stream = createReadStream(join(__dirname, 'elasticsearch.tar.gz')) + pipeline(stream, gunzip(), tar.extract(), err => { + if (err) return reject(err) + for (const dir of readdirSync(join(__dirname, '..'))) { + if (dir.startsWith('elastic-elasticsearch-')) { + renameSync(dir, esFolder) + break + } + } + console.log('Elasticsearch completely unpacked.') + resolve() + }) + }) +} + +function formatPath (path) { + return path + .split('/') + .map(p => p.startsWith('{') ? `:${p.slice(1, -1)}` : p) + .join('/') +} + +if (require.main === module) { + generate(minimist(process.argv.slice(2), { + string: ['tag', 'branch'], + boolean: ['pretty'] + })).catch(console.log) +} diff --git a/test.js b/test.js index f4773b5..8ed4596 100644 --- a/test.js +++ b/test.js @@ -556,6 +556,14 @@ test('.add should throw if method and path are not defined', async t => { t.true(err instanceof errors.ConfigurationError) t.is(err.message, 'The resolver function is not defined') } + + try { + mock.add({ api: 'ifno' }) + t.fail('Should throw') + } catch (err) { + t.true(err instanceof errors.ConfigurationError) + t.is(err.message, "The api 'ifno' does not exist") + } }) test('Define multiple methods at once', async t => { @@ -664,3 +672,134 @@ test('Define multiple paths and method at once', async t => { t.deepEqual(response.body, { status: 'ok' }) t.is(response.statusCode, 200) }) + +test('High level mock', async t => { + const mock = new Mock() + const client = new Client({ + node: 'http://localhost:9200', + Connection: mock.getConnection() + }) + + mock.add({ + api: 'info' + }, () => { + return { status: 'ok' } + }) + + const response = await client.info() + t.deepEqual(response.body, { status: 'ok' }) + t.is(response.statusCode, 200) +}) + +test('High level mock with dynamic url', async t => { + const mock = new Mock() + const client = new Client({ + node: 'http://localhost:9200', + Connection: mock.getConnection() + }) + + mock.add({ + api: 'search' + }, () => { + return { + hits: { + total: { value: 1, relation: 'eq' }, + hits: [{ _source: { baz: 'faz' } }] + } + } + }) + + const response = await client.search({ + index: 'test', + body: { query: { match_all: {} } } + }) + + t.deepEqual(response.body, { + hits: { + total: { value: 1, relation: 'eq' }, + hits: [{ _source: { baz: 'faz' } }] + } + }) + t.is(response.statusCode, 200) +}) + +test('High level mock granularity', async t => { + const mock = new Mock() + const client = new Client({ + node: 'http://localhost:9200', + Connection: mock.getConnection() + }) + + mock.add({ + api: 'search' + }, () => { + return { + hits: { + total: { value: 1, relation: 'eq' }, + hits: [{ _source: { baz: 'faz' } }] + } + } + }) + + mock.add({ + api: 'search', + body: { query: { match: { foo: 'bar' } } } + }, () => { + return { + hits: { + total: { value: 0, relation: 'eq' }, + hits: [] + } + } + }) + + let response = await client.search({ + index: 'test', + body: { query: { match_all: {} } } + }) + + t.deepEqual(response.body, { + hits: { + total: { value: 1, relation: 'eq' }, + hits: [{ _source: { baz: 'faz' } }] + } + }) + + response = await client.search({ + index: 'test', + body: { query: { match: { foo: 'bar' } } } + }) + + t.deepEqual(response.body, { + hits: { + total: { value: 0, relation: 'eq' }, + hits: [] + } + }) +}) + +test('High level mock, discriminate by querystring', async t => { + const mock = new Mock() + const client = new Client({ + node: 'http://localhost:9200', + Connection: mock.getConnection() + }) + + mock.add({ + api: 'info', + querystring: { pretty: 'true' } + }, () => { + return { status: 'not ok' } + }) + + mock.add({ + api: 'info', + querystring: { human: 'true' } + }, () => { + return { status: 'ok' } + }) + + const response = await client.info({ human: true }) + t.deepEqual(response.body, { status: 'ok' }) + t.is(response.statusCode, 200) +})