Skip to content

Commit

Permalink
feat: search index data-service methods COMPASS-7185 (#4802)
Browse files Browse the repository at this point in the history
* WIP: search index data-service methods

* mocked unit tests for search indexes for now

* add @op

* async

* add chai-as-promised
  • Loading branch information
lerouxb authored Sep 7, 2023
1 parent a4d4807 commit b7d0093
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 1 deletion.
2 changes: 2 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions packages/data-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"@types/whatwg-url": "^8.2.1",
"bson": "^5.2.0",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"depcheck": "^1.4.1",
"eslint": "^7.25.0",
"kerberos": "^2.0.0",
Expand Down
168 changes: 167 additions & 1 deletion packages/data-service/src/data-service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import assert from 'assert';
import { ObjectId } from 'bson';
import { expect } from 'chai';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import type { Sort } from 'mongodb';
import { MongoServerError } from 'mongodb';
import { MongoClient } from 'mongodb';
import sinon from 'sinon';
import { v4 as uuid } from 'uuid';
Expand All @@ -18,6 +20,10 @@ import { AbortController } from '../test/mocks';
import { createClonedClient } from './connect-mongo-client';
import { runCommand } from './run-command';
import { mochaTestServer } from '@mongodb-js/compass-test-server';
import type { SearchIndex } from './search-index-detail-helper';

const { expect } = chai;
chai.use(chaiAsPromised);

const TEST_DOCS = [
{
Expand Down Expand Up @@ -1123,6 +1129,58 @@ describe('DataService', function () {
expect(stop.callCount).to.equal(1);
});
});

describe('#isListSearchIndexesSupported', function () {
it('returns false', async function () {
expect(
await dataService.isListSearchIndexesSupported(testNamespace)
).to.be.false;
});
});

describe('#getSearchIndexes', function () {
it('throws an error', async function () {
await expect(
dataService.getSearchIndexes(testNamespace)
).to.be.rejectedWith(
MongoServerError,
"Unrecognized pipeline stage name: '$listSearchIndexes'"
);
});
});

describe('#createSearchIndex', function () {
it('throws an error', async function () {
await expect(
dataService.createSearchIndex(testNamespace, 'my-index', {})
).to.be.rejectedWith(
MongoServerError,
"no such command: 'createSearchIndexes'"
);
});
});

describe('#updateSearchIndex', function () {
it('throws an error', async function () {
await expect(
dataService.updateSearchIndex(testNamespace, 'my-index', {})
).to.be.rejectedWith(
MongoServerError,
"no such command: 'updateSearchIndex'"
);
});
});

describe('#dropSearchIndex', function () {
it('throws an error', async function () {
await expect(
dataService.dropSearchIndex(testNamespace, 'my-index')
).to.be.rejectedWith(
MongoServerError,
"no such command: 'dropSearchIndex'"
);
});
});
});

context('with mocked client', function () {
Expand Down Expand Up @@ -1483,5 +1541,113 @@ describe('DataService', function () {
expect(dataService['_crudClient']).to.equal(fakeClonedClient);
});
});

describe('#isListSearchIndexesSupported', function () {
it('resolves to true if listSearchIndexes succeeds', async function () {
const searchIndexes: SearchIndex[] = [
{
id: '1',
name: 'a',
status: 'READY',
queryable: true,
latestDefinition: {},
},
{
id: '2',
name: 'b',
status: 'READY',
queryable: true,
latestDefinition: {},
},
];

const dataService: any = createDataServiceWithMockedClient({
searchIndexes: {
test: {
test: searchIndexes,
},
},
});
expect(
await dataService.isListSearchIndexesSupported('test.test')
).to.be.true;
});

it('resolves to false if listSearchIndexes fails', async function () {
const dataService: any = createDataServiceWithMockedClient({
searchIndexes: {
test: {
test: new Error('fake error'),
},
},
});
expect(
await dataService.isListSearchIndexesSupported('test.test')
).to.be.false;
});
});

describe('#getSearchIndexes', function () {
it('returns the search indexes', async function () {
const searchIndexes: SearchIndex[] = [
{
id: '1',
name: 'a',
status: 'READY',
queryable: true,
latestDefinition: {},
},
{
id: '2',
name: 'b',
status: 'READY',
queryable: true,
latestDefinition: {},
},
];

const dataService: any = createDataServiceWithMockedClient({
searchIndexes: {
test: {
test: searchIndexes,
},
},
});
expect(await dataService.getSearchIndexes('test.test')).to.deep.equal(
searchIndexes
);
});
});

describe('#createSearchIndex', function () {
it('creates a search index', async function () {
const dataService: any = createDataServiceWithMockedClient({});
expect(
await dataService.createSearchIndex('test.test', 'my-index', {
mappings: { dynamic: true },
})
).to.deep.equal('my-index');
});
});

describe('#updateSearchIndex', function () {
it('updates a search index', async function () {
const dataService: any = createDataServiceWithMockedClient({});
expect(
await dataService.updateSearchIndex('test.test', 'my-index', {
mappings: { dynamic: true },
})
).to.be.undefined;
});
});

describe('#dropSearchIndex', function () {
it('drops a search index', async function () {
const dataService: any = createDataServiceWithMockedClient({});
expect(
await dataService.dropSearchIndex('test.test', 'my-index')
).to.be.undefined;
});
});
});
});
66 changes: 66 additions & 0 deletions packages/data-service/src/data-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import type {
IndexInfo,
} from './index-detail-helper';
import { createIndexDefinition } from './index-detail-helper';
import type { SearchIndex } from './search-index-detail-helper';
import type {
BoundLogger,
DataServiceImplLogger,
Expand Down Expand Up @@ -424,6 +425,26 @@ export interface DataService {
*/
dropIndex(ns: string, name: string): Promise<Document>;

/*** SearchIndexes ***/

isListSearchIndexesSupported(ns: string): Promise<boolean>;

getSearchIndexes(ns: string): Promise<SearchIndex[]>;

createSearchIndex(
ns: string,
name: string,
definition: Document
): Promise<string>;

updateSearchIndex(
ns: string,
name: string,
definition: Document
): Promise<void>;

dropSearchIndex(ns: string, name: string): Promise<void>;

/*** Aggregation ***/

/**
Expand Down Expand Up @@ -1513,6 +1534,51 @@ class DataServiceImpl extends WithLogContext implements DataService {
return await coll.dropIndex(name);
}

@op(mongoLogId(1_001_000_237))
async isListSearchIndexesSupported(ns: string): Promise<boolean> {
try {
await this.getSearchIndexes(ns);
} catch (err) {
return false;
}
return true;
}

@op(mongoLogId(1_001_000_238))
async getSearchIndexes(ns: string): Promise<SearchIndex[]> {
const coll = this._collection(ns, 'CRUD');
const cursor = coll.listSearchIndexes();
const indexes = await cursor.toArray();
void cursor.close();
return indexes as SearchIndex[];
}

@op(mongoLogId(1_001_000_239))
async createSearchIndex(
ns: string,
name: string,
definition: Document
): Promise<string> {
const coll = this._collection(ns, 'CRUD');
return coll.createSearchIndex({ name, definition });
}

@op(mongoLogId(1_001_000_240))
async updateSearchIndex(
ns: string,
name: string,
definition: Document
): Promise<void> {
const coll = this._collection(ns, 'CRUD');
return coll.updateSearchIndex(name, definition);
}

@op(mongoLogId(1_001_000_241))
async dropSearchIndex(ns: string, name: string): Promise<void> {
const coll = this._collection(ns, 'CRUD');
return coll.dropSearchIndex(name);
}

@op(mongoLogId(1_001_000_041), ([ns, pipeline]) => {
return { ns, stages: pipeline.map((stage) => Object.keys(stage)[0]) };
})
Expand Down
1 change: 1 addition & 0 deletions packages/data-service/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export {
export type { ReauthenticationHandler } from './connect-mongo-client';
export type { ExplainExecuteOptions } from './data-service';
export type { IndexDefinition } from './index-detail-helper';
export type { SearchIndex } from './search-index-detail-helper';
9 changes: 9 additions & 0 deletions packages/data-service/src/search-index-detail-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { Document } from 'mongodb';

export type SearchIndex = {
id: string;
name: string;
status: 'BUILDING' | 'FAILED' | 'PENDING' | 'READY' | 'STALE';
queryable: boolean;
latestDefinition: Document;
};
32 changes: 32 additions & 0 deletions packages/data-service/test/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { MongoClient } from 'mongodb';
import { ConnectionString } from 'mongodb-connection-string-url';

import type { SearchIndex } from '../src/search-index-detail-helper';

export type ClientMockOptions = {
hosts: [{ host: string; port: number }];
commands: Partial<{
Expand All @@ -14,13 +16,15 @@ export type ClientMockOptions = {
collMod: unknown;
}>;
collections: Record<string, string[] | Error>;
searchIndexes: Record<string, Record<string, SearchIndex[] | Error>>;
clientOptions: Record<string, unknown>;
};

export function createMongoClientMock({
hosts = [{ host: 'localhost', port: 9999 }],
commands = {},
collections = {},
searchIndexes = {},
clientOptions = {},
}: Partial<ClientMockOptions> = {}): {
client: MongoClient;
Expand Down Expand Up @@ -85,6 +89,34 @@ export function createMongoClientMock({
},
};
},
collection(collectionName: string) {
return {
listSearchIndexes() {
return {
toArray() {
const indexes =
searchIndexes[databaseName][collectionName] ?? [];
if (indexes instanceof Error) {
return Promise.reject(indexes);
}
return Promise.resolve(indexes);
},
close() {
/* ignore */
},
};
},
createSearchIndex({ name }: { name: string }) {
return Promise.resolve(name);
},
updateSearchIndex() {
return Promise.resolve();
},
dropSearchIndex() {
return Promise.resolve();
},
};
},
};
},
options: {
Expand Down

0 comments on commit b7d0093

Please sign in to comment.