Skip to content

Commit

Permalink
implement create index & drop index
Browse files Browse the repository at this point in the history
  • Loading branch information
Blankll committed Jul 11, 2023
1 parent 5572ab0 commit 3bfa5cd
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 65 deletions.
61 changes: 60 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,67 @@

Jest preset for running tests with local ElasticSearch, OpenSearch and ZincSearch.

## Usage
**Prerequisite:**
ElasticSearch and OpenSearch relies on Java, please make sure you have Java installed and `JAVA_HOME` is set.

install
**1. install library**
```bash
npm install --save-dev @geek-fun/jest-search
```

**2. create config file `jest-search-config.js`**
```javascript
module.exports = () => {
return {
engine: 'elasticearch', // or 'opensearch' or 'zincsearch'
version: '8.8.2',
port: 9200,
binaryLocation: '', // optional
clusterName: 'jest-search-local',
nodeName: 'jest-search-local',
indexes: [
{
name: 'index-name',
body: {
settings: {
number_of_shards: '1',
number_of_replicas: '1'
},
aliases: {
'your-alias': {}
},
mappings: {
dynamic: false,
properties: {
id: {
type: 'keyword'
}
}
}
}
}
]
};
};
```

**3. modify the `jest-config.js`**
```javascript
module.exports = {
preset: '@geek-fun/jest-search',
};
```

**3. play with your test**
```typescript
// tests/utils/helper.ts sample utils to add item for test
export const saveBook = async (bookDoc: { name: string; author: string }) => {
await esClient.index({ index, body: bookDoc, refresh: true });
};

// tests/book.test.ts sample test
beforeAll(async () => {
await saveBook(mockBook);
});
```
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@geek-fun/jest-search",
"version": "0.0.2",
"version": "0.0.3",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"description": "Jest preset for running tests with local search platform",
Expand Down
8 changes: 8 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const DISABLE_PROXY = {
env: { http_proxy: undefined, https_proxy: undefined, all_proxy: undefined },
};
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',
};
96 changes: 57 additions & 39 deletions src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import download from 'download-tarball';
import { execSync } from 'child_process';
import path from 'path';
import { debug } from './debug';
import { ARTIFACTS, DISABLE_PROXY } from './constants';

export enum EngineType {
ZINC = 'zinc',
Expand All @@ -14,37 +15,32 @@ export enum EngineType {
}

export type EngineOptions = {
engine?: EngineType;
version?: string;
binaryLocation?: string;
clusterName?: string;
nodeName?: string;
port?: number;
indexes?: Array<string>;
};

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',
engine: EngineType;
version: string;
binaryLocation: string;
clusterName: string;
nodeName: string;
port: number;
indexes: Array<{ name: string; body: unknown }>;
};
type ConfiguredOptions = Omit<EngineOptions, 'binaryLocation'> & { binaryFilepath: string };

let server: execa.ExecaChildProcess;
let engineOptions: EngineOptions;
let engineOptions: ConfiguredOptions;
const getEngineResourceURL = async (engine: EngineType, version: string) => {
const { sysName, arch } = await platform();
const engines: {
[engineType: string]: () => string;
} = {
[EngineType.ELASTICSEARCH]: () =>
parseInt(version.charAt(0)) >= 7
? `${artifacts.ES}-${version}-${sysName}-${arch}.tar.gz`
: `${artifacts.ES}-${version}.tar.gz`,
? `${ARTIFACTS.ES}-${version}-${sysName}-${arch}.tar.gz`
: `${ARTIFACTS.ES}-${version}.tar.gz`,

[EngineType.OPENSEARCH]: () =>
`${artifacts.OS}/${version}/opensearch-${version}-${sysName}-${arch.slice(1, 4)}.tar.gz`,
`${ARTIFACTS.OS}/${version}/opensearch-${version}-${sysName}-${arch.slice(1, 4)}.tar.gz`,
[EngineType.ZINC]: () =>
`${artifacts.ZINC}/v${version}/zinc_${version}_${sysName}_${arch}.tar.gz`,
`${ARTIFACTS.ZINC}/v${version}/zinc_${version}_${sysName}_${arch}.tar.gz`,
};

return engines[engine]();
Expand All @@ -64,12 +60,29 @@ const prepareEngine = async (engine: EngineType, version: string, binaryLocation

return binaryFilepath;
};
const createIndexes = async () => {
const { indexes = [], port, engine } = engineOptions;

const curlCommands: {
[engineType: string]: (indexItem: { name: string; body: unknown }) => string;
} = {
[EngineType.ELASTICSEARCH]: ({ name, body }: { name: string; body: unknown }) =>
`curl -XPUT "http://localhost:${port}/${name}" -H "Content-Type: application/json" -d'${JSON.stringify(
body
)}'`,
[EngineType.OPENSEARCH]: ({ name, body }: { name: string; body: unknown }) =>
`curl -XPUT "http://localhost:${port}/${name}" -H "Content-Type: application/json" -d'${JSON.stringify(
body
)}'`,
};
debug('creating indexes');
await Promise.all(
indexes.map(async (index) => await execSync(curlCommands[engine](index), DISABLE_PROXY))
);
};

const start = async (
options: Omit<EngineOptions, 'binaryLocation'> & { binaryFilepath: string }
) => {
engineOptions = options;
const { engine, version, binaryFilepath, clusterName, nodeName, port = 9200 } = options;
const start = async () => {
const { engine, version, binaryFilepath, clusterName, nodeName, port } = engineOptions;
debug(`Starting ${engine} ${version}, ${binaryFilepath}`);
const esExecArgs = [
'-p',
Expand All @@ -84,24 +97,27 @@ const start = async (

await waitForLocalhost(port);
debug(`${engine} is running on port: ${port}, pid: ${server.pid}`);
};

const cleanupIndices = (options: { indexes?: Array<string> }): void => {
const indexes = options.indexes?.join(',');
await createIndexes();

if (indexes) {
const result = execSync(`curl -s -X DELETE http://localhost/${indexes}`, {
env: { http_proxy: undefined, https_proxy: undefined, all_proxy: undefined },
});
debug(`indexes created`);
};

const error = getError(result);
const cleanupIndices = async (): Promise<void> => {
const { port, indexes } = engineOptions;
if (indexes.length <= 0) return;
debug(' deleting indexes');
const result = execSync(
`curl -s -X DELETE http://localhost:${port}/${indexes.map(({ name }) => name).join(',')}`,
DISABLE_PROXY
);

if (error) {
throw new Error(`Failed to remove index: ${error.reason}`);
}
const error = getError(result);

debug('Removed all indexes');
if (error) {
throw new Error(`Failed to remove index: ${error.reason}`);
}

debug('Removed all indexes');
};

const killProcess = async (): Promise<void> => {
Expand All @@ -125,14 +141,16 @@ export const startEngine = async ({
binaryLocation = path.resolve(__dirname + '/../') + '/node_modules/.cache/jest-search',
clusterName = 'jest-search-local',
nodeName = 'jest-search-local',
}: EngineOptions = {}) => {
indexes = [],
}: Partial<EngineOptions> = {}) => {
const binaryFilepath = await prepareEngine(engine, version, binaryLocation);
engineOptions = { engine, version, port, clusterName, nodeName, binaryFilepath, indexes };
// start engine
await start({ engine, version, port, clusterName, nodeName, binaryFilepath });
await start();
};

export const stopEngine = async (): Promise<void> => {
cleanupIndices({ indexes: engineOptions.indexes });
await cleanupIndices();
await killProcess();

debug('ES has been stopped');
Expand Down
8 changes: 7 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { startEngine, stopEngine, EngineType, EngineOptions } from './engine';
import path from 'path';

const globalSetup = startEngine;
const globalSetup = async () => {
const configPath =
process.env.JEST_ELASTICSEARCH_CONFIG || path.resolve(__dirname + '/../jest-search-config.js');
const config = (await import(configPath))();
await startEngine(config);
};
const globalTeardown = stopEngine;

export { globalSetup, globalTeardown, EngineType, EngineOptions };
3 changes: 2 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { promisify } from 'util';
import execa from 'execa';
import { execSync } from 'child_process';
import { debug } from './debug';
import { DISABLE_PROXY } from './constants';

export const waitForLocalhost = async (port: number, retries = 60) => {
debug(`checking the local engine startup: ${retries}`);
Expand All @@ -13,7 +14,7 @@ export const waitForLocalhost = async (port: number, retries = 60) => {

const response = execSync(
'curl -s -o /dev/null -i -w "%{http_code}" "http://localhost:9200" || true',
{ env: { http_proxy: undefined, https_proxy: undefined, all_proxy: undefined } }
DISABLE_PROXY
);

const statusCode = parseInt(response.toString('utf-8'), 10);
Expand Down
74 changes: 56 additions & 18 deletions tests/engine.test.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,66 @@
import { startEngine, stopEngine } from '../src/engine';
import { execSync } from 'child_process';
import { DISABLE_PROXY } from '../src/constants';
const indexes = [
{
name: 'books',
body: {
settings: {
number_of_shards: '1',
number_of_replicas: '1',
},
mappings: {
properties: {
name: {
type: 'text',
},
author: {
type: 'keyword',
},
},
},
},
},
];

jest.setTimeout(10 * 60 * 1000);
describe('unit test for elasticearch', () => {
it('should start engine with default config', async () => {
await startEngine();
const response = await execSync('curl -s "http://localhost:9200/?pretty"', {
env: { http_proxy: undefined, https_proxy: undefined, all_proxy: undefined },
});

await stopEngine();

it('should start engine with default config', async () => {
await startEngine();
const response = await execSync('curl -s "http://localhost:9200/?pretty"', {
env: { http_proxy: undefined, https_proxy: undefined, all_proxy: undefined },
expect(JSON.parse(response.toString())).toMatchObject({
name: 'jest-search-local',
cluster_name: 'jest-search-local',
version: {
number: '8.8.2',
build_flavor: 'default',
build_type: 'tar',
build_snapshot: false,
lucene_version: '9.6.0',
minimum_wire_compatibility_version: '7.17.0',
minimum_index_compatibility_version: '7.0.0',
},
tagline: 'You Know, for Search',
});
});

await stopEngine();
it('should start engine and create index when passing indexes', async () => {
await startEngine({ indexes: [indexes[0]] });

expect(JSON.parse(response.toString())).toMatchObject({
name: 'jest-search-local',
cluster_name: 'jest-search-local',
version: {
number: '8.8.2',
build_flavor: 'default',
build_type: 'tar',
build_snapshot: false,
lucene_version: '9.6.0',
minimum_wire_compatibility_version: '7.17.0',
minimum_index_compatibility_version: '7.0.0',
},
tagline: 'You Know, for Search',
const response = await execSync(
'curl -s "http://localhost:9200/books/_mapping?pretty"',
DISABLE_PROXY
);

await stopEngine();

expect(JSON.parse(response.toString())).toEqual({
books: { mappings: { properties: { author: { type: 'keyword' }, name: { type: 'text' } } } },
});
});
});

0 comments on commit 3bfa5cd

Please sign in to comment.