diff --git a/README.md b/README.md index c7ef6c3..5a54db6 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ module.exports = () => { binaryLocation: '', // optional clusterName: 'jest-search-local', nodeName: 'jest-search-local', + zincAdmin: 'admin', + zincPassword: 'Complexpass#123', indexes: [ { name: 'index-name', @@ -83,6 +85,11 @@ module.exports = () => { - indexes: specify the configuration like index name, and mapping of indexes that you want to create during the startup, and indexes will get deleted once test is finished: default: `[]` +- zincAdmin: zincsearch requires pass env `ZINC_FIRST_ADMIN_USER` when starting zincsearch, default: `admin`, + +- zincPassword: : zincsearch requires pass env `ZINC_FIRST_ADMIN_PASSWORD` when starting zincsearch, default: `Complexpass#123` + + **3. create `jest-global-setup.js`** @@ -136,4 +143,3 @@ beforeAll(async () => { ### Known issues 1. Windows is not on the support list yet, I didn't see the necessity of it yet, feel free to reach out if you have the needs to use it on Windows, then will prioritize it -2. ZincSearch is working in progress diff --git a/package-lock.json b/package-lock.json index 38ba7ef..aae705f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@geek-fun/jest-search", - "version": "0.0.9", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@geek-fun/jest-search", - "version": "0.0.9", + "version": "1.0.0", "license": "MIT", "dependencies": { "debug": "^4.3.4", diff --git a/package.json b/package.json index 9a77e46..b485a62 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@geek-fun/jest-search", - "version": "0.0.9", + "version": "1.0.0", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", "description": "Jest preset for running tests with local search platform", diff --git a/src/constants.ts b/src/constants.ts index 9518ef5..8dbe0bd 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -5,5 +5,5 @@ export const DISABLE_PROXY = { export const ARTIFACTS = { ES: 'https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch', OS: 'https://artifacts.opensearch.org/releases/bundle/opensearch', - ZINC: 'https://github.com/zinclabs/zinc/releases/download', + ZINC: 'https://github.com/zincsearch/zincsearch/releases/download', }; diff --git a/src/engine.ts b/src/engine.ts index 392c563..0445c18 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -7,13 +7,14 @@ import { execSync } from 'child_process'; import path from 'path'; import { debug } from './debug'; import { ARTIFACTS, DISABLE_PROXY } from './constants'; +import { downloadZinc, startZinc } from './zinc'; export enum EngineType { - ZINC = 'zinc', + ZINCSEARCH = 'zincsearch', ELASTICSEARCH = 'elasticsearch', OPENSEARCH = 'opensearch', } - +type IndexBody = { name: string; body?: unknown; mappings?: unknown }; export type EngineOptions = { engine: EngineType; version: string; @@ -21,13 +22,15 @@ export type EngineOptions = { clusterName: string; nodeName: string; port: number; - indexes: Array<{ name: string; body: unknown }>; + zincAdmin: string; + zincPassword: string; + indexes: Array; }; -type ConfiguredOptions = Omit & { binaryFilepath: string }; +export type ConfiguredOptions = Omit & { binaryFilepath: string }; let server: execa.ExecaChildProcess; let engineOptions: ConfiguredOptions; -// 'https://artifacts.opensearch.org/releases/core/opensearch/2.8.0/opensearch-min-2.8.0-linux-x64.tar.gz' + const getEngineResourceURL = async (engine: EngineType, version: string) => { const { sysName, arch } = await platform(); const engines: { @@ -39,8 +42,8 @@ const getEngineResourceURL = async (engine: EngineType, version: string) => { : `${ARTIFACTS.ES}-${version}.tar.gz`, [EngineType.OPENSEARCH]: () => `${ARTIFACTS.OS}/${version}/opensearch-${version}-linux-${arch.replace('86_', '')}.tar.gz`, - [EngineType.ZINC]: () => - `${ARTIFACTS.ZINC}/v${version}/zinc_${version}_${sysName}_${arch}.tar.gz`, + [EngineType.ZINCSEARCH]: () => + `${ARTIFACTS.ZINC}/v${version}/zincsearch_${version}_${sysName}_${arch}.tar.gz`, }; return engines[engine](); @@ -48,6 +51,10 @@ const getEngineResourceURL = async (engine: EngineType, version: string) => { const prepareEngine = async (engine: EngineType, version: string, binaryLocation: string) => { const url = await getEngineResourceURL(engine, version); const binaryFilepath = `${binaryLocation}/${engine}-${version}`; + if (engine === EngineType.ZINCSEARCH) { + await downloadZinc(url, binaryFilepath); + return binaryFilepath; + } debug(`checking if binary exists: ${binaryFilepath}`); if (!(await isFileExists(binaryFilepath))) { @@ -64,16 +71,20 @@ const createIndexes = async () => { const { indexes, port, engine } = engineOptions; const curlCommands: { - [engineType: string]: (indexItem: { name: string; body: unknown }) => string; + [engineType: string]: (indexItem: IndexBody) => string; } = { - [EngineType.ELASTICSEARCH]: ({ name, body }: { name: string; body: unknown }) => + [EngineType.ELASTICSEARCH]: ({ name, body }: IndexBody) => `curl -XPUT "http://localhost:${port}/${name}" -H "Content-Type: application/json" -d'${JSON.stringify( body )}'`, - [EngineType.OPENSEARCH]: ({ name, body }: { name: string; body: unknown }) => + [EngineType.OPENSEARCH]: ({ name, body }: IndexBody) => `curl -XPUT "http://localhost:${port}/${name}" -H "Content-Type: application/json" -d'${JSON.stringify( body )}'`, + [EngineType.ZINCSEARCH]: (index: IndexBody) => + `curl -XPUT "http://localhost:${port}/api/index" -u ${engineOptions.zincAdmin}:${ + engineOptions.zincPassword + } -H "Content-Type: application/json" -d'${JSON.stringify(index)}'`, }; debug('creating indexes'); await Promise.all( @@ -84,27 +95,26 @@ const createIndexes = async () => { const start = async () => { const { engine, version, binaryFilepath, clusterName, nodeName, port } = engineOptions; debug(`Starting ${engine} ${version}, ${binaryFilepath}`); - const startMatrix: { [key: string]: Array } = { - [EngineType.ELASTICSEARCH]: [ - '-p', - `${binaryFilepath}/server-pid`, - `-Ecluster.name=${clusterName}`, - `-Enode.name=${nodeName}`, - `-Ehttp.port=${port}`, - `-Expack.security.enabled=false`, - ], - [EngineType.OPENSEARCH]: [ - '-p', - `${binaryFilepath}/server-pid`, - `-Ecluster.name=${clusterName}`, - `-Enode.name=${nodeName}`, - `-Ehttp.port=${port}`, - `-Eplugins.security.disabled=true`, - ], - }; - server = execa(`${binaryFilepath}/bin/${engine}`, startMatrix[engine], { all: true }); + if (engine === EngineType.ZINCSEARCH) { + server = startZinc(engineOptions); + } else { + server = execa( + `${binaryFilepath}/bin/${engine}`, + [ + '-p', + `${binaryFilepath}/server-pid`, + `-Ecluster.name=${clusterName}`, + `-Enode.name=${nodeName}`, + `-Ehttp.port=${port}`, + engine === EngineType.OPENSEARCH + ? `-Eplugins.security.disabled=true` + : `-Expack.security.enabled=false`, + ], + { all: true } + ); + } - await waitForLocalhost(port); + await waitForLocalhost(engine, port); debug(`${engine} is running on port: ${port}, pid: ${server.pid}`); await createIndexes(); @@ -112,11 +122,13 @@ const start = async () => { }; const cleanupIndices = async (): Promise => { - const { port, indexes } = engineOptions; + const { engine, port, indexes, zincAdmin, zincPassword } = engineOptions; if (indexes.length <= 0) return; debug(' deleting indexes'); const result = execSync( - `curl -s -X DELETE http://localhost:${port}/${indexes.map(({ name }) => name).join(',')}`, + engine === EngineType.ZINCSEARCH + ? `curl -s -X DELETE http://localhost:${port}/api/index/* -u ${zincAdmin}:${zincPassword}` + : `curl -s -X DELETE http://localhost:${port}/${indexes.map(({ name }) => name).join(',')}`, DISABLE_PROXY ); @@ -156,9 +168,21 @@ export const startEngine = async ({ clusterName = 'jest-search-local', nodeName = 'jest-search-local', indexes = [], + zincAdmin = 'admin', + zincPassword = 'Complexpass#123', }: Partial = {}) => { const binaryFilepath = await prepareEngine(engine, version, binaryLocation); - engineOptions = { engine, version, port, clusterName, nodeName, binaryFilepath, indexes }; + engineOptions = { + engine, + version, + port, + clusterName, + nodeName, + binaryFilepath, + indexes, + zincAdmin, + zincPassword, + }; // start engine await start(); }; diff --git a/src/utils.ts b/src/utils.ts index baf8cdc..50265e4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,8 +4,9 @@ import execa from 'execa'; import { execSync } from 'child_process'; import { debug } from './debug'; import { DISABLE_PROXY } from './constants'; +import { EngineType } from './engine'; -export const waitForLocalhost = async (port: number, retries = 60) => { +export const waitForLocalhost = async (engine: EngineType, port: number, retries = 60) => { debug(`checking the local engine startup: ${retries}`); await new Promise((resolve) => setTimeout(() => resolve(0), 2000)); if (retries <= 0) { @@ -13,7 +14,9 @@ export const waitForLocalhost = async (port: number, retries = 60) => { } const response = execSync( - `curl -s -o /dev/null -i -w "%{http_code}" "http://localhost:${port}" || true`, + engine === EngineType.ZINCSEARCH + ? `curl -s -o /dev/null -i -w "%{http_code}" "http://localhost:${port}/es/" || true` + : `curl -s -o /dev/null -i -w "%{http_code}" "http://localhost:${port}" || true`, DISABLE_PROXY ); @@ -21,7 +24,7 @@ export const waitForLocalhost = async (port: number, retries = 60) => { debug(`curl response: ${statusCode}`); if (statusCode !== 200) { - await waitForLocalhost(port, retries - 1); + await waitForLocalhost(engine, port, retries - 1); } else { debug('engine started'); } diff --git a/src/zinc.ts b/src/zinc.ts new file mode 100644 index 0000000..eb3d228 --- /dev/null +++ b/src/zinc.ts @@ -0,0 +1,40 @@ +import execa from 'execa'; +import { ConfiguredOptions, EngineType } from './engine'; +import { debug } from './debug'; +import { isFileExists } from './utils'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import download from 'download-tarball'; + +export const startZinc = ({ + binaryFilepath, + engine, + port, + zincAdmin, + zincPassword, +}: Pick< + ConfiguredOptions, + 'binaryFilepath' | 'engine' | 'port' | 'zincAdmin' | 'zincPassword' +>) => { + return execa(`${binaryFilepath}/${engine}`, { + all: true, + env: { + ZINC_SERVER_PORT: `${port}`, + ZINC_FIRST_ADMIN_USER: zincAdmin, + ZINC_FIRST_ADMIN_PASSWORD: zincPassword, + ZINC_SHARD_NUM: '1', + ZINC_DATA_PATH: `${binaryFilepath}/data`, + }, + }); +}; + +export const downloadZinc = async (url: string, binaryFilepath: string) => { + debug(`checking if binary exists: ${binaryFilepath}`); + if (!(await isFileExists(binaryFilepath))) { + debug(`downloading binary, url: ${url}, path: ${binaryFilepath}`); + await download({ url, dir: binaryFilepath }); + debug(`Downloaded ${EngineType.ZINCSEARCH}`); + } else { + debug(`${EngineType.ZINCSEARCH} already downloaded`); + } +}; diff --git a/tests/engine.test.ts b/tests/engine.test.ts index 19fa460..c3f37f4 100644 --- a/tests/engine.test.ts +++ b/tests/engine.test.ts @@ -71,7 +71,16 @@ const engineMartix = [ nodeName: uuid(), indexes: indexes, }, - // { engine: EngineType.ZINC, version: '1.0.0', indexes: indexes }, + { + engine: EngineType.ZINCSEARCH, + version: '0.4.7', + port: 9200, + clusterName: 'N/A', + nodeName: 'N/A', + indexes: [{ ...indexes[0], mappings: indexes[0].body.mappings }], + zincAdmin: 'admin', + zincPassword: 'Complexpass#123', + }, ]; describe(`unit test for default config`, () => { @@ -118,37 +127,85 @@ describe(`unit test for default config`, () => { }); engineMartix.forEach((engineConfig) => { - const { engine, version, indexes, port, clusterName, nodeName } = engineConfig; + const { engine, version, indexes, port, clusterName, nodeName, zincAdmin, zincPassword } = + engineConfig; describe(`unit test for ${engine}-${version}:${port}`, () => { it(`should start ${engine}-${version} and create index`, async () => { await startEngine({ engine, version, port, clusterName, nodeName, indexes }); - const inspect = await execSync(`curl -s "http://localhost:${port}/?pretty"`, DISABLE_PROXY); + const inspect = await execSync( + engine === EngineType.ZINCSEARCH + ? `curl -s "http://localhost:${port}/es/"` + : `curl -s "http://localhost:${port}/?pretty"`, + DISABLE_PROXY + ); const mapping = await execSync( - `curl -s "http://localhost:${port}/books/_mapping?pretty"`, + engine === EngineType.ZINCSEARCH + ? `curl -s "http://localhost:${port}/api/${indexes[0].name}/_mapping" -u ${zincAdmin}:${zincPassword}` + : `curl -s "http://localhost:${port}/books/_mapping?pretty"`, DISABLE_PROXY ); await stopEngine(); expect(JSON.parse(inspect.toString())).toMatchObject({ - name: nodeName, + name: engine === EngineType.ZINCSEARCH ? 'zinc' : nodeName, cluster_name: clusterName, version: { number: version, - build_type: 'tar', build_snapshot: false, lucene_version: expect.any(String), - minimum_wire_compatibility_version: expect.any(String), - minimum_index_compatibility_version: expect.any(String), }, tagline: expect.any(String), }); - expect(JSON.parse(mapping.toString())).toEqual({ - books: { mappings: indexes[0].body.mappings }, - }); + if (engine === EngineType.ZINCSEARCH) { + expect(JSON.parse(mapping.toString())).toEqual({ + books: { + mappings: { + properties: { + '@timestamp': { + aggregatable: true, + highlightable: false, + index: true, + sortable: true, + store: false, + type: 'date', + }, + _id: { + aggregatable: true, + highlightable: false, + index: true, + sortable: true, + store: false, + type: 'keyword', + }, + author: { + aggregatable: true, + highlightable: false, + index: true, + sortable: true, + store: false, + type: 'keyword', + }, + name: { + aggregatable: false, + highlightable: false, + index: true, + sortable: false, + store: false, + type: 'text', + }, + }, + }, + }, + }); + } else { + expect(JSON.parse(mapping.toString())).toEqual({ + books: { mappings: indexes[0].body.mappings }, + }); + } }); }); });