diff --git a/src/Common/dataAccess/readCollections.test.ts b/src/Common/dataAccess/readCollections.test.ts index 50348b574..d3998ecfc 100644 --- a/src/Common/dataAccess/readCollections.test.ts +++ b/src/Common/dataAccess/readCollections.test.ts @@ -1,13 +1,39 @@ jest.mock("../../Utils/arm/request"); jest.mock("../CosmosClient"); +jest.mock("../Logger"); +jest.mock("../ErrorHandlingUtils", () => ({ handleError: jest.fn() })); +jest.mock("../../Utils/NotificationConsoleUtils"); +import * as Logger from "Common/Logger"; import { AuthType } from "../../AuthType"; import { DatabaseAccount } from "../../Contracts/DataModels"; import { updateUserContext } from "../../UserContext"; +import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { armRequest } from "../../Utils/arm/request"; import { client } from "../CosmosClient"; +import { handleError } from "../ErrorHandlingUtils"; import { readCollections } from "./readCollections"; describe("readCollections", () => { + const clearMessage = jest.fn(); + const fetchAll = jest.fn(); + const readAll = jest.fn(() => ({ fetchAll })); + const database = jest.fn(() => ({ containers: { readAll } })); + const diagnostics = { + clientSideRequestStatistics: { + requestDurationInMs: 123, + locationEndpointsContacted: ["https://test.documents.azure.com"], + retryDiagnostics: { failedAttempts: [{ statusCode: 429 }] }, + }, + diagnosticNode: { data: { responsePayload: "not-for-logging" } }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + (logConsoleProgress as jest.Mock).mockReturnValue(clearMessage); + (client as jest.Mock).mockReturnValue({ database }); + updateUserContext({ authType: AuthType.MasterKey }); + }); + beforeAll(() => { updateUserContext({ databaseAccount: { @@ -23,26 +49,61 @@ describe("readCollections", () => { }); await readCollections("database"); expect(armRequest).toHaveBeenCalled(); + expect(client).not.toHaveBeenCalled(); + expect(clearMessage).toHaveBeenCalledTimes(1); }); - it("should call SDK if not logged in with non-AAD method", async () => { - updateUserContext({ - authType: AuthType.MasterKey, + it("should log SDK request statistics and return collections for non-AAD authentication", async () => { + const resources = [{ id: "container" }]; + fetchAll.mockResolvedValue({ resources, diagnostics }); + + await expect(readCollections("database")).resolves.toBe(resources); + + expect(database).toHaveBeenCalledWith("database"); + expect(readAll).toHaveBeenCalledWith(); + expect(fetchAll).toHaveBeenCalledTimes(1); + expect(Logger.logInfo).toHaveBeenLastCalledWith( + expect.stringContaining(`diagnostics=${JSON.stringify(diagnostics.clientSideRequestStatistics)}`), + "readCollections", + ); + expect(JSON.stringify(jest.mocked(Logger.logInfo).mock.calls)).not.toContain("not-for-logging"); + expect(clearMessage).toHaveBeenCalledTimes(1); + }); + + it("should log SDK failure statistics and rethrow the original error", async () => { + const error = Object.assign(new Error("fetchAll failed"), { diagnostics }); + fetchAll.mockRejectedValue(error); + + await expect(readCollections("database")).rejects.toBe(error); + + expect(Logger.logError).toHaveBeenCalledWith( + `readCollections: fetchAll failed for database database, diagnostics=${JSON.stringify( + diagnostics.clientSideRequestStatistics, + )}`, + "readCollections", + ); + expect(handleError).toHaveBeenCalledWith( + error, + "ReadCollections", + "Error while querying containers for database database", + ); + expect(clearMessage).toHaveBeenCalledTimes(1); + }); + + it("should preserve error handling when diagnostics are unavailable", async () => { + const error = new Error("client initialization failed"); + (client as jest.Mock).mockImplementationOnce(() => { + throw error; }); - (client as jest.Mock).mockReturnValue({ - database: () => { - return { - containers: { - readAll: () => { - return { - fetchAll: (): unknown => [], - }; - }, - }, - }; - }, - }); - await readCollections("database"); - expect(client).toHaveBeenCalled(); + + await expect(readCollections("database")).rejects.toBe(error); + + expect(Logger.logError).not.toHaveBeenCalled(); + expect(handleError).toHaveBeenCalledWith( + error, + "ReadCollections", + "Error while querying containers for database database", + ); + expect(clearMessage).toHaveBeenCalledTimes(1); }); }); diff --git a/src/Common/dataAccess/readCollections.ts b/src/Common/dataAccess/readCollections.ts index d90832219..caf317a16 100644 --- a/src/Common/dataAccess/readCollections.ts +++ b/src/Common/dataAccess/readCollections.ts @@ -78,7 +78,9 @@ export async function readCollections(databaseId: string): Promise /dev/null; then echo '⚠️ mongosh not found. Installing...'; fi", - `if ! command -v mongosh &> /dev/null; then curl -LO https://downloads.mongodb.com/compass/mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`, - `if ! command -v mongosh &> /dev/null; then tar -xvzf mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`, - `if ! command -v mongosh &> /dev/null; then mkdir -p ~/mongosh/bin && mv mongosh-${PACKAGE_VERSION}-linux-x64/bin/mongosh ~/mongosh/bin/ && chmod +x ~/mongosh/bin/mongosh; fi`, - `if ! command -v mongosh &> /dev/null; then rm -rf mongosh-${PACKAGE_VERSION}-linux-x64 mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`, + `if ! command -v mongosh &> /dev/null; then curl -LO https://downloads.mongodb.com/compass/mongosh-${packageVersion}-linux-x64.tgz; fi`, + `if ! command -v mongosh &> /dev/null; then tar -xvzf mongosh-${packageVersion}-linux-x64.tgz; fi`, + `if ! command -v mongosh &> /dev/null; then mkdir -p ~/mongosh/bin && mv mongosh-${packageVersion}-linux-x64/bin/mongosh ~/mongosh/bin/ && chmod +x ~/mongosh/bin/mongosh; fi`, + `if ! command -v mongosh &> /dev/null; then rm -rf mongosh-${packageVersion}-linux-x64 mongosh-${packageVersion}-linux-x64.tgz; fi`, "if ! command -v mongosh &> /dev/null; then echo 'export PATH=$HOME/mongosh/bin:$PATH' >> ~/.bashrc; fi", "if ! command -v mongosh &> /dev/null; then source ~/.bashrc; fi", ]; diff --git a/src/Explorer/Tabs/CloudShellTab/ShellTypes/MongoShellHandler.test.tsx b/src/Explorer/Tabs/CloudShellTab/ShellTypes/MongoShellHandler.test.tsx index 28a79d404..dcba243bf 100644 --- a/src/Explorer/Tabs/CloudShellTab/ShellTypes/MongoShellHandler.test.tsx +++ b/src/Explorer/Tabs/CloudShellTab/ShellTypes/MongoShellHandler.test.tsx @@ -5,6 +5,9 @@ import { MongoShellHandler } from "./MongoShellHandler"; // Define interfaces for type safety interface DatabaseAccountProperties { mongoEndpoint?: string; + apiProperties?: { + serverVersion?: string; + }; } interface DatabaseAccount { @@ -80,6 +83,17 @@ describe("MongoShellHandler", () => { expect(commands.length).toBe(7); expect(commands[1]).toContain("mongosh-2.5.6-linux-x64.tgz"); }); + + it("should download a MongoDB 3.6-compatible package for 3.6 accounts", () => { + const properties = (userContext as UserContextType).databaseAccount.properties; + const originalApiProperties = properties.apiProperties; + properties.apiProperties = { serverVersion: "3.6" }; + + const commands = mongoShellHandler.getSetUpCommands(); + + expect(commands[1]).toContain("mongosh-1.10.6-linux-x64.tgz"); + properties.apiProperties = originalApiProperties; + }); }); describe("getConnectionCommand", () => { diff --git a/src/Explorer/Tabs/CloudShellTab/ShellTypes/MongoShellHandler.tsx b/src/Explorer/Tabs/CloudShellTab/ShellTypes/MongoShellHandler.tsx index b41dbd6db..12869cb93 100644 --- a/src/Explorer/Tabs/CloudShellTab/ShellTypes/MongoShellHandler.tsx +++ b/src/Explorer/Tabs/CloudShellTab/ShellTypes/MongoShellHandler.tsx @@ -29,7 +29,7 @@ export class MongoShellHandler extends AbstractShellHandler { } public getSetUpCommands(): string[] { - return this.mongoShellSetupCommands(); + return this.mongoShellSetupCommands(userContext.databaseAccount?.properties.apiProperties?.serverVersion); } public getConnectionCommand(): string {