diff --git a/test/unit/AGENTS.md b/test/unit/AGENTS.md index 5fa0da47ce..9c1be5463e 100644 --- a/test/unit/AGENTS.md +++ b/test/unit/AGENTS.md @@ -2,11 +2,11 @@ ### Rules & Expectations -- Do not edit application/source files unless the refactor demands it. Confirm before editing files outside of /test/unit, and justify why you need to make those changes. +- Do not edit application/source files unless writing effective unit tests demands it. Confirm before editing files outside of /test/unit, and justify why you need to make those changes. - Use Sinon, not TypeMoq. If easily possible, replace TypeMoq mocks/stubs/helpers with Sinon equivalents. - Use a Sinon sandbox (setup/teardown with sinon.createSandbox()); keep helper closures (e.g., createServer) inside setup where the sandbox is created. -- Default to chai.expect; when checking Sinon interactions, use sinon-chai. +- Use chai's `expect` for assertions; when checking Sinon interactions, use sinon-chai. Avoid `sinon.assert` and Node's `assert` in favor of `expect(...).to.have.been...` helpers. - Avoid Object.defineProperty hacks and (if possible) fake/partial plain objects; use sandbox.createStubInstance(type) and sandbox.stub(obj, 'prop').value(...). - Add shared Sinon helpers to test/unit/utils.ts when they’ll be reused. - If updating preexisting tests, preserve relevant inline comments from the original tests. @@ -14,7 +14,9 @@ sandbox, create stub instances, and return them. - Avoid unnecessary casts, like `myVar as unknown as MyType` when myVar is already a sinon-stubbed instance of MyType. - Maintain existing formatting conventions, line endings, and text encoding. -- Nest test suits as necessary to group tests in a logical manner. +- Nest test suites as necessary to group tests in a logical manner. +- Always await async prompt helpers (for example, `await prompt.render()`) so sinon stubs execute before assertions. +- If the class under test relies on VS Code services (event emitters, secret storage, etc.), stub their accessors via the sandbox (e.g., `sandbox.stub(obj, 'prop').get(() => emitter)` or provide a real `new vscode.EventEmitter()`) rather than providing a plain object. ### Process diff --git a/test/unit/chatCommands.test.ts b/test/unit/chatCommands.test.ts index 53dd77d2ea..a22a6c0d15 100644 --- a/test/unit/chatCommands.test.ts +++ b/test/unit/chatCommands.test.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from "vscode"; -import * as TypeMoq from "typemoq"; -import { expect } from "chai"; +import * as chai from "chai"; +import sinonChai from "sinon-chai"; import * as sinon from "sinon"; import { handleChatCommand, @@ -19,75 +19,63 @@ import { import MainController from "../../src/controllers/mainController"; import ConnectionManager, { ConnectionInfo } from "../../src/controllers/connectionManager"; import * as telemetry from "../../src/telemetry/telemetry"; +import { IServerInfo } from "vscode-mssql"; + +const { expect } = chai; + +chai.use(sinonChai); suite("Chat Commands Tests", () => { - let mockMainController: TypeMoq.IMock; - let mockConnectionManager: TypeMoq.IMock; - let mockConnectionInfo: TypeMoq.IMock; - let mockChatStream: TypeMoq.IMock; - let mockChatRequest: TypeMoq.IMock; let sandbox: sinon.SinonSandbox; + let mockMainController: sinon.SinonStubbedInstance; + let mockConnectionManager: sinon.SinonStubbedInstance; + let connectionInfo: ConnectionInfo; + let chatStream: vscode.ChatResponseStream; + let chatStreamMarkdownStub: sinon.SinonStub; const sampleConnectionUri = "file:///path/to/sample.sql"; setup(() => { sandbox = sinon.createSandbox(); - // Stub telemetry functions sandbox.stub(telemetry, "sendActionEvent"); sandbox.stub(telemetry, "sendErrorEvent"); - // Mock ConnectionInfo with minimal required properties - mockConnectionInfo = TypeMoq.Mock.ofType(); - mockConnectionInfo - .setup((x) => x.credentials) - .returns( - () => - ({ - server: "localhost", - database: "testdb", - authenticationType: "Integrated", - user: "testuser", - // eslint-disable-next-line @typescript-eslint/no-explicit-any - }) as any, - ); - - // Mock ConnectionManager - mockConnectionManager = TypeMoq.Mock.ofType(); - mockConnectionManager - .setup((x) => x.getConnectionInfo(TypeMoq.It.isAny())) - .returns(() => mockConnectionInfo.object); - mockConnectionManager - .setup((x) => x.getServerInfo(TypeMoq.It.isAny())) - .returns( - () => - ({ - serverVersion: "15.0.2000.5", - serverEdition: "Standard Edition", - isCloud: false, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - }) as any, - ); - mockConnectionManager - .setup((x) => x.disconnect(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(true)); - - // Mock MainController - mockMainController = TypeMoq.Mock.ofType(); - mockMainController - .setup((x) => x.connectionManager) - .returns(() => mockConnectionManager.object); - mockMainController.setup((x) => x.onNewConnection()).returns(() => Promise.resolve(true)); - mockMainController.setup((x) => x.onChooseDatabase()).returns(() => Promise.resolve(true)); - - // Mock ChatResponseStream - mockChatStream = TypeMoq.Mock.ofType(); - mockChatStream.setup((x) => x.markdown(TypeMoq.It.isAny())).returns(() => undefined); - - // Mock ChatRequest - mockChatRequest = TypeMoq.Mock.ofType(); + connectionInfo = { + credentials: { + server: "localhost", + database: "testdb", + authenticationType: "Integrated", + user: "testuser", + }, + } as ConnectionInfo; + + mockConnectionManager = sandbox.createStubInstance(ConnectionManager); + mockConnectionManager.getConnectionInfo.returns(connectionInfo); + mockConnectionManager.getServerInfo.returns({ + serverVersion: "15.0.2000.5", + serverEdition: "Standard Edition", + isCloud: false, + } as IServerInfo); + mockConnectionManager.disconnect.resolves(true); + + mockMainController = sandbox.createStubInstance(MainController); + sandbox + .stub(mockMainController, "connectionManager") + .get(() => mockConnectionManager as unknown as ConnectionManager); + mockMainController.onNewConnection.resolves(true); + mockMainController.onChooseDatabase.resolves(true); + + chatStreamMarkdownStub = sandbox.stub(); + chatStream = { + markdown: chatStreamMarkdownStub, + } as unknown as vscode.ChatResponseStream; }); + function createChatRequest(command?: string): vscode.ChatRequest { + return { command } as vscode.ChatRequest; + } + teardown(() => { sandbox.restore(); }); @@ -144,12 +132,12 @@ suite("Chat Commands Tests", () => { suite("Command Handler Tests", () => { test("handleChatCommand returns handled=false for unknown command", async () => { - mockChatRequest.setup((x) => x.command).returns(() => "unknownCommand"); + const chatRequest = createChatRequest("unknownCommand"); const result = await handleChatCommand( - mockChatRequest.object, - mockChatStream.object, - mockMainController.object, + chatRequest, + chatStream, + mockMainController as unknown as MainController, sampleConnectionUri, ); @@ -158,12 +146,12 @@ suite("Chat Commands Tests", () => { }); test("handleChatCommand returns handled=false for undefined command", async () => { - mockChatRequest.setup((x) => x.command).returns(() => undefined); + const chatRequest = createChatRequest(undefined); const result = await handleChatCommand( - mockChatRequest.object, - mockChatStream.object, - mockMainController.object, + chatRequest, + chatStream, + mockMainController as unknown as MainController, sampleConnectionUri, ); @@ -171,15 +159,13 @@ suite("Chat Commands Tests", () => { }); test("handleChatCommand returns error for connection-required command without connection", async () => { - mockChatRequest.setup((x) => x.command).returns(() => "disconnect"); - mockConnectionManager - .setup((x) => x.getConnectionInfo(TypeMoq.It.isAny())) - .returns(() => undefined); // No connection + const chatRequest = createChatRequest("disconnect"); + mockConnectionManager.getConnectionInfo.returns(undefined as unknown as ConnectionInfo); const result = await handleChatCommand( - mockChatRequest.object, - mockChatStream.object, - mockMainController.object, + chatRequest, + chatStream, + mockMainController as unknown as MainController, undefined, ); @@ -188,147 +174,133 @@ suite("Chat Commands Tests", () => { }); test("connect command executes successfully", async () => { - mockChatRequest.setup((x) => x.command).returns(() => "connect"); + const chatRequest = createChatRequest("connect"); const result = await handleChatCommand( - mockChatRequest.object, - mockChatStream.object, - mockMainController.object, - undefined, // Connect doesn't need existing connection + chatRequest, + chatStream, + mockMainController as unknown as MainController, + undefined, ); expect(result.handled).to.be.true; expect(result.errorMessage).to.be.undefined; - mockMainController.verify((x) => x.onNewConnection(), TypeMoq.Times.once()); - mockChatStream.verify((x) => x.markdown(TypeMoq.It.isAny()), TypeMoq.Times.once()); + expect(mockMainController.onNewConnection).to.have.been.calledOnce; + expect(chatStreamMarkdownStub).to.have.been.calledOnce; }); test("disconnect command executes successfully with connection", async () => { - mockChatRequest.setup((x) => x.command).returns(() => "disconnect"); + const chatRequest = createChatRequest("disconnect"); const result = await handleChatCommand( - mockChatRequest.object, - mockChatStream.object, - mockMainController.object, + chatRequest, + chatStream, + mockMainController as unknown as MainController, sampleConnectionUri, ); expect(result.handled).to.be.true; expect(result.errorMessage).to.be.undefined; - mockConnectionManager.verify( - (x) => x.disconnect(TypeMoq.It.isAny()), - TypeMoq.Times.once(), + expect(mockConnectionManager.disconnect).to.have.been.calledOnceWithExactly( + sampleConnectionUri, ); }); test("getConnectionDetails command shows connection information", async () => { - mockChatRequest.setup((x) => x.command).returns(() => "getConnectionDetails"); + const chatRequest = createChatRequest("getConnectionDetails"); const result = await handleChatCommand( - mockChatRequest.object, - mockChatStream.object, - mockMainController.object, + chatRequest, + chatStream, + mockMainController as unknown as MainController, sampleConnectionUri, ); expect(result.handled).to.be.true; expect(result.errorMessage).to.be.undefined; - // isConnectionActive calls getConnectionInfo once, handler calls it again - mockConnectionManager.verify( - (x) => x.getConnectionInfo(TypeMoq.It.isAny()), - TypeMoq.Times.atLeastOnce(), - ); - mockConnectionManager.verify( - (x) => x.getServerInfo(TypeMoq.It.isAny()), - TypeMoq.Times.once(), - ); - mockChatStream.verify((x) => x.markdown(TypeMoq.It.isAny()), TypeMoq.Times.once()); + expect(mockConnectionManager.getConnectionInfo).to.have.been.called; + expect(mockConnectionManager.getServerInfo).to.have.been.calledOnce; + expect(chatStreamMarkdownStub).to.have.been.calledOnce; }); test("changeDatabase command executes successfully", async () => { - mockChatRequest.setup((x) => x.command).returns(() => "changeDatabase"); + const chatRequest = createChatRequest("changeDatabase"); const result = await handleChatCommand( - mockChatRequest.object, - mockChatStream.object, - mockMainController.object, + chatRequest, + chatStream, + mockMainController as unknown as MainController, sampleConnectionUri, ); expect(result.handled).to.be.true; expect(result.errorMessage).to.be.undefined; - mockMainController.verify((x) => x.onChooseDatabase(), TypeMoq.Times.once()); + expect(mockMainController.onChooseDatabase).to.have.been.calledOnce; }); test("listServers command executes successfully", async () => { - mockChatRequest.setup((x) => x.command).returns(() => "listServers"); + const chatRequest = createChatRequest("listServers"); const mockConnectionStore = { - readAllConnections: () => - Promise.resolve([ - { - profileName: "Test Profile", - server: "localhost", - database: "testdb", - authenticationType: "Integrated", - }, - ]), + readAllConnections: async () => [ + { + profileName: "Test Profile", + server: "localhost", + database: "testdb", + authenticationType: "Integrated", + }, + ], }; - mockConnectionManager - .setup((x) => x.connectionStore) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .returns(() => mockConnectionStore as any); + sandbox + .stub(mockConnectionManager, "connectionStore") + .get(() => mockConnectionStore as unknown as ConnectionManager["connectionStore"]); const result = await handleChatCommand( - mockChatRequest.object, - mockChatStream.object, - mockMainController.object, - undefined, // listServers doesn't need connection + chatRequest, + chatStream, + mockMainController as unknown as MainController, + undefined, ); expect(result.handled).to.be.true; expect(result.errorMessage).to.be.undefined; - mockChatStream.verify( - (x) => x.markdown(TypeMoq.It.isAny()), - TypeMoq.Times.atLeastOnce(), - ); + expect(chatStreamMarkdownStub).to.have.been.called; }); test("prompt substitute command returns promptToAdd", async () => { - mockChatRequest.setup((x) => x.command).returns(() => "runQuery"); + const chatRequest = createChatRequest("runQuery"); const result = await handleChatCommand( - mockChatRequest.object, - mockChatStream.object, - mockMainController.object, + chatRequest, + chatStream, + mockMainController as unknown as MainController, sampleConnectionUri, ); - expect(result.handled).to.be.false; // Prompt substitute commands don't handle completely + expect(result.handled).to.be.false; expect(result.promptToAdd).to.not.be.undefined; - expect(result.promptToAdd).to.contain("query"); // Should contain template text + expect(result.promptToAdd).to.contain("query"); }); test("command with exception returns error message", async () => { - mockChatRequest.setup((x) => x.command).returns(() => "listServers"); - // Mock the connection store to throw an error + const chatRequest = createChatRequest("listServers"); const mockConnectionStore = { - readAllConnections: () => Promise.reject(new Error("Database error")), + readAllConnections: async () => { + throw new Error("Database error"); + }, }; - mockConnectionManager - .setup((x) => x.connectionStore) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .returns(() => mockConnectionStore as any); + sandbox + .stub(mockConnectionManager, "connectionStore") + .get(() => mockConnectionStore as unknown as ConnectionManager["connectionStore"]); const result = await handleChatCommand( - mockChatRequest.object, - mockChatStream.object, - mockMainController.object, + chatRequest, + chatStream, + mockMainController as unknown as MainController, undefined, ); expect(result.handled).to.be.true; expect(result.errorMessage).to.not.be.undefined; - // Just check that an error message exists }); }); diff --git a/test/unit/checkbox.test.ts b/test/unit/checkbox.test.ts index f717501466..fc1c142c6b 100644 --- a/test/unit/checkbox.test.ts +++ b/test/unit/checkbox.test.ts @@ -3,67 +3,73 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as TypeMoq from "typemoq"; +import * as sinon from "sinon"; +import sinonChai from "sinon-chai"; +import { expect } from "chai"; +import * as chai from "chai"; import * as figures from "figures"; import VscodeWrapper from "../../src/controllers/vscodeWrapper"; import CheckboxPrompt from "../../src/prompts/checkbox"; +chai.use(sinonChai); + // @cssuh 10/22 - commented this test because it was throwing some random undefined errors suite("Test Checkbox prompt", () => { - test("Test checkbox prompt with simple question", () => { - let question = { + let sandbox: sinon.SinonSandbox; + + setup(() => { + sandbox = sinon.createSandbox(); + }); + + teardown(() => { + sandbox.restore(); + }); + + test("Test checkbox prompt with simple question", async () => { + const question = { choices: [ { name: "test1", checked: true }, { name: "test2", checked: false }, ], }; - let vscodeWrapper = TypeMoq.Mock.ofType(VscodeWrapper, TypeMoq.MockBehavior.Loose); - vscodeWrapper - .setup((v) => v.showQuickPickStrings(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve("test1")); - let checkbox = new CheckboxPrompt(question, vscodeWrapper.object); - checkbox.render(); - vscodeWrapper.verify( - (v) => v.showQuickPickStrings(TypeMoq.It.isAny(), TypeMoq.It.isAny()), - TypeMoq.Times.once(), - ); + const vscodeWrapper = sandbox.createStubInstance(VscodeWrapper); + vscodeWrapper.showQuickPickStrings.resolves(figures.tick); + + const checkbox = new CheckboxPrompt(question, vscodeWrapper); + await checkbox.render(); + + expect(vscodeWrapper.showQuickPickStrings).to.have.been.calledOnce; }); - test("Test Checkbox prompt with error", () => { - let question = { + test("Test Checkbox prompt with error", async () => { + const question = { choices: [ { name: "test1", checked: true }, { name: "test2", checked: false }, ], }; - let vscodeWrapper = TypeMoq.Mock.ofType(VscodeWrapper, TypeMoq.MockBehavior.Loose); - vscodeWrapper - .setup((v) => v.showQuickPickStrings(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve(undefined)); - let checkbox = new CheckboxPrompt(question, vscodeWrapper.object); - checkbox.render(); - vscodeWrapper.verify( - (v) => v.showQuickPickStrings(TypeMoq.It.isAny(), TypeMoq.It.isAny()), - TypeMoq.Times.once(), - ); + const vscodeWrapper = sandbox.createStubInstance(VscodeWrapper); + vscodeWrapper.showQuickPickStrings.resolves(undefined); + + const checkbox = new CheckboxPrompt(question, vscodeWrapper); + await checkbox.render().catch(() => undefined); + + expect(vscodeWrapper.showQuickPickStrings).to.have.been.calledOnce; }); - test("Test Checkbox prompt with checked answer", () => { - let question = { + test("Test Checkbox prompt with checked answer", async () => { + const question = { choices: [ { name: "test1", checked: true }, { name: "test2", checked: false }, ], }; - let vscodeWrapper = TypeMoq.Mock.ofType(VscodeWrapper, TypeMoq.MockBehavior.Loose); - vscodeWrapper - .setup((v) => v.showQuickPickStrings(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve(figures.tick)); - let checkbox = new CheckboxPrompt(question, vscodeWrapper.object); - checkbox.render(); - vscodeWrapper.verify( - (v) => v.showQuickPickStrings(TypeMoq.It.isAny(), TypeMoq.It.isAny()), - TypeMoq.Times.once(), - ); + const vscodeWrapper = sandbox.createStubInstance(VscodeWrapper); + vscodeWrapper.showQuickPickStrings.resolves(figures.tick); + + const checkbox = new CheckboxPrompt(question, vscodeWrapper); + await checkbox.render(); + + expect(vscodeWrapper.showQuickPickStrings).to.have.been.calledOnce; }); }); diff --git a/test/unit/connectionConfig.test.ts b/test/unit/connectionConfig.test.ts index ae45f57845..328d97f6cf 100644 --- a/test/unit/connectionConfig.test.ts +++ b/test/unit/connectionConfig.test.ts @@ -4,40 +4,39 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from "vscode"; -import * as TypeMoq from "typemoq"; import { ConnectionConfig } from "../../src/connectionconfig/connectionconfig"; import VscodeWrapper from "../../src/controllers/vscodeWrapper"; import * as sinon from "sinon"; -import { expect } from "chai"; +import * as chai from "chai"; +import sinonChai from "sinon-chai"; import { IConnectionGroup, IConnectionProfile } from "../../src/models/interfaces"; import * as Constants from "../../src/constants/constants"; import { deepClone } from "../../src/models/utils"; +import { stubVscodeWrapper } from "./utils"; + +const { expect } = chai; + +chai.use(sinonChai); suite("ConnectionConfig Tests", () => { let sandbox: sinon.SinonSandbox; - let mockVscodeWrapper: TypeMoq.IMock; - let outputChannel: TypeMoq.IMock; + let mockVscodeWrapper: sinon.SinonStubbedInstance; const rootGroupId = "root-group-id"; + + /* eslint-disable @typescript-eslint/no-explicit-any */ let mockGlobalConfigData: Map = new Map(); let mockWorkspaceConfigData: Map = new Map(); + /* eslint-enable @typescript-eslint/no-explicit-any */ setup(() => { sandbox = sinon.createSandbox(); - // Reset test data mockGlobalConfigData = new Map(); mockWorkspaceConfigData = new Map(); - mockVscodeWrapper = TypeMoq.Mock.ofType(); - - outputChannel = TypeMoq.Mock.ofType(); - outputChannel.setup((c) => c.clear()); - outputChannel.setup((c) => c.append(TypeMoq.It.isAny())); - outputChannel.setup((c) => c.show(TypeMoq.It.isAny())); + mockVscodeWrapper = stubVscodeWrapper(sandbox); - mockVscodeWrapper.setup((v) => v.outputChannel).returns(() => outputChannel.object); - - const mockConfiguration: any = { + const mockConfiguration = { inspect: (setting: string) => { let result; if (setting === Constants.connectionsArrayName) { @@ -61,40 +60,19 @@ suite("ConnectionConfig Tests", () => { return deepClone(result); }, }; + const workspaceConfiguration = mockConfiguration as vscode.WorkspaceConfiguration; + + mockVscodeWrapper.getConfiguration.callsFake((section: string) => + section === Constants.extensionName ? workspaceConfiguration : undefined, + ); - mockVscodeWrapper - .setup((x) => - x.getConfiguration(TypeMoq.It.isValue(Constants.extensionName), TypeMoq.It.isAny()), - ) - .returns(() => mockConfiguration); - - // like the acutal connection config, only supports writing to Global/User config, not workspace - mockVscodeWrapper - .setup((x) => - x.setConfiguration( - TypeMoq.It.isValue(Constants.extensionName), - TypeMoq.It.isAny(), - TypeMoq.It.isAny(), - ), - ) - .callback((_section, key, value) => { - mockGlobalConfigData.set(key, deepClone(value)); - return Promise.resolve(); - }) - .returns(() => Promise.resolve()); - - mockVscodeWrapper.setup((x) => x.activeTextEditorUri).returns(() => undefined); - - mockVscodeWrapper - .setup((x) => x.outputChannel) - .returns( - () => - ({ - appendLine: () => {}, - clear: () => {}, - show: () => {}, - }) as any, - ); + mockVscodeWrapper.setConfiguration.callsFake(async (_section, key, value) => { + mockGlobalConfigData.set(key, deepClone(value)); + }); + + sandbox.stub(mockVscodeWrapper, "activeTextEditorUri").get(() => undefined); + + mockVscodeWrapper.showErrorMessage.resolves(undefined); }); teardown(() => { @@ -103,7 +81,7 @@ suite("ConnectionConfig Tests", () => { suite("Initialization", () => { test("Initialization creates ROOT group when it doesn't exist", async () => { - const config = new ConnectionConfig(mockVscodeWrapper.object); + const config = new ConnectionConfig(mockVscodeWrapper); await config.initialized; const savedGroups = mockGlobalConfigData.get( @@ -121,7 +99,7 @@ suite("ConnectionConfig Tests", () => { { name: "Group without ID" } as IConnectionGroup, // Missing ID ]); - const connConfig = new ConnectionConfig(mockVscodeWrapper.object); + const connConfig = new ConnectionConfig(mockVscodeWrapper); await connConfig.initialized; const savedGroups = mockGlobalConfigData.get( @@ -154,7 +132,7 @@ suite("ConnectionConfig Tests", () => { } as IConnectionProfile, ]); - const connConfig = new ConnectionConfig(mockVscodeWrapper.object); + const connConfig = new ConnectionConfig(mockVscodeWrapper); await connConfig.initialized; const savedProfiles = mockGlobalConfigData.get( @@ -185,19 +163,11 @@ suite("ConnectionConfig Tests", () => { } as IConnectionProfile, ]); - const connConfig = new ConnectionConfig(mockVscodeWrapper.object); + const connConfig = new ConnectionConfig(mockVscodeWrapper); await connConfig.initialized; // Verify setConfiguration was not called since no changes needed - mockVscodeWrapper.verify( - (v) => - v.setConfiguration( - TypeMoq.It.isValue(Constants.extensionName), - TypeMoq.It.isAny(), - TypeMoq.It.isAny(), - ), - TypeMoq.Times.never(), - ); + expect(mockVscodeWrapper.setConfiguration).to.not.have.been.called; }); }); @@ -210,7 +180,7 @@ suite("ConnectionConfig Tests", () => { suite("Connections", () => { test("addConnection adds a new connection to profiles", async () => { - const connConfig = new ConnectionConfig(mockVscodeWrapper.object); + const connConfig = new ConnectionConfig(mockVscodeWrapper); await connConfig.initialized; // Add a connection @@ -251,7 +221,7 @@ suite("ConnectionConfig Tests", () => { mockGlobalConfigData.set(Constants.connectionsArrayName, [testConnProfile]); - const connConfig = new ConnectionConfig(mockVscodeWrapper.object); + const connConfig = new ConnectionConfig(mockVscodeWrapper); await connConfig.initialized; const result = await connConfig.removeConnection(testConnProfile); @@ -282,7 +252,7 @@ suite("ConnectionConfig Tests", () => { } as IConnectionProfile, ]); - const connConfig = new ConnectionConfig(mockVscodeWrapper.object); + const connConfig = new ConnectionConfig(mockVscodeWrapper); await connConfig.initialized; // Try to remove a profile that doesn't exist in the config @@ -297,15 +267,7 @@ suite("ConnectionConfig Tests", () => { ); // Verify setConfiguration was not called - mockVscodeWrapper.verify( - (v) => - v.setConfiguration( - TypeMoq.It.isValue(Constants.extensionName), - TypeMoq.It.isAny(), - TypeMoq.It.isAny(), - ), - TypeMoq.Times.never(), - ); + expect(mockVscodeWrapper.setConfiguration).to.not.have.been.called; }); test("getConnections filters out workspace connections that are missing IDs", async () => { @@ -328,13 +290,10 @@ suite("ConnectionConfig Tests", () => { mockWorkspaceConfigData.set(Constants.connectionsArrayName, testConnProfiles); - mockVscodeWrapper - .setup((x) => x.showErrorMessage(TypeMoq.It.isAny())) - .returns(() => { - return Promise.resolve(undefined); - }); + mockVscodeWrapper; + mockVscodeWrapper.showErrorMessage.resolves(undefined); - const connConfig = new ConnectionConfig(mockVscodeWrapper.object); + const connConfig = new ConnectionConfig(mockVscodeWrapper); await connConfig.initialized; const result = await connConfig.getConnections(true /* getWorkspaceConnections */); @@ -344,16 +303,12 @@ suite("ConnectionConfig Tests", () => { "Workspace connection missing ID should not be returned", ); - mockVscodeWrapper.verify( - (v) => - v.showErrorMessage( - TypeMoq.It.is( - (msg) => - msg.includes("Test Profile One") && - msg.includes("Test Profile Two"), - ), - ), - TypeMoq.Times.once(), + expect(mockVscodeWrapper.showErrorMessage).to.have.been.calledOnce; + expect(mockVscodeWrapper.showErrorMessage).to.have.been.calledWithMatch( + sinon.match( + (msg: string) => + msg.includes("Test Profile One") && msg.includes("Test Profile Two"), + ), ); }); @@ -368,13 +323,10 @@ suite("ConnectionConfig Tests", () => { mockGlobalConfigData.set(Constants.connectionsArrayName, [testConnProfile]); - mockVscodeWrapper - .setup((x) => x.showErrorMessage(TypeMoq.It.isAny())) - .returns(() => { - return Promise.resolve(undefined); - }); + mockVscodeWrapper; + mockVscodeWrapper.showErrorMessage.resolves(undefined); - const connConfig = new ConnectionConfig(mockVscodeWrapper.object); + const connConfig = new ConnectionConfig(mockVscodeWrapper); await connConfig.initialized; const result = await connConfig.getConnections(false /* getWorkspaceConnections */); @@ -384,10 +336,7 @@ suite("ConnectionConfig Tests", () => { "Connection missing server should not be returned", ); - mockVscodeWrapper.verify( - (v) => v.showErrorMessage(TypeMoq.It.isAny()), - TypeMoq.Times.once(), - ); + expect(mockVscodeWrapper.showErrorMessage).to.have.been.calledOnce; }); test("updateConnection updates an existing connection profile", async () => { @@ -401,7 +350,7 @@ suite("ConnectionConfig Tests", () => { mockGlobalConfigData.set(Constants.connectionsArrayName, [testConnProfile]); - const connConfig = new ConnectionConfig(mockVscodeWrapper.object); + const connConfig = new ConnectionConfig(mockVscodeWrapper); await connConfig.initialized; const updatedProfile: IConnectionProfile = { @@ -422,7 +371,7 @@ suite("ConnectionConfig Tests", () => { suite("Connection Groups", () => { test("addGroup adds a new connection group", async () => { - const connConfig = new ConnectionConfig(mockVscodeWrapper.object); + const connConfig = new ConnectionConfig(mockVscodeWrapper); await connConfig.initialized; const newGroup: IConnectionGroup = { @@ -455,7 +404,7 @@ suite("ConnectionConfig Tests", () => { testGroup, ]); - const connConfig = new ConnectionConfig(mockVscodeWrapper.object); + const connConfig = new ConnectionConfig(mockVscodeWrapper); await connConfig.initialized; const result = await connConfig.removeGroup(testGroup.id, "delete"); @@ -500,7 +449,7 @@ suite("ConnectionConfig Tests", () => { mockGlobalConfigData.set(Constants.connectionsArrayName, [conn1, conn2]); - const connConfig = new ConnectionConfig(mockVscodeWrapper.object); + const connConfig = new ConnectionConfig(mockVscodeWrapper); await connConfig.initialized; // Remove Group A @@ -554,7 +503,7 @@ suite("ConnectionConfig Tests", () => { mockGlobalConfigData.set(Constants.connectionsArrayName, [conn1, conn2]); - const connConfig = new ConnectionConfig(mockVscodeWrapper.object); + const connConfig = new ConnectionConfig(mockVscodeWrapper); await connConfig.initialized; // Remove Group A with move option @@ -601,7 +550,7 @@ suite("ConnectionConfig Tests", () => { mockGlobalConfigData.set(Constants.connectionGroupsArrayName, testGroups); - const connConfig = new ConnectionConfig(mockVscodeWrapper.object); + const connConfig = new ConnectionConfig(mockVscodeWrapper); await connConfig.initialized; const groups = await connConfig.getGroups(); @@ -618,7 +567,7 @@ suite("ConnectionConfig Tests", () => { mockGlobalConfigData.set(Constants.connectionGroupsArrayName, testGroups); - const connConfig = new ConnectionConfig(mockVscodeWrapper.object); + const connConfig = new ConnectionConfig(mockVscodeWrapper); await connConfig.initialized; const group = await connConfig.getGroupById("test-group-id"); @@ -629,7 +578,7 @@ suite("ConnectionConfig Tests", () => { }); test("getGroupById returns undefined for non-existent group", async () => { - const connConfig = new ConnectionConfig(mockVscodeWrapper.object); + const connConfig = new ConnectionConfig(mockVscodeWrapper); await connConfig.initialized; const group = await connConfig.getGroupById("non-existent-id"); diff --git a/test/unit/connectionStore.test.ts b/test/unit/connectionStore.test.ts index 49224dc128..9a6f9476cc 100644 --- a/test/unit/connectionStore.test.ts +++ b/test/unit/connectionStore.test.ts @@ -3,43 +3,48 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as TypeMoq from "typemoq"; +import { expect } from "chai"; +import * as sinon from "sinon"; import * as vscode from "vscode"; import { ConnectionStore } from "../../src/models/connectionStore"; -import { ICredentialStore } from "../../src/credentialstore/icredentialstore"; +import { CredentialStore } from "../../src/credentialstore/credentialstore"; import { Logger } from "../../src/models/logger"; import { ConnectionConfig } from "../../src/connectionconfig/connectionconfig"; import VscodeWrapper from "../../src/controllers/vscodeWrapper"; -import { expect } from "chai"; -import * as sinon from "sinon"; -import { azureAuthConn, sqlAuthConn, connStringConn } from "./utils.test"; import { IConnectionProfile, IConnectionProfileWithSource } from "../../src/models/interfaces"; import { MatchScore } from "../../src/models/utils"; +import { Deferred } from "../../src/protocol"; +import { azureAuthConn, sqlAuthConn, connStringConn } from "./utils.test"; +import { getMockContext } from "./utils"; suite("ConnectionStore Tests", () => { let sandbox: sinon.SinonSandbox; let connectionStore: ConnectionStore; - let mockContext: TypeMoq.IMock; - let mockLogger: TypeMoq.IMock; - let mockCredentialStore: TypeMoq.IMock; - let mockConnectionConfig: TypeMoq.IMock; - let mockVscodeWrapper: TypeMoq.IMock; + let mockContext: vscode.ExtensionContext; + let mockLogger: sinon.SinonStubbedInstance; + let mockCredentialStore: sinon.SinonStubbedInstance; + let mockConnectionConfig: sinon.SinonStubbedInstance; + let mockVscodeWrapper: sinon.SinonStubbedInstance; + let initializedDeferred: Deferred; setup(async () => { sandbox = sinon.createSandbox(); - mockContext = TypeMoq.Mock.ofType(); - mockVscodeWrapper = TypeMoq.Mock.ofType(); - mockLogger = TypeMoq.Mock.ofType(); - mockCredentialStore = TypeMoq.Mock.ofType(); - mockConnectionConfig = TypeMoq.Mock.ofType(); - - mockConnectionConfig - .setup((c) => c.getConnections(TypeMoq.It.isAny())) - .returns(() => { - return Promise.resolve([]); - }); + mockContext = getMockContext(); + mockVscodeWrapper = sandbox.createStubInstance(VscodeWrapper); + mockLogger = sandbox.createStubInstance(Logger); + + mockCredentialStore = sandbox.createStubInstance(CredentialStore); + mockCredentialStore.readCredential.resolves(undefined as any); + mockCredentialStore.saveCredential.resolves(true); + mockCredentialStore.deleteCredential.resolves(); + + mockConnectionConfig = sandbox.createStubInstance(ConnectionConfig); + initializedDeferred = new Deferred(); + initializedDeferred.resolve(); + mockConnectionConfig.initialized = initializedDeferred; + mockConnectionConfig.getConnections.resolves([]); }); teardown(() => { @@ -49,11 +54,11 @@ suite("ConnectionStore Tests", () => { test("Initializes correctly", async () => { expect(() => { connectionStore = new ConnectionStore( - mockContext.object, - mockCredentialStore.object, - mockLogger.object, - mockConnectionConfig.object, - mockVscodeWrapper.object, + mockContext, + mockCredentialStore, + mockLogger, + mockConnectionConfig, + mockVscodeWrapper, ); }).to.not.throw(); @@ -102,11 +107,11 @@ suite("ConnectionStore Tests", () => { test("findMatchingProfile", async () => { connectionStore = new ConnectionStore( - mockContext.object, - mockCredentialStore.object, - mockLogger.object, - mockConnectionConfig.object, - mockVscodeWrapper.object, + mockContext, + mockCredentialStore, + mockLogger, + mockConnectionConfig, + mockVscodeWrapper, ); await connectionStore.initialized; diff --git a/test/unit/credentialStore.test.ts b/test/unit/credentialStore.test.ts index 93da61f708..2e4baebf5c 100644 --- a/test/unit/credentialStore.test.ts +++ b/test/unit/credentialStore.test.ts @@ -3,62 +3,115 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as sinon from "sinon"; +import sinonChai from "sinon-chai"; +import { expect } from "chai"; +import * as chai from "chai"; import * as vscode from "vscode"; -import * as TypeMoq from "typemoq"; import * as Contracts from "../../src/models/contracts"; import SqlToolsServiceClient from "../../src/languageservice/serviceclient"; import { CredentialStore } from "../../src/credentialstore/credentialstore"; import { ICredentialStore } from "../../src/credentialstore/icredentialstore"; import VscodeWrapper from "../../src/controllers/vscodeWrapper"; +chai.use(sinonChai); + suite("Credential Store Tests", () => { - let client: TypeMoq.IMock; + let sandbox: sinon.SinonSandbox; + let client: sinon.SinonStubbedInstance; let credentialStore: ICredentialStore; - let mockContext: TypeMoq.IMock; + let secretStorage: { + get: sinon.SinonStub<[string], Promise>; + store: sinon.SinonStub<[string, string], Promise>; + delete: sinon.SinonStub<[string], Promise>; + }; + let context: vscode.ExtensionContext; + let vscodeWrapper: sinon.SinonStubbedInstance; + let saveRequestStub: sinon.SinonStub; + let readRequestStub: sinon.SinonStub; + let deleteRequestStub: sinon.SinonStub; + + const credentialId = "test_credential"; setup(() => { - client = TypeMoq.Mock.ofType(SqlToolsServiceClient, TypeMoq.MockBehavior.Loose); - mockContext = TypeMoq.Mock.ofType(); - client - .setup((c) => c.sendRequest(Contracts.SaveCredentialRequest.type, TypeMoq.It.isAny())) - .returns(() => Promise.resolve(true)); - client - .setup((c) => c.sendRequest(Contracts.ReadCredentialRequest.type, TypeMoq.It.isAny())) - .returns(() => Promise.resolve(undefined)); - client - .setup((c) => c.sendRequest(Contracts.DeleteCredentialRequest.type, TypeMoq.It.isAny())) - .returns(() => Promise.resolve(undefined)); - credentialStore = new CredentialStore( - mockContext.object, - TypeMoq.Mock.ofType().object, - client.object, - ); + sandbox = sinon.createSandbox(); + + client = sandbox.createStubInstance(SqlToolsServiceClient); + + secretStorage = { + get: sandbox.stub<[string], Promise>(), + store: sandbox.stub<[string, string], Promise>(), + delete: sandbox.stub<[string], Promise>(), + }; + + secretStorage.get.resolves(undefined); + secretStorage.store.resolves(); + secretStorage.delete.resolves(); + + context = { + secrets: secretStorage as unknown as vscode.SecretStorage, + } as vscode.ExtensionContext; + + saveRequestStub = client.sendRequest + .withArgs(Contracts.SaveCredentialRequest.type, sinon.match.any) + .resolves(true); + readRequestStub = client.sendRequest + .withArgs(Contracts.ReadCredentialRequest.type, sinon.match.any) + .resolves(undefined); + deleteRequestStub = client.sendRequest + .withArgs(Contracts.DeleteCredentialRequest.type, sinon.match.any) + .resolves(undefined); + + vscodeWrapper = sandbox.createStubInstance(VscodeWrapper); + + credentialStore = new CredentialStore(context, vscodeWrapper, client); + }); + + teardown(() => { + sandbox.restore(); }); - test("Read credential should send a ReadCredentialRequest", () => { - void credentialStore.readCredential("test_credential").then(() => { - client.verify( - (c) => c.sendRequest(Contracts.ReadCredentialRequest.type, TypeMoq.It.isAny()), - TypeMoq.Times.once(), - ); - }); + test("Read credential should send a ReadCredentialRequest when secret storage misses", async () => { + const serverCredential: Contracts.Credential = new Contracts.Credential(); + serverCredential.credentialId = credentialId; + serverCredential.password = "test_password"; + + readRequestStub.resolves(serverCredential); + + await credentialStore.readCredential(credentialId); + + expect(secretStorage.get).to.have.been.calledOnceWithExactly(credentialId); + expect(readRequestStub).to.have.been.calledOnceWithExactly( + Contracts.ReadCredentialRequest.type, + sinon.match.has("credentialId", credentialId), + ); + expect(secretStorage.store).to.have.been.calledOnceWithExactly( + credentialId, + serverCredential.password, + ); + expect(deleteRequestStub).to.have.been.calledOnceWithExactly( + Contracts.DeleteCredentialRequest.type, + sinon.match.has("credentialId", credentialId), + ); }); - test("Save credential should send a SaveCredentialRequest", () => { - void credentialStore.saveCredential("test_credential", "test_password").then(() => { - client.verify( - (c) => c.sendRequest(Contracts.SaveCredentialRequest.type, TypeMoq.It.isAny()), - TypeMoq.Times.once(), - ); - }); + test("Save credential should store in secret storage", async () => { + await credentialStore.saveCredential(credentialId, "test_password"); + + expect(secretStorage.store).to.have.been.calledOnceWithExactly( + credentialId, + "test_password", + ); + expect(saveRequestStub).to.not.have.been.called; }); - test("Delete credential should send a DeleteCredentialRequest", () => { - void credentialStore.deleteCredential("test_credential").then(() => { - client.verify( - (c) => c.sendRequest(Contracts.DeleteCredentialRequest.type, TypeMoq.It.isAny()), - TypeMoq.Times.once(), - ); - }); + test("Delete credential should remove from secret storage and request delete", async () => { + await credentialStore.deleteCredential(credentialId); + + expect(secretStorage.delete).to.have.been.calledOnceWithExactly(credentialId); + expect(deleteRequestStub).to.have.been.calledWithExactly( + Contracts.DeleteCredentialRequest.type, + sinon.match.has("credentialId", credentialId), + ); }); }); diff --git a/test/unit/expand.test.ts b/test/unit/expand.test.ts index eef80e3139..3815707bec 100644 --- a/test/unit/expand.test.ts +++ b/test/unit/expand.test.ts @@ -3,84 +3,87 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as sinon from "sinon"; +import sinonChai from "sinon-chai"; +import { expect } from "chai"; +import * as chai from "chai"; import * as vscode from "vscode"; -import * as TypeMoq from "typemoq"; import VscodeWrapper from "../../src/controllers/vscodeWrapper"; import ExpandPrompt from "../../src/prompts/expand"; +chai.use(sinonChai); + suite("Test Expand Prompt", () => { - test("Test expand prompt with simple question", () => { - let question = { + let sandbox: sinon.SinonSandbox; + + setup(() => { + sandbox = sinon.createSandbox(); + }); + + teardown(() => { + sandbox.restore(); + }); + + test("Test expand prompt with simple question", async () => { + const question = { choices: [{ name: "test", value: "test" }], - validate: (e) => false, + validate: () => false, }; - let vscodeWrapper = TypeMoq.Mock.ofType(VscodeWrapper, TypeMoq.MockBehavior.Loose); - vscodeWrapper - .setup((v) => v.showQuickPickStrings(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve("test")); - let expand = new ExpandPrompt(question, vscodeWrapper.object); - expand.render(); - vscodeWrapper.verify( - (v) => v.showQuickPickStrings(TypeMoq.It.isAny(), TypeMoq.It.isAny()), - TypeMoq.Times.once(), - ); + const vscodeWrapper = sandbox.createStubInstance(VscodeWrapper); + vscodeWrapper.showQuickPickStrings.resolves("test"); + + const expand = new ExpandPrompt(question, vscodeWrapper); + await expand.render(); + + expect(vscodeWrapper.showQuickPickStrings).to.have.been.calledOnce; }); // @cssuh 10/22 - commented this test because it was throwing some random undefined errors - test.skip("Test expand prompt with error question", () => { - let question = { + test.skip("Test expand prompt with error question", async () => { + const question = { choices: [{ name: "test", value: "test" }], - validate: (e) => true, + validate: () => true, }; - let vscodeWrapper = TypeMoq.Mock.ofType(VscodeWrapper, TypeMoq.MockBehavior.Loose); - vscodeWrapper - .setup((v) => v.showQuickPickStrings(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve(undefined)); - let expand = new ExpandPrompt(question, vscodeWrapper.object); - expand.render(); - vscodeWrapper.verify( - (v) => v.showQuickPickStrings(TypeMoq.It.isAny(), TypeMoq.It.isAny()), - TypeMoq.Times.once(), - ); + const vscodeWrapper = sandbox.createStubInstance(VscodeWrapper); + vscodeWrapper.showQuickPickStrings.resolves(undefined); + + const expand = new ExpandPrompt(question, vscodeWrapper); + await expand.render(); + + expect(vscodeWrapper.showQuickPickStrings).to.have.been.calledOnce; }); - test.skip("Test expand prompt with quick pick item", () => { - let quickPickItem: vscode.QuickPickItem = { + test.skip("Test expand prompt with quick pick item", async () => { + const quickPickItem: vscode.QuickPickItem = { label: "test", }; - let question = { + const question = { choices: [quickPickItem], - validate: (e) => true, + validate: () => true, }; - let vscodeWrapper = TypeMoq.Mock.ofType(VscodeWrapper, TypeMoq.MockBehavior.Loose); - vscodeWrapper - .setup((v) => v.showQuickPick(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve(quickPickItem)); - let expand = new ExpandPrompt(question, vscodeWrapper.object); - expand.render(); - vscodeWrapper.verify( - (v) => v.showQuickPick(TypeMoq.It.isAny(), TypeMoq.It.isAny()), - TypeMoq.Times.once(), - ); + const vscodeWrapper = sandbox.createStubInstance(VscodeWrapper); + vscodeWrapper.showQuickPick.resolves(quickPickItem); + + const expand = new ExpandPrompt(question, vscodeWrapper); + await expand.render(); + + expect(vscodeWrapper.showQuickPick).to.have.been.calledOnce; }); - test.skip("Test expand prompt with error quick pick item", () => { - let quickPickItem: vscode.QuickPickItem = { + test.skip("Test expand prompt with error quick pick item", async () => { + const quickPickItem: vscode.QuickPickItem = { label: "test", }; - let question = { + const question = { choices: [quickPickItem], - validate: (e) => false, + validate: () => false, }; - let vscodeWrapper = TypeMoq.Mock.ofType(VscodeWrapper, TypeMoq.MockBehavior.Loose); - vscodeWrapper - .setup((v) => v.showQuickPick(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve(undefined)); - let expand = new ExpandPrompt(question, vscodeWrapper.object); - expand.render(); - vscodeWrapper.verify( - (v) => v.showQuickPick(TypeMoq.It.isAny(), TypeMoq.It.isAny()), - TypeMoq.Times.once(), - ); + const vscodeWrapper = sandbox.createStubInstance(VscodeWrapper); + vscodeWrapper.showQuickPick.resolves(undefined); + + const expand = new ExpandPrompt(question, vscodeWrapper); + await expand.render(); + + expect(vscodeWrapper.showQuickPick).to.have.been.calledOnce; }); }); diff --git a/test/unit/input.test.ts b/test/unit/input.test.ts index 9ac27a22e8..9c7bdfeccd 100644 --- a/test/unit/input.test.ts +++ b/test/unit/input.test.ts @@ -3,71 +3,84 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as TypeMoq from "typemoq"; +import * as sinon from "sinon"; +import sinonChai from "sinon-chai"; +import { expect } from "chai"; +import * as chai from "chai"; import VscodeWrapper from "../../src/controllers/vscodeWrapper"; import InputPrompt from "../../src/prompts/input"; +chai.use(sinonChai); + suite("Input Prompt Tests", () => { + let sandbox: sinon.SinonSandbox; + setup(() => { - let vscodeWrapper = TypeMoq.Mock.ofType(VscodeWrapper, TypeMoq.MockBehavior.Loose); - vscodeWrapper - .setup((v) => v.showInputBox(TypeMoq.It.isAny())) - .returns(() => Promise.resolve("test")); + sandbox = sinon.createSandbox(); + }); + + teardown(() => { + sandbox.restore(); }); - test("Test list prompt render simple question", () => { - let vscodeWrapper = TypeMoq.Mock.ofType(VscodeWrapper, TypeMoq.MockBehavior.Loose); - vscodeWrapper - .setup((v) => v.showInputBox(TypeMoq.It.isAny())) - .returns(() => Promise.resolve("test")); + test("Test list prompt render simple question", async () => { + const vscodeWrapper = sandbox.createStubInstance(VscodeWrapper); + vscodeWrapper.showInputBox.resolves("test"); const question = { message: "test", placeHolder: "test", choices: [{ name: "test", value: "test" }], }; - let listPrompt = new InputPrompt(question, vscodeWrapper.object); - listPrompt.render(); - vscodeWrapper.verify((v) => v.showInputBox(TypeMoq.It.isAny()), TypeMoq.Times.once()); + const listPrompt = new InputPrompt(question, vscodeWrapper); + + await listPrompt.render(); + + expect(vscodeWrapper.showInputBox).to.have.been.calledOnce; }); - test.skip("Test prompt an error question should throw", () => { - let vscodeWrapper = TypeMoq.Mock.ofType(VscodeWrapper, TypeMoq.MockBehavior.Loose); + test.skip("Test prompt an error question should throw", async () => { + const vscodeWrapper = sandbox.createStubInstance(VscodeWrapper); const errorQuestion = { default: new Error("test"), placeHolder: undefined, }; - vscodeWrapper - .setup((v) => v.showInputBox(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(undefined)); - let listPrompt = new InputPrompt(errorQuestion, vscodeWrapper.object); - listPrompt.render(); - vscodeWrapper.verify((v) => v.showInputBox(TypeMoq.It.isAny()), TypeMoq.Times.once()); + vscodeWrapper.showInputBox.resolves(undefined); + const listPrompt = new InputPrompt(errorQuestion, vscodeWrapper); + + await listPrompt.render(); + + expect(vscodeWrapper.showInputBox).to.have.been.calledOnce; }); - test("Test prompt question with default message", () => { - let vscodeWrapper = TypeMoq.Mock.ofType(VscodeWrapper, TypeMoq.MockBehavior.Loose); + test("Test prompt question with default message", async () => { + const vscodeWrapper = sandbox.createStubInstance(VscodeWrapper); const defaultQuestion = { default: "test_default", }; - vscodeWrapper - .setup((v) => v.showInputBox(TypeMoq.It.isAny())) - .returns(() => Promise.resolve("")); - let listPrompt = new InputPrompt(defaultQuestion, vscodeWrapper.object); - listPrompt.render(); - vscodeWrapper.verify((v) => v.showInputBox(TypeMoq.It.isAny()), TypeMoq.Times.once()); + vscodeWrapper.showInputBox.resolves(""); + const listPrompt = new InputPrompt(defaultQuestion, vscodeWrapper); + + await listPrompt.render(); + + expect(vscodeWrapper.showInputBox).to.have.been.calledOnce; }); - test("Test prompt question with validation error", () => { - let vscodeWrapper = TypeMoq.Mock.ofType(VscodeWrapper, TypeMoq.MockBehavior.Loose); - vscodeWrapper - .setup((v) => v.showInputBox(TypeMoq.It.isAny())) - .returns(() => Promise.resolve("")); + test("Test prompt question with validation error", async () => { + const vscodeWrapper = sandbox.createStubInstance(VscodeWrapper); + vscodeWrapper.showInputBox.onFirstCall().resolves(""); + vscodeWrapper.showInputBox.onSecondCall().resolves("valid"); + let attempts = 0; const validationQuestion = { default: "test", - validate: (e) => false, + validate: () => { + attempts += 1; + return attempts === 1 ? "validation error" : undefined; + }, }; - let listPrompt = new InputPrompt(validationQuestion, vscodeWrapper.object); - listPrompt.render(); - vscodeWrapper.verify((v) => v.showInputBox(TypeMoq.It.isAny()), TypeMoq.Times.once()); + const listPrompt = new InputPrompt(validationQuestion, vscodeWrapper); + + await listPrompt.render(); + + expect(vscodeWrapper.showInputBox).to.have.been.calledTwice; }); }); diff --git a/test/unit/mainController.test.ts b/test/unit/mainController.test.ts index 419ef77677..8b8e8d8d04 100644 --- a/test/unit/mainController.test.ts +++ b/test/unit/mainController.test.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from "assert"; -import * as TypeMoq from "typemoq"; +import * as sinon from "sinon"; +import sinonChai from "sinon-chai"; +import { expect } from "chai"; +import * as chai from "chai"; import * as vscode from "vscode"; import * as Extension from "../../src/extension"; import MainController from "../../src/controllers/mainController"; @@ -15,74 +17,83 @@ import { activateExtension } from "./utils"; import { SchemaCompareEndpointInfo } from "vscode-mssql"; import * as Constants from "../../src/constants/constants"; +chai.use(sinonChai); + suite("MainController Tests", function () { + let sandbox: sinon.SinonSandbox; let mainController: MainController; - let connectionManager: TypeMoq.IMock; + let connectionManager: sinon.SinonStubbedInstance; setup(async () => { + sandbox = sinon.createSandbox(); // Need to activate the extension to get the mainController await activateExtension(); // Using the mainController that was instantiated with the extension mainController = await Extension.getController(); - // Setting up a mocked connectionManager - let mockContext: TypeMoq.IMock = - TypeMoq.Mock.ofType(); - connectionManager = TypeMoq.Mock.ofType( - ConnectionManager, - TypeMoq.MockBehavior.Loose, - mockContext.object, - ); - mainController.connectionManager = connectionManager.object; - mainController.sqlDocumentService["_connectionMgr"] = connectionManager.object; + // Setting up a stubbed connectionManager + connectionManager = sandbox.createStubInstance(ConnectionManager); + mainController.connectionManager = connectionManager; + (mainController.sqlDocumentService as any)["_connectionMgr"] = connectionManager; + }); + + teardown(() => { + sandbox.restore(); }); test("validateTextDocumentHasFocus returns false if there is no active text document", () => { - let vscodeWrapperMock: TypeMoq.IMock = TypeMoq.Mock.ofType(VscodeWrapper); - vscodeWrapperMock.setup((x) => x.activeTextEditorUri).returns(() => undefined); - let controller: MainController = new MainController( + const vscodeWrapper = sandbox.createStubInstance(VscodeWrapper); + let getterCalls = 0; + sandbox.stub(vscodeWrapper, "activeTextEditorUri").get(() => { + getterCalls += 1; + return undefined; + }); + const controller: MainController = new MainController( TestExtensionContext.object, undefined, // ConnectionManager - vscodeWrapperMock.object, + vscodeWrapper, ); - let result = (controller as any).validateTextDocumentHasFocus(); - assert.equal( + const result = (controller as any).validateTextDocumentHasFocus(); + + expect( result, - false, "Expected validateTextDocumentHasFocus to return false when the active document URI is undefined", - ); - vscodeWrapperMock.verify((x) => x.activeTextEditorUri, TypeMoq.Times.once()); + ).to.be.false; + expect(getterCalls).to.equal(1); }); test("validateTextDocumentHasFocus returns true if there is an active text document", () => { - let vscodeWrapperMock: TypeMoq.IMock = TypeMoq.Mock.ofType(VscodeWrapper); - vscodeWrapperMock.setup((x) => x.activeTextEditorUri).returns(() => "test_uri"); - let controller: MainController = new MainController( + const vscodeWrapper = sandbox.createStubInstance(VscodeWrapper); + sandbox.stub(vscodeWrapper, "activeTextEditorUri").get(() => "test_uri"); + const controller: MainController = new MainController( TestExtensionContext.object, undefined, // ConnectionManager - vscodeWrapperMock.object, + vscodeWrapper, ); - let result = (controller as any).validateTextDocumentHasFocus(); - assert.equal( + const result = (controller as any).validateTextDocumentHasFocus(); + + expect( result, - true, "Expected validateTextDocumentHasFocus to return true when the active document URI is not undefined", - ); + ).to.be.true; }); test("onManageProfiles should call the connection manager to manage profiles", async () => { - let vscodeWrapperMock: TypeMoq.IMock = TypeMoq.Mock.ofType(VscodeWrapper); - connectionManager.setup((c) => c.onManageProfiles()); - let controller: MainController = new MainController( + const vscodeWrapper = sandbox.createStubInstance(VscodeWrapper); + connectionManager.onManageProfiles.resolves(); + + const controller: MainController = new MainController( TestExtensionContext.object, - connectionManager.object, - vscodeWrapperMock.object, + connectionManager, + vscodeWrapper, ); + await controller.onManageProfiles(); - connectionManager.verify((c) => c.onManageProfiles(), TypeMoq.Times.once()); + + expect(connectionManager.onManageProfiles).to.have.been.calledOnce; }); test("runComparison command should call onSchemaCompare on the controller", async () => { @@ -121,18 +132,10 @@ suite("MainController Tests", function () { gotRunComparison = wrapped.runComparison ?? false; } - assert.equal(called, true, "Expected onSchemaCompare to be called"); - assert.deepStrictEqual( - gotMaybeSource, - src, - "Expected source passed through to handler", - ); - assert.deepStrictEqual( - gotMaybeTarget, - tgt, - "Expected target passed through to handler", - ); - assert.equal(gotRunComparison, false, "Expected runComparison to be false"); + expect(called, "Expected onSchemaCompare to be called").to.be.true; + expect(gotMaybeSource, "Expected source passed through to handler").to.deep.equal(src); + expect(gotMaybeTarget, "Expected target passed through to handler").to.deep.equal(tgt); + expect(gotRunComparison, "Expected runComparison to be false").to.be.false; } finally { // restore original handler so the test doesn't leak state (mainController as any).onSchemaCompare = originalHandler; @@ -160,12 +163,11 @@ suite("MainController Tests", function () { testProjectPath, ); - assert.equal(called, true, "Expected onPublishDatabaseProject to be called"); - assert.deepStrictEqual( + expect(called, "Expected onPublishDatabaseProject to be called").to.be.true; + expect( gotProjectFilePath, - testProjectPath, "Expected projectFilePath passed through to handler", - ); + ).to.equal(testProjectPath); } finally { // restore original handler so the test doesn't leak state mainController.onPublishDatabaseProject = originalHandler; diff --git a/test/unit/schemaCompareWebViewController.test.ts b/test/unit/schemaCompareWebViewController.test.ts index e2b27667e0..d00ba7484e 100644 --- a/test/unit/schemaCompareWebViewController.test.ts +++ b/test/unit/schemaCompareWebViewController.test.ts @@ -3,12 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from "assert"; -import * as vscode from "vscode"; -import * as TypeMoq from "typemoq"; import * as sinon from "sinon"; +import sinonChai from "sinon-chai"; +import { expect } from "chai"; +import * as chai from "chai"; +import * as vscode from "vscode"; import * as mssql from "vscode-mssql"; +chai.use(sinonChai); + import { SchemaCompareWebViewController } from "../../src/schemaCompare/schemaCompareWebViewController"; import { TreeNodeInfo } from "../../src/objectExplorer/nodes/treeNodeInfo"; import ConnectionManager, { ConnectionInfo } from "../../src/controllers/connectionManager"; @@ -21,20 +24,23 @@ import { } from "../../src/sharedInterfaces/schemaCompare"; import * as scUtils from "../../src/schemaCompare/schemaCompareUtils"; import VscodeWrapper from "../../src/controllers/vscodeWrapper"; +import { IconUtils } from "../../src/utils/iconUtils"; import { IConnectionProfile } from "../../src/models/interfaces"; import { AzureAuthType } from "../../src/models/contracts/azure"; +import { SchemaCompareService } from "../../src/services/schemaCompareService"; suite("SchemaCompareWebViewController Tests", () => { let controller: SchemaCompareWebViewController; let sandbox: sinon.SinonSandbox; let mockContext: vscode.ExtensionContext; let treeNode: TreeNodeInfo; - let mockSchemaCompareService: TypeMoq.IMock; - let mockConnectionManager: TypeMoq.IMock; - let mockConnectionInfo: TypeMoq.IMock; - let mockServerConnInfo: TypeMoq.IMock; + let mockConnectionInfo: ConnectionInfo; + let mockServerConnInfo: mssql.IConnectionInfo; let mockInitialState: SchemaCompareWebViewState; - let vscodeWrapper: TypeMoq.IMock; + let schemaCompareService: mssql.ISchemaCompareService; + let connectionManagerStub: sinon.SinonStubbedInstance; + let vscodeWrapperStub: sinon.SinonStubbedInstance; + let connectionChangedEmitter: vscode.EventEmitter; const schemaCompareWebViewTitle = "Schema Compare"; const operationId = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE"; let generateOperationIdStub: sinon.SinonStub<[], string>; @@ -216,6 +222,8 @@ suite("SchemaCompareWebViewController Tests", () => { extensionPath: "path", } as unknown as vscode.ExtensionContext; + IconUtils.initialize(mockContext.extensionUri); + let context: mssql.TreeNodeContextValue = { type: "", subType: "", @@ -293,60 +301,57 @@ suite("SchemaCompareWebViewController Tests", () => { undefined, ); - mockSchemaCompareService = TypeMoq.Mock.ofType(); + schemaCompareService = sandbox.createStubInstance(SchemaCompareService); - vscodeWrapper = TypeMoq.Mock.ofType(VscodeWrapper, TypeMoq.MockBehavior.Loose); + connectionManagerStub = sandbox.createStubInstance(ConnectionManager); + connectionChangedEmitter = new vscode.EventEmitter(); + Object.defineProperty(connectionManagerStub, "onConnectionsChanged", { + value: connectionChangedEmitter.event, + }); + connectionManagerStub.getUriForConnection.returns("localhost,1433_undefined_sa_undefined"); - mockConnectionManager = TypeMoq.Mock.ofType(); - mockConnectionManager - .setup((mgr) => mgr.getUriForConnection(TypeMoq.It.isAny())) - .returns(() => "localhost,1433_undefined_sa_undefined"); + mockServerConnInfo = { + server: "server1", + profileName: "profile1", + } as unknown as mssql.IConnectionInfo; - mockServerConnInfo = TypeMoq.Mock.ofType(); - mockServerConnInfo.setup((info) => info.server).returns(() => "server1"); - mockServerConnInfo.setup((info: any) => info.profileName).returns(() => "profile1"); + mockConnectionInfo = { + credentials: mockServerConnInfo, + } as unknown as ConnectionInfo; - mockConnectionInfo = TypeMoq.Mock.ofType(); - mockConnectionInfo - .setup((info) => info.credentials) - .returns(() => mockServerConnInfo.object); + sandbox.stub(connectionManagerStub, "activeConnections").get(() => ({ + conn_uri: mockConnectionInfo, + })); - mockConnectionManager - .setup((mgr) => mgr.activeConnections) - .returns(() => ({ - conn_uri: mockConnectionInfo.object, - })); + connectionManagerStub.listDatabases.resolves(["db1", "db2"]); - mockConnectionManager - .setup((mgr) => mgr.listDatabases(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(["db1", "db2"])); + vscodeWrapperStub = sandbox.createStubInstance(VscodeWrapper); generateOperationIdStub = sandbox.stub(scUtils, "generateOperationId").returns(operationId); controller = new SchemaCompareWebViewController( mockContext, - vscodeWrapper.object, + vscodeWrapperStub, treeNode, undefined, false, - mockSchemaCompareService.object, - mockConnectionManager.object, + schemaCompareService, + connectionManagerStub, deploymentOptionsResultMock, schemaCompareWebViewTitle, ); }); teardown(() => { - generateOperationIdStub.restore(); + generateOperationIdStub?.restore(); + connectionChangedEmitter?.dispose(); sandbox.restore(); }); test("controller - initialize title - is 'Schema Compare'", () => { - assert.deepStrictEqual( - controller.panel.title, + expect(controller.panel.title, "Webview Title should match").to.equal( schemaCompareWebViewTitle, - "Webview Title should match", ); }); @@ -367,12 +372,12 @@ suite("SchemaCompareWebViewController Tests", () => { }; controller = new SchemaCompareWebViewController( mockContext, - vscodeWrapper.object, + vscodeWrapperStub, undefined, mockTarget, false, - mockSchemaCompareService.object, - mockConnectionManager.object, + schemaCompareService, + connectionManagerStub, deploymentOptionsResultMock, schemaCompareWebViewTitle, ); @@ -381,16 +386,16 @@ suite("SchemaCompareWebViewController Tests", () => { await controller.start(undefined, mockTarget, false); - sinon.assert.calledTwice(launchStub); + expect(launchStub).to.have.been.calledTwice; // First call: from constructor // Second call: from explicit start const [sourceArg2, targetArg2, runComparisonArg2] = launchStub.secondCall.args; // You can assert the second call matches your expectations - assert.strictEqual(sourceArg2, undefined, "source should be undefined"); - assert.deepStrictEqual(targetArg2, mockTarget, "target should match mockTarget"); - assert.strictEqual(runComparisonArg2, false, "runComparison should be false"); + expect(sourceArg2, "source should be undefined").to.be.undefined; + expect(targetArg2, "target should match mockTarget").to.deep.equal(mockTarget); + expect(runComparisonArg2, "runComparison should be false").to.be.false; launchStub.restore(); }); @@ -426,12 +431,12 @@ suite("SchemaCompareWebViewController Tests", () => { }; controller = new SchemaCompareWebViewController( mockContext, - vscodeWrapper.object, + vscodeWrapperStub, mockSource, mockTarget, true, - mockSchemaCompareService.object, - mockConnectionManager.object, + schemaCompareService, + connectionManagerStub, deploymentOptionsResultMock, schemaCompareWebViewTitle, ); @@ -440,13 +445,13 @@ suite("SchemaCompareWebViewController Tests", () => { await controller.start(mockSource, mockTarget, true); - sinon.assert.calledTwice(launchStub); + expect(launchStub).to.have.been.calledTwice; // Second call: from explicit start const [sourceArg2, targetArg2, runComparisonArg2] = launchStub.secondCall.args; - assert.deepStrictEqual(sourceArg2, mockSource, "source should match mockSource"); - assert.deepStrictEqual(targetArg2, mockTarget, "target should match mockTarget"); - assert.strictEqual(runComparisonArg2, true, "runComparison should be true"); + expect(sourceArg2, "source should match mockSource").to.deep.equal(mockSource); + expect(targetArg2, "target should match mockTarget").to.deep.equal(mockTarget); + expect(runComparisonArg2, "runComparison should be true").to.be.true; launchStub.restore(); }); @@ -470,12 +475,12 @@ suite("SchemaCompareWebViewController Tests", () => { const scController = new SchemaCompareWebViewController( mockContext, - vscodeWrapper.object, + vscodeWrapperStub, mockSqlProjectNode, undefined, false, - mockSchemaCompareService.object, - mockConnectionManager.object, + schemaCompareService, + connectionManagerStub, deploymentOptionsResultMock, schemaCompareWebViewTitle, ); @@ -494,11 +499,10 @@ suite("SchemaCompareWebViewController Tests", () => { extractTarget: 5, }; - assert.deepEqual( + expect( scController.state.sourceEndpointInfo, - expected, "sourceEndpointInfo should match the expected path", - ); + ).to.deep.equal(expected); }); test("compare reducer - when called - completes successfully", async () => { @@ -523,18 +527,15 @@ suite("SchemaCompareWebViewController Tests", () => { payload, ); - assert.deepEqual( + expect( compareStub.firstCall.args, - [operationId, TaskExecutionMode.execute, payload, mockSchemaCompareService.object], "compare should be called with correct arguments", - ); + ).to.deep.equal([operationId, TaskExecutionMode.execute, payload, schemaCompareService]); - assert.ok(compareStub.calledOnce, "compare should be called once"); + expect(compareStub, "compare should be called once").to.have.been.calledOnce; - assert.deepEqual( - result.schemaCompareResult, + expect(result.schemaCompareResult, "compare should return expected result").to.deep.equal( expectedCompareResultMock, - "compare should return expected result", ); compareStub.restore(); @@ -560,19 +561,17 @@ suite("SchemaCompareWebViewController Tests", () => { payload, ); - assert.ok(generateScriptStub.calledOnce, "generateScript should be called once"); + expect(generateScriptStub, "generateScript should be called once").to.have.been.calledOnce; - assert.deepEqual( + expect( generateScriptStub.firstCall.args, - [operationId, TaskExecutionMode.script, payload, mockSchemaCompareService.object], "generateScript should be called with correct arguments", - ); + ).to.deep.equal([operationId, TaskExecutionMode.script, payload, schemaCompareService]); - assert.deepEqual( + expect( result.generateScriptResultStatus, - expectedScriptResultMock, "generateScript should return expected result", - ); + ).to.deep.equal(expectedScriptResultMock); generateScriptStub.restore(); }); @@ -597,22 +596,18 @@ suite("SchemaCompareWebViewController Tests", () => { payload, ); - assert.ok( - publishDatabaseChangesStub.calledOnce, - "publishDatabaseChanges should be called once", - ); + expect(publishDatabaseChangesStub, "publishDatabaseChanges should be called once").to.have + .been.calledOnce; - assert.deepEqual( + expect( publishDatabaseChangesStub.firstCall.args, - [operationId, TaskExecutionMode.execute, payload, mockSchemaCompareService.object], "publishDatabaseChanges should be called with correct arguments", - ); + ).to.deep.equal([operationId, TaskExecutionMode.execute, payload, schemaCompareService]); - assert.deepEqual( + expect( actualResult.publishDatabaseChangesResultStatus, - expectedResultMock, "publishDatabaseChanges should return expected result", - ); + ).to.deep.equal(expectedResultMock); publishDatabaseChangesStub.restore(); }); @@ -641,22 +636,18 @@ suite("SchemaCompareWebViewController Tests", () => { payload, ); - assert.ok( - publishProjectChangesStub.calledOnce, - "publishProjectChanges should be called once", - ); + expect(publishProjectChangesStub, "publishProjectChanges should be called once").to.have + .been.calledOnce; - assert.deepEqual( + expect( publishProjectChangesStub.firstCall.args, - [operationId, payload, mockSchemaCompareService.object], "publishProjectChanges should be called with correct arguments", - ); + ).to.deep.equal([operationId, payload, schemaCompareService]); - assert.deepEqual( + expect( actualResult.schemaComparePublishProjectResult, - expectedResultMock, "publishProjectChanges should return expected result", - ); + ).to.deep.equal(expectedResultMock); publishProjectChangesStub.restore(); }); @@ -673,19 +664,18 @@ suite("SchemaCompareWebViewController Tests", () => { payload, ); - assert.ok(getDefaultOptionsStub.calledOnce, "getDefaultOptions should be called once"); + expect(getDefaultOptionsStub, "getDefaultOptions should be called once").to.have.been + .calledOnce; - assert.deepEqual( + expect( getDefaultOptionsStub.firstCall.args, - [mockSchemaCompareService.object], "getDefaultOptions should be called with correct arguments", - ); + ).to.deep.equal([schemaCompareService]); - assert.deepEqual( + expect( actualResult.defaultDeploymentOptionsResult, - deploymentOptionsResultMock, "getDefaultOptions should return expected result", - ); + ).to.deep.equal(deploymentOptionsResultMock); getDefaultOptionsStub.restore(); }); @@ -724,19 +714,18 @@ suite("SchemaCompareWebViewController Tests", () => { payload, ); - assert.ok(publishProjectChangesStub.calledOnce, "includeExcludeNode should be called once"); + expect(publishProjectChangesStub, "includeExcludeNode should be called once").to.have.been + .calledOnce; - assert.deepEqual( + expect( publishProjectChangesStub.firstCall.args, - [operationId, TaskExecutionMode.execute, payload, mockSchemaCompareService.object], "includeExcludeNode should be called with correct arguments", - ); + ).to.deep.equal([operationId, TaskExecutionMode.execute, payload, schemaCompareService]); - assert.deepEqual( + expect( actualResult.schemaCompareIncludeExcludeResult, - expectedResultMock, "includeExcludeNode should return expected result", - ); + ).to.deep.equal(expectedResultMock); publishProjectChangesStub.restore(); }); @@ -771,31 +760,26 @@ suite("SchemaCompareWebViewController Tests", () => { payload, ); - assert.ok( - showOpenDialogForScmpStub.calledOnce, - "showOpenDialogForScmp should be called once", - ); + expect(showOpenDialogForScmpStub, "showOpenDialogForScmp should be called once").to.have + .been.calledOnce; - assert.ok(openScmpStub.calledOnce, "openScmp should be called once"); + expect(openScmpStub, "openScmp should be called once").to.have.been.calledOnce; - assert.deepEqual( + expect( openScmpStub.firstCall.args, - [filePath, mockSchemaCompareService.object], "openScmp should be called with correct arguments", - ); + ).to.deep.equal([filePath, schemaCompareService]); - assert.deepEqual( + expect( actualResult.schemaCompareOpenScmpResult, - expectedResultMock, "openScmp should return expected result", - ); + ).to.deep.equal(expectedResultMock); // Verify that intermediaryOptionsResult is updated with loaded options - assert.deepEqual( + expect( actualResult.intermediaryOptionsResult?.defaultDeploymentOptions, - expectedResultMock.deploymentOptions, "intermediaryOptionsResult should be updated with loaded deployment options", - ); + ).to.deep.equal(expectedResultMock.deploymentOptions); openScmpStub.restore(); }); @@ -823,33 +807,29 @@ suite("SchemaCompareWebViewController Tests", () => { payload, ); - assert.ok( - showSaveDialogForScmpStub.calledOnce, - "showSaveDialogForScmp should be called once", - ); + expect(showSaveDialogForScmpStub, "showSaveDialogForScmp should be called once").to.have + .been.calledOnce; - assert.ok(publishProjectChangesStub.calledOnce, "saveScmp should be called once"); + expect(publishProjectChangesStub, "saveScmp should be called once").to.have.been.calledOnce; - assert.deepEqual( + expect( publishProjectChangesStub.firstCall.args, - [ - databaseSourceEndpointInfo, - undefined, - TaskExecutionMode.execute, - deploymentOptions, - savePath, - [], - [], - mockSchemaCompareService.object, - ], "saveScmp should be called with correct arguments", - ); + ).to.deep.equal([ + databaseSourceEndpointInfo, + undefined, + TaskExecutionMode.execute, + deploymentOptions, + savePath, + [], + [], + schemaCompareService, + ]); - assert.deepEqual( + expect( actualResult.saveScmpResultStatus, - expectedResultMock, "saveScmp should return expected result", - ); + ).to.deep.equal(expectedResultMock); publishProjectChangesStub.restore(); }); @@ -871,19 +851,17 @@ suite("SchemaCompareWebViewController Tests", () => { payload, ); - assert.ok(publishProjectChangesStub.calledOnce, "cancel should be called once"); + expect(publishProjectChangesStub, "cancel should be called once").to.have.been.calledOnce; - assert.deepEqual( + expect( publishProjectChangesStub.firstCall.args, - [operationId, mockSchemaCompareService.object], "cancel should be called with correct arguments", - ); + ).to.deep.equal([operationId, schemaCompareService]); - assert.deepEqual( + expect( actualResult.cancelResultStatus, - expectedResultMock, "cancel should be called with correct arguments", - ); + ).to.deep.equal(expectedResultMock); publishProjectChangesStub.restore(); }); @@ -898,11 +876,10 @@ suite("SchemaCompareWebViewController Tests", () => { const expectedResult = { conn_uri: { profileName: "profile1", server: "server1" } }; - assert.deepEqual( + expect( actualResult.activeServers, - expectedResult, "listActiveServers should return: {conn_uri: {profileName: 'profile1', server: 'server1'}}", - ); + ).to.deep.equal(expectedResult); }); test("listDatabasesForActiveServer reducer - when called - returns: ['db1', 'db2']", async () => { @@ -914,11 +891,10 @@ suite("SchemaCompareWebViewController Tests", () => { const expectedResult = ["db1", "db2"]; - assert.deepEqual( + expect( actualResult.databases, - expectedResult, "listActiveServers should return ['db1', 'db2']", - ); + ).to.deep.equal(expectedResult); }); test("selectFile reducer - when called - returns correct auxiliary endpoint info", async () => { @@ -949,11 +925,10 @@ suite("SchemaCompareWebViewController Tests", () => { targetScripts: [], }; - assert.deepEqual( + expect( actualResult.auxiliaryEndpointInfo, - expectedResult, "selectFile should return the expected auxiliary endpoint info", - ); + ).to.deep.equal(expectedResult); }); test("confirmSelectedFile reducer - when called - auxiliary endpoint info becomes target endpoint info", async () => { @@ -983,11 +958,10 @@ suite("SchemaCompareWebViewController Tests", () => { payload, ); - assert.deepEqual( + expect( actualResult.targetEndpointInfo, - expectedResult, "confirmSelectedSchema should make auxiliary endpoint info the target endpoint info", - ); + ).to.deep.equal(expectedResult); }); test("includeExcludeAllNodes reducer - when includeRequest is false - all nodes are excluded", async () => { @@ -1056,13 +1030,13 @@ suite("SchemaCompareWebViewController Tests", () => { payload, ); - assert.ok(includeExcludeAllStub.calledOnce, "includeExcludeAllNodes should be called once"); + expect(includeExcludeAllStub, "includeExcludeAllNodes should be called once").to.have.been + .calledOnce; - assert.deepEqual( + expect( actualResult.schemaCompareResult.differences, - expectedResult.allIncludedOrExcludedDifferences, "includeExcludeAllNodes should return the expected result", - ); + ).to.deep.equal(expectedResult.allIncludedOrExcludedDifferences); includeExcludeAllStub.restore(); }); @@ -1133,13 +1107,13 @@ suite("SchemaCompareWebViewController Tests", () => { payload, ); - assert.ok(includeExcludeAllStub.calledOnce, "includeExcludeAllNodes should be called once"); + expect(includeExcludeAllStub, "includeExcludeAllNodes should be called once").to.have.been + .calledOnce; - assert.deepEqual( + expect( actualResult.schemaCompareResult.differences, - expectedResult.allIncludedOrExcludedDifferences, "includeExcludeAllNodes should return the expected result", - ); + ).to.deep.equal(expectedResult.allIncludedOrExcludedDifferences); includeExcludeAllStub.restore(); }); @@ -1173,22 +1147,22 @@ suite("SchemaCompareWebViewController Tests", () => { const excludeObjectTypes = actualResult.intermediaryOptionsResult.defaultDeploymentOptions.excludeObjectTypes .value; - assert.ok( + expect( excludeObjectTypes.includes("Aggregates"), "Aggregates should be added to exclusion list", - ); - assert.ok( + ).to.be.true; + expect( excludeObjectTypes.includes("ApplicationRoles"), "ApplicationRoles should be added to exclusion list", - ); - assert.ok( + ).to.be.true; + expect( excludeObjectTypes.includes("ServerTriggers"), "Existing ServerTriggers should remain in exclusion list", - ); - assert.ok( + ).to.be.true; + expect( excludeObjectTypes.includes("Routes"), "Existing Routes should remain in exclusion list", - ); + ).to.be.true; }); test("intermediaryIncludeObjectTypesBulkChanged reducer - when unchecking object types - removes them from exclusion list", async () => { @@ -1220,22 +1194,22 @@ suite("SchemaCompareWebViewController Tests", () => { const excludeObjectTypes = actualResult.intermediaryOptionsResult.defaultDeploymentOptions.excludeObjectTypes .value; - assert.ok( + expect( !excludeObjectTypes.includes("Aggregates"), "Aggregates should be removed from exclusion list", - ); - assert.ok( + ).to.be.true; + expect( !excludeObjectTypes.includes("ApplicationRoles"), "ApplicationRoles should be removed from exclusion list", - ); - assert.ok( + ).to.be.true; + expect( excludeObjectTypes.includes("ServerTriggers"), "Existing ServerTriggers should remain in exclusion list", - ); - assert.ok( + ).to.be.true; + expect( excludeObjectTypes.includes("Routes"), "Existing Routes should remain in exclusion list", - ); + ).to.be.true; }); test("intermediaryIncludeObjectTypesBulkChanged reducer - when checking already included types - no duplicates added", async () => { @@ -1267,23 +1241,19 @@ suite("SchemaCompareWebViewController Tests", () => { const excludeObjectTypes = actualResult.intermediaryOptionsResult.defaultDeploymentOptions.excludeObjectTypes .value; - assert.strictEqual( - excludeObjectTypes.length, - 1, - "Should only have 1 item in exclusion list", - ); - assert.ok( + expect(excludeObjectTypes.length, "Should only have 1 item in exclusion list").to.equal(1); + expect( excludeObjectTypes.includes("ServerTriggers"), "ServerTriggers should remain in exclusion list", - ); - assert.ok( + ).to.be.true; + expect( !excludeObjectTypes.includes("Aggregates"), "Aggregates should not be in exclusion list", - ); - assert.ok( + ).to.be.true; + expect( !excludeObjectTypes.includes("ApplicationRoles"), "ApplicationRoles should not be in exclusion list", - ); + ).to.be.true; }); test("intermediaryIncludeObjectTypesBulkChanged reducer - when unchecking already excluded types - no duplicates added", async () => { @@ -1316,20 +1286,17 @@ suite("SchemaCompareWebViewController Tests", () => { actualResult.intermediaryOptionsResult.defaultDeploymentOptions.excludeObjectTypes .value; const aggregatesCount = excludeObjectTypes.filter((type) => type === "Aggregates").length; - assert.strictEqual( - aggregatesCount, - 1, - "Aggregates should appear only once in exclusion list", - ); - assert.ok( + expect(aggregatesCount, "Aggregates should appear only once in exclusion list").to.equal(1); + expect( excludeObjectTypes.includes("ApplicationRoles"), "ApplicationRoles should be added to exclusion list", - ); - assert.ok( + ).to.be.true; + expect( excludeObjectTypes.includes("ServerTriggers"), "ServerTriggers should remain in exclusion list", - ); - assert.ok(excludeObjectTypes.includes("Routes"), "Routes should remain in exclusion list"); + ).to.be.true; + expect(excludeObjectTypes.includes("Routes"), "Routes should remain in exclusion list").to + .be.true; }); test("intermediaryIncludeObjectTypesBulkChanged reducer - case insensitive comparison works correctly", async () => { @@ -1361,11 +1328,10 @@ suite("SchemaCompareWebViewController Tests", () => { const excludeObjectTypes = actualResult.intermediaryOptionsResult.defaultDeploymentOptions.excludeObjectTypes .value; - assert.strictEqual( + expect( excludeObjectTypes.length, - 0, "All object types should be removed from exclusion list", - ); + ).to.equal(0); }); test("intermediaryIncludeObjectTypesBulkChanged reducer - with empty keys array - no changes made", async () => { @@ -1397,11 +1363,10 @@ suite("SchemaCompareWebViewController Tests", () => { const excludeObjectTypes = actualResult.intermediaryOptionsResult.defaultDeploymentOptions.excludeObjectTypes .value; - assert.deepStrictEqual( - excludeObjectTypes, - ["ServerTriggers", "Routes"], - "Exclusion list should remain unchanged", - ); + expect(excludeObjectTypes, "Exclusion list should remain unchanged").to.deep.equal([ + "ServerTriggers", + "Routes", + ]); }); test("intermediaryGeneralOptionsBulkChanged reducer - when setting options to true - updates all specified options", async () => { @@ -1445,21 +1410,18 @@ suite("SchemaCompareWebViewController Tests", () => { const booleanOptions = actualResult.intermediaryOptionsResult.defaultDeploymentOptions .booleanOptionsDictionary; - assert.strictEqual( + expect( booleanOptions.allowDropBlockingAssemblies.value, - true, "allowDropBlockingAssemblies should be set to true", - ); - assert.strictEqual( + ).to.equal(true); + expect( booleanOptions.allowExternalLanguagePaths.value, - true, "allowExternalLanguagePaths should be set to true", - ); - assert.strictEqual( + ).to.equal(true); + expect( booleanOptions.allowExternalLibraryPaths.value, - true, "allowExternalLibraryPaths should remain unchanged (was already true)", - ); + ).to.equal(true); }); test("intermediaryGeneralOptionsBulkChanged reducer - when setting options to false - updates all specified options", async () => { @@ -1503,21 +1465,18 @@ suite("SchemaCompareWebViewController Tests", () => { const booleanOptions = actualResult.intermediaryOptionsResult.defaultDeploymentOptions .booleanOptionsDictionary; - assert.strictEqual( + expect( booleanOptions.allowDropBlockingAssemblies.value, - false, "allowDropBlockingAssemblies should be set to false", - ); - assert.strictEqual( + ).to.equal(false); + expect( booleanOptions.allowExternalLanguagePaths.value, - false, "allowExternalLanguagePaths should be set to false", - ); - assert.strictEqual( + ).to.equal(false); + expect( booleanOptions.allowExternalLibraryPaths.value, - false, "allowExternalLibraryPaths should remain unchanged (was already false)", - ); + ).to.equal(false); }); test("intermediaryGeneralOptionsBulkChanged reducer - when key does not exist - ignores non-existent options", async () => { @@ -1556,29 +1515,23 @@ suite("SchemaCompareWebViewController Tests", () => { const booleanOptions = actualResult.intermediaryOptionsResult.defaultDeploymentOptions .booleanOptionsDictionary; - assert.strictEqual( + expect( booleanOptions.allowDropBlockingAssemblies.value, - true, "allowDropBlockingAssemblies should be set to true", - ); - assert.strictEqual( + ).to.equal(true); + expect( booleanOptions.allowExternalLanguagePaths.value, - true, "allowExternalLanguagePaths should remain unchanged", - ); - assert.strictEqual( - Object.keys(booleanOptions).length, - 2, - "No new options should be created", - ); - assert.ok( + ).to.equal(true); + expect(Object.keys(booleanOptions).length, "No new options should be created").to.equal(2); + expect( !booleanOptions.hasOwnProperty("nonExistentOption"), "nonExistentOption should not be created", - ); - assert.ok( + ).to.be.true; + expect( !booleanOptions.hasOwnProperty("anotherNonExistentOption"), "anotherNonExistentOption should not be created", - ); + ).to.be.true; }); test("intermediaryGeneralOptionsBulkChanged reducer - with empty keys array - no changes made", async () => { @@ -1617,16 +1570,14 @@ suite("SchemaCompareWebViewController Tests", () => { const booleanOptions = actualResult.intermediaryOptionsResult.defaultDeploymentOptions .booleanOptionsDictionary; - assert.strictEqual( + expect( booleanOptions.allowDropBlockingAssemblies.value, - false, "allowDropBlockingAssemblies should remain unchanged", - ); - assert.strictEqual( + ).to.equal(false); + expect( booleanOptions.allowExternalLanguagePaths.value, - true, "allowExternalLanguagePaths should remain unchanged", - ); + ).to.equal(true); }); test("intermediaryGeneralOptionsBulkChanged reducer - with mixed option states - updates all specified options uniformly", async () => { @@ -1674,21 +1625,18 @@ suite("SchemaCompareWebViewController Tests", () => { const booleanOptions = actualResult.intermediaryOptionsResult.defaultDeploymentOptions .booleanOptionsDictionary; - assert.strictEqual( + expect( booleanOptions.allowDropBlockingAssemblies.value, - false, "allowDropBlockingAssemblies should be set to false", - ); - assert.strictEqual( + ).to.equal(false); + expect( booleanOptions.allowExternalLanguagePaths.value, - false, "allowExternalLanguagePaths should be set to false", - ); - assert.strictEqual( + ).to.equal(false); + expect( booleanOptions.allowExternalLibraryPaths.value, - false, "allowExternalLibraryPaths should be set to false", - ); + ).to.equal(false); }); test("intermediaryGeneralOptionsBulkChanged reducer - preserves option metadata - only changes value property", async () => { @@ -1725,16 +1673,12 @@ suite("SchemaCompareWebViewController Tests", () => { const option = actualResult.intermediaryOptionsResult.defaultDeploymentOptions.booleanOptionsDictionary .allowDropBlockingAssemblies; - assert.strictEqual(option.value, true, "Value should be updated to true"); - assert.strictEqual( - option.description, + expect(option.value, "Value should be updated to true").to.equal(true); + expect(option.description, "Description should remain unchanged").to.equal( originalDescription, - "Description should remain unchanged", ); - assert.strictEqual( - option.displayName, + expect(option.displayName, "Display name should remain unchanged").to.equal( originalDisplayName, - "Display name should remain unchanged", ); }); }); diff --git a/test/unit/serviceClient.test.ts b/test/unit/serviceClient.test.ts index f54dbdbefe..e1bb2ea5c6 100644 --- a/test/unit/serviceClient.test.ts +++ b/test/unit/serviceClient.test.ts @@ -3,239 +3,191 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as TypeMoq from "typemoq"; -import * as assert from "assert"; +import * as sinon from "sinon"; +import sinonChai from "sinon-chai"; +import * as chai from "chai"; +import { expect } from "chai"; import ServerProvider from "../../src/languageservice/server"; import SqlToolsServiceClient from "../../src/languageservice/serviceclient"; import { Logger, LogLevel } from "../../src/models/logger"; import { PlatformInformation } from "../../src/models/platform"; import StatusView from "../../src/views/statusView"; import * as LanguageServiceContracts from "../../src/models/contracts/languageService"; -import { IConfigUtils } from "../../src/languageservice/interfaces"; import ExtConfig from "../../src/configurations/extConfig"; import VscodeWrapper from "../../src/controllers/vscodeWrapper"; +import { stubVscodeWrapper } from "./utils"; + +chai.use(sinonChai); interface IFixture { platformInfo: PlatformInformation; - installedServerPath: string; - downloadedServerPath: string; + installedServerPath: string | undefined; + downloadedServerPath: string | undefined; } suite("Service Client tests", () => { - let testConfig: TypeMoq.IMock; - let testServiceProvider: TypeMoq.IMock; - let logger = new Logger((text) => console.log(text), LogLevel.Verbose, false); - let testStatusView: TypeMoq.IMock; - let vscodeWrapper: TypeMoq.IMock; + let sandbox: sinon.SinonSandbox; + let testConfig: sinon.SinonStubbedInstance; + let testServiceProvider: sinon.SinonStubbedInstance; + const logger = new Logger((text) => console.log(text), LogLevel.Verbose, false); + let testStatusView: sinon.SinonStubbedInstance; + let vscodeWrapper: sinon.SinonStubbedInstance; setup(() => { - testConfig = TypeMoq.Mock.ofType(ExtConfig, TypeMoq.MockBehavior.Loose); - testServiceProvider = TypeMoq.Mock.ofType(ServerProvider, TypeMoq.MockBehavior.Strict); - testStatusView = TypeMoq.Mock.ofType(StatusView); - vscodeWrapper = TypeMoq.Mock.ofType(VscodeWrapper); + sandbox = sinon.createSandbox(); + testConfig = sandbox.createStubInstance(ExtConfig); + testServiceProvider = sandbox.createStubInstance(ServerProvider); + testStatusView = sandbox.createStubInstance(StatusView); + vscodeWrapper = stubVscodeWrapper(sandbox); + }); + + teardown(() => { + sandbox.restore(); }); + function createServiceClient(): SqlToolsServiceClient { + return new SqlToolsServiceClient( + testConfig, + testServiceProvider, + logger, + testStatusView, + vscodeWrapper, + ); + } + function setupMocks(fixture: IFixture): void { - testServiceProvider - .setup((x) => x.downloadServerFiles(fixture.platformInfo.runtimeId)) - .returns(() => { - return Promise.resolve(fixture.downloadedServerPath); - }); - testServiceProvider - .setup((x) => x.getServerPath(fixture.platformInfo.runtimeId)) - .returns(() => { - return Promise.resolve(fixture.installedServerPath); - }); + if (fixture.downloadedServerPath === undefined) { + testServiceProvider.downloadServerFiles.rejects( + new Error("downloadServerFiles should not be called"), + ); + } else { + testServiceProvider.downloadServerFiles.resolves(fixture.downloadedServerPath); + } + testServiceProvider.getServerPath.resolves(fixture.installedServerPath); } - // @cssuh 10/22 - commented this test because it was throwing some random undefined errors - test.skip("initializeForPlatform should not install the service if already exists", (done) => { - let fixture: IFixture = { + test.skip("initializeForPlatform should not install the service if already exists", async () => { + const fixture: IFixture = { installedServerPath: "already installed service", downloadedServerPath: undefined, platformInfo: new PlatformInformation("win32", "x86_64", undefined), }; setupMocks(fixture); - let serviceClient = new SqlToolsServiceClient( - testConfig.object, - testServiceProvider.object, - logger, - testStatusView.object, - vscodeWrapper.object, - ); + const serviceClient = createServiceClient(); - void serviceClient.initializeForPlatform(fixture.platformInfo, undefined).then((result) => { - assert.notEqual(result, undefined); - assert.equal(result.serverPath, fixture.installedServerPath); - assert.equal(result.installedBeforeInitializing, false); - }); - done(); + const result = await serviceClient.initializeForPlatform(fixture.platformInfo, undefined); + + expect(result).to.not.be.undefined; + expect(result?.serverPath).to.equal(fixture.installedServerPath); + expect(result?.installedBeforeInitializing).to.be.false; }); - test.skip("initializeForPlatform should install the service if not exists", (done) => { - let fixture: IFixture = { + test.skip("initializeForPlatform should install the service if not exists", async () => { + const fixture: IFixture = { installedServerPath: undefined, downloadedServerPath: "downloaded service", platformInfo: new PlatformInformation("win32", "x86_64", undefined), }; setupMocks(fixture); - let serviceClient = new SqlToolsServiceClient( - testConfig.object, - testServiceProvider.object, - logger, - testStatusView.object, - vscodeWrapper.object, - ); + const serviceClient = createServiceClient(); - void serviceClient.initializeForPlatform(fixture.platformInfo, undefined).then((result) => { - assert.notEqual(result, undefined); - assert.equal(result.serverPath, fixture.downloadedServerPath); - assert.equal(result.installedBeforeInitializing, true); - }); - done(); + const result = await serviceClient.initializeForPlatform(fixture.platformInfo, undefined); + + expect(result).to.not.be.undefined; + expect(result?.serverPath).to.equal(fixture.downloadedServerPath); + expect(result?.installedBeforeInitializing).to.be.true; }); - test("initializeForPlatform should fail given unsupported platform", () => { - let fixture: IFixture = { + test("initializeForPlatform should fail given unsupported platform", async () => { + const fixture: IFixture = { installedServerPath: "already installed service", downloadedServerPath: undefined, platformInfo: new PlatformInformation("invalid platform", "x86_64", undefined), }; setupMocks(fixture); - let serviceClient = new SqlToolsServiceClient( - testConfig.object, - testServiceProvider.object, - logger, - testStatusView.object, - vscodeWrapper.object, - ); - - return serviceClient - .initializeForPlatform(fixture.platformInfo, undefined) - .catch((error) => { - return assert.equal(error, "Invalid Platform"); - }); + const serviceClient = createServiceClient(); + + try { + await serviceClient.initializeForPlatform(fixture.platformInfo, undefined); + expect.fail("Expected initializeForPlatform to throw for an invalid platform"); + } catch (error) { + expect(error).to.equal("Invalid Platform"); + } }); - // @cssuh 10/22 - commented this test because it was throwing some random undefined errors - test.skip("initializeForPlatform should set v1 given mac 10.11 or lower", (done) => { - let platformInfoMock = TypeMoq.Mock.ofInstance( - new PlatformInformation("darwin", "x86_64", undefined), - ); - platformInfoMock.callBase = true; - platformInfoMock - .setup((x) => x.isMacVersionLessThan(TypeMoq.It.isAnyString())) - .returns(() => true); + test.skip("initializeForPlatform should set v1 given mac 10.11 or lower", async () => { + const platformInfo = new PlatformInformation("darwin", "x86_64", undefined); + const isMacVersionLessThan = sandbox + .stub(platformInfo, "isMacVersionLessThan") + .returns(true); - let fixture: IFixture = { + const fixture: IFixture = { installedServerPath: "already installed service", downloadedServerPath: undefined, - platformInfo: platformInfoMock.object, + platformInfo, }; - let serviceVersion = 0; - testConfig - .setup((x) => x.useServiceVersion(TypeMoq.It.isAnyNumber())) - .callback((num) => (serviceVersion = num)); - setupMocks(fixture); - let serviceClient = new SqlToolsServiceClient( - testConfig.object, - testServiceProvider.object, - logger, - testStatusView.object, - vscodeWrapper.object, - ); + const serviceClient = createServiceClient(); - void serviceClient.initializeForPlatform(fixture.platformInfo, undefined).then((result) => { - assert.equal(serviceVersion, 1); - platformInfoMock.verify( - (x) => x.isMacVersionLessThan(TypeMoq.It.isAny()), - TypeMoq.Times.once(), - ); - testConfig.verify((x) => x.useServiceVersion(1), TypeMoq.Times.once()); - assert.notEqual(result, undefined); - assert.equal(result.serverPath, fixture.installedServerPath); - assert.equal(result.installedBeforeInitializing, false); - }); - done(); + const result = await serviceClient.initializeForPlatform(fixture.platformInfo, undefined); + + expect(isMacVersionLessThan).to.have.been.calledOnce; + expect(testConfig.useServiceVersion).to.have.been.calledOnceWithExactly(1); + expect(result).to.not.be.undefined; + expect(result?.serverPath).to.equal(fixture.installedServerPath); + expect(result?.installedBeforeInitializing).to.be.false; }); - test.skip("initializeForPlatform should ignore service version given mac 10.12 or higher", (done) => { - let platformInfoMock = TypeMoq.Mock.ofInstance( - new PlatformInformation("darwin", "x86_64", undefined), - ); - platformInfoMock.callBase = true; - platformInfoMock - .setup((x) => x.isMacVersionLessThan(TypeMoq.It.isAnyString())) - .returns(() => false); + test.skip("initializeForPlatform should ignore service version given mac 10.12 or higher", async () => { + const platformInfo = new PlatformInformation("darwin", "x86_64", undefined); + const isMacVersionLessThan = sandbox + .stub(platformInfo, "isMacVersionLessThan") + .returns(false); - let fixture: IFixture = { + const fixture: IFixture = { installedServerPath: "already installed service", downloadedServerPath: undefined, - platformInfo: platformInfoMock.object, + platformInfo, }; - let serviceVersion = 0; - testConfig - .setup((x) => x.useServiceVersion(TypeMoq.It.isAnyNumber())) - .callback((num) => (serviceVersion = num)); - setupMocks(fixture); - let serviceClient = new SqlToolsServiceClient( - testConfig.object, - testServiceProvider.object, - logger, - testStatusView.object, - vscodeWrapper.object, - ); + const serviceClient = createServiceClient(); - void serviceClient.initializeForPlatform(fixture.platformInfo, undefined).then((result) => { - assert.equal(serviceVersion, 0); - platformInfoMock.verify( - (x) => x.isMacVersionLessThan(TypeMoq.It.isAny()), - TypeMoq.Times.once(), - ); - testConfig.verify((x) => x.useServiceVersion(1), TypeMoq.Times.never()); - assert.notEqual(result, undefined); - assert.equal(result.serverPath, fixture.installedServerPath); - assert.equal(result.installedBeforeInitializing, false); - }); - done(); + const result = await serviceClient.initializeForPlatform(fixture.platformInfo, undefined); + + expect(isMacVersionLessThan).to.have.been.calledOnce; + expect(testConfig.useServiceVersion).to.not.have.been.called; + expect(result).to.not.be.undefined; + expect(result?.serverPath).to.equal(fixture.installedServerPath); + expect(result?.installedBeforeInitializing).to.be.false; }); - test("handleLanguageServiceStatusNotification should change the UI status", (done) => { - return new Promise((resolve, reject) => { - let fixture: IFixture = { - installedServerPath: "already installed service", - downloadedServerPath: undefined, - platformInfo: new PlatformInformation("win32", "x86_64", undefined), - }; - const testFile = "file:///my/test/file.sql"; - const status = "new status"; - - setupMocks(fixture); - let serviceClient = new SqlToolsServiceClient( - testConfig.object, - testServiceProvider.object, - logger, - testStatusView.object, - vscodeWrapper.object, - ); - let statusChangeParams = new LanguageServiceContracts.StatusChangeParams(); - statusChangeParams.ownerUri = testFile; - statusChangeParams.status = status; - serviceClient - .handleLanguageServiceStatusNotification() - .call(serviceClient, statusChangeParams); - testStatusView.verify( - (x) => x.languageServiceStatusChanged(testFile, status), - TypeMoq.Times.once(), - ); - done(); - }); + test("handleLanguageServiceStatusNotification should change the UI status", () => { + const fixture: IFixture = { + installedServerPath: "already installed service", + downloadedServerPath: undefined, + platformInfo: new PlatformInformation("win32", "x86_64", undefined), + }; + const testFile = "file:///my/test/file.sql"; + const status = "new status"; + + setupMocks(fixture); + const serviceClient = createServiceClient(); + const statusChangeParams = new LanguageServiceContracts.StatusChangeParams(); + statusChangeParams.ownerUri = testFile; + statusChangeParams.status = status; + + serviceClient + .handleLanguageServiceStatusNotification() + .call(serviceClient, statusChangeParams); + + expect(testStatusView.languageServiceStatusChanged).to.have.been.calledOnceWithExactly( + testFile, + status, + ); }); });