diff --git a/src/common/session.ts b/src/common/session.ts index 3c702a645..4ec536f4e 100644 --- a/src/common/session.ts +++ b/src/common/session.ts @@ -153,4 +153,17 @@ export class Session extends EventEmitter { get connectedAtlasCluster(): AtlasClusterConnectionInfo | undefined { return this.connectionManager.currentConnectionState.connectedAtlasCluster; } + + async isSearchIndexSupported(): Promise { + try { + const dummyDatabase = `search-index-test-db-${Date.now()}`; + const dummyCollection = `search-index-test-coll-${Date.now()}`; + // If a cluster supports search indexes, the call below will succeed + // with a cursor otherwise will throw an Error + await this.serviceProvider.getSearchIndexes(dummyDatabase, dummyCollection); + return true; + } catch { + return false; + } + } } diff --git a/src/resources/common/debug.ts b/src/resources/common/debug.ts index ad1f383df..f76030b5a 100644 --- a/src/resources/common/debug.ts +++ b/src/resources/common/debug.ts @@ -56,13 +56,15 @@ export class DebugResource extends ReactiveResource< } } - toOutput(): string { + async toOutput(): Promise { let result = ""; switch (this.current.tag) { - case "connected": - result += "The user is connected to the MongoDB cluster."; + case "connected": { + const searchIndexesSupported = await this.session.isSearchIndexSupported(); + result += `The user is connected to the MongoDB cluster${searchIndexesSupported ? " with support for search indexes" : " without any support for search indexes"}.`; break; + } case "errored": result += `The user is not connected to a MongoDB cluster because of an error.\n`; if (this.current.connectedAtlasCluster) { diff --git a/src/resources/resource.ts b/src/resources/resource.ts index cf265a490..a9cb702ac 100644 --- a/src/resources/resource.ts +++ b/src/resources/resource.ts @@ -73,10 +73,10 @@ export abstract class ReactiveResource ({ + private resourceCallback: ReadResourceCallback = async (uri) => ({ contents: [ { - text: this.toOutput(), + text: await this.toOutput(), mimeType: "application/json", uri: uri.href, }, @@ -101,5 +101,5 @@ export abstract class ReactiveResource[]): Value; - public abstract toOutput(): string; + public abstract toOutput(): string | Promise; } diff --git a/tests/unit/common/session.test.ts b/tests/unit/common/session.test.ts index 9402df246..ea6ac348b 100644 --- a/tests/unit/common/session.test.ts +++ b/tests/unit/common/session.test.ts @@ -1,4 +1,4 @@ -import type { Mocked } from "vitest"; +import type { Mocked, MockedFunction } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver"; import { Session } from "../../../src/common/session.js"; @@ -119,4 +119,30 @@ describe("Session", () => { expect(connectionString).toContain("--test-device-id--unknown"); }); }); + + describe("isSearchIndexSupported", () => { + let getSearchIndexesMock: MockedFunction<() => unknown>; + beforeEach(() => { + getSearchIndexesMock = vi.fn(); + MockNodeDriverServiceProvider.connect = vi.fn().mockResolvedValue({ + getSearchIndexes: getSearchIndexesMock, + } as unknown as NodeDriverServiceProvider); + }); + + it("should return true if listing search indexes succeed", async () => { + getSearchIndexesMock.mockResolvedValue([]); + await session.connectToMongoDB({ + connectionString: "mongodb://localhost:27017", + }); + expect(await session.isSearchIndexSupported()).toEqual(true); + }); + + it("should return false if listing search indexes fail with search error", async () => { + getSearchIndexesMock.mockRejectedValue(new Error("SearchNotEnabled")); + await session.connectToMongoDB({ + connectionString: "mongodb://localhost:27017", + }); + expect(await session.isSearchIndexSupported()).toEqual(false); + }); + }); }); diff --git a/tests/unit/resources/common/debug.test.ts b/tests/unit/resources/common/debug.test.ts index f031fd218..3a4c68e27 100644 --- a/tests/unit/resources/common/debug.test.ts +++ b/tests/unit/resources/common/debug.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import { DebugResource } from "../../../../src/resources/common/debug.js"; import { Session } from "../../../../src/common/session.js"; import { Telemetry } from "../../../../src/telemetry/telemetry.js"; @@ -13,13 +13,15 @@ import { Keychain } from "../../../../src/common/keychain.js"; describe("debug resource", () => { const logger = new CompositeLogger(); const deviceId = DeviceId.create(logger); - const session = new Session({ - apiBaseUrl: "", - logger, - exportsManager: ExportsManager.init(config, logger), - connectionManager: new MCPConnectionManager(config, driverOptions, logger, deviceId), - keychain: new Keychain(), - }); + const session = vi.mocked( + new Session({ + apiBaseUrl: "", + logger, + exportsManager: ExportsManager.init(config, logger), + connectionManager: new MCPConnectionManager(config, driverOptions, logger, deviceId), + keychain: new Keychain(), + }) + ); const telemetry = Telemetry.create(session, { ...config, telemetry: "disabled" }, deviceId); let debugResource: DebugResource = new DebugResource(session, config, telemetry); @@ -28,54 +30,56 @@ describe("debug resource", () => { debugResource = new DebugResource(session, config, telemetry); }); - it("should be connected when a connected event happens", () => { + it("should be connected when a connected event happens", async () => { debugResource.reduceApply("connect", undefined); - const output = debugResource.toOutput(); + const output = await debugResource.toOutput(); - expect(output).toContain(`The user is connected to the MongoDB cluster.`); + expect(output).toContain( + `The user is connected to the MongoDB cluster without any support for search indexes.` + ); }); - it("should be disconnected when a disconnect event happens", () => { + it("should be disconnected when a disconnect event happens", async () => { debugResource.reduceApply("disconnect", undefined); - const output = debugResource.toOutput(); + const output = await debugResource.toOutput(); expect(output).toContain(`The user is not connected to a MongoDB cluster.`); }); - it("should be disconnected when a close event happens", () => { + it("should be disconnected when a close event happens", async () => { debugResource.reduceApply("close", undefined); - const output = debugResource.toOutput(); + const output = await debugResource.toOutput(); expect(output).toContain(`The user is not connected to a MongoDB cluster.`); }); - it("should be disconnected and contain an error when an error event occurred", () => { + it("should be disconnected and contain an error when an error event occurred", async () => { debugResource.reduceApply("connection-error", { tag: "errored", errorReason: "Error message from the server", }); - const output = debugResource.toOutput(); + const output = await debugResource.toOutput(); expect(output).toContain(`The user is not connected to a MongoDB cluster because of an error.`); expect(output).toContain(`Error message from the server`); }); - it("should show the inferred authentication type", () => { + it("should show the inferred authentication type", async () => { debugResource.reduceApply("connection-error", { tag: "errored", connectionStringAuthType: "scram", errorReason: "Error message from the server", }); - const output = debugResource.toOutput(); + const output = await debugResource.toOutput(); expect(output).toContain(`The user is not connected to a MongoDB cluster because of an error.`); expect(output).toContain(`The inferred authentication mechanism is "scram".`); expect(output).toContain(`Error message from the server`); }); - it("should show the atlas cluster information when provided", () => { + it("should show the atlas cluster information when provided", async () => { debugResource.reduceApply("connection-error", { tag: "errored", connectionStringAuthType: "scram", @@ -88,7 +92,7 @@ describe("debug resource", () => { }, }); - const output = debugResource.toOutput(); + const output = await debugResource.toOutput(); expect(output).toContain(`The user is not connected to a MongoDB cluster because of an error.`); expect(output).toContain( @@ -97,4 +101,12 @@ describe("debug resource", () => { expect(output).toContain(`The inferred authentication mechanism is "scram".`); expect(output).toContain(`Error message from the server`); }); + + it("should notify if a cluster supports search indexes", async () => { + session.isSearchIndexSupported = vi.fn().mockResolvedValue(true); + debugResource.reduceApply("connect", undefined); + const output = await debugResource.toOutput(); + + expect(output).toContain(`The user is connected to the MongoDB cluster with support for search indexes.`); + }); });