From f5c9174c2f07e4c69856b6dfe4d6eb863e71f2c8 Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Fri, 22 Mar 2019 11:30:20 -0700 Subject: [PATCH] Revert "Connection Store Refactor (#4632)" (#4671) This reverts commit 756f77063a3233dfbcf6fbd0887ce7f02212a320. --- src/sql/base/common/map.ts | 45 -- .../viewlet/connectionTreeAction.ts | 6 +- .../viewlet/dragAndDropController.ts | 14 +- .../connection/common/connectionConfig.ts | 66 +-- .../common/connectionManagementService.ts | 28 +- .../connection/common/connectionStore.ts | 408 ++++++++++---- .../platform/connection/common/constants.ts | 12 +- .../connection/common/iconnectionConfig.ts | 29 + .../test/common/connectionConfig.test.ts | 129 ++--- .../test/common/connectionStore.test.ts | 528 ------------------ .../test/common/testConfigurationService.ts | 9 +- .../test/common/testStateService.ts | 30 - .../credentials/common/credentialsService.ts | 12 +- .../test/common/testCredentialsService.ts | 44 -- .../connectionManagementService.test.ts | 5 +- .../parts/connection/connectionStore.test.ts | 508 +++++++++++++++++ .../connection/connectionTreeActions.test.ts | 6 +- .../parts/query/editor/queryActions.test.ts | 18 +- src/sqltest/stubs/credentialsTestStubs.ts | 8 +- 19 files changed, 1008 insertions(+), 897 deletions(-) create mode 100644 src/sql/platform/connection/common/iconnectionConfig.ts delete mode 100644 src/sql/platform/connection/test/common/connectionStore.test.ts delete mode 100644 src/sql/platform/connection/test/common/testStateService.ts delete mode 100644 src/sql/platform/credentials/test/common/testCredentialsService.ts create mode 100644 src/sqltest/parts/connection/connectionStore.test.ts diff --git a/src/sql/base/common/map.ts b/src/sql/base/common/map.ts index a3f06ef5be..d13a792806 100644 --- a/src/sql/base/common/map.ts +++ b/src/sql/base/common/map.ts @@ -15,48 +15,3 @@ export function toObject(map: Map): { [key: string]: V } { } return {}; } - -export class ReverseLookUpMap { - private forward = new Map(); - private reverse = new Map(); - - public clear(): void { - this.forward.clear(); - this.reverse.clear(); - } - - public delete(key: K): boolean { - let reverseKey = this.forward.get(key); - return this.forward.delete(key) && this.reverse.delete(reverseKey); - } - - public forEach(callbackfn: (value: V, index: K, map: Map) => void, thisArg?: any): void { - this.forward.forEach(callbackfn, thisArg); - } - - public get(key: K): V { - return this.forward.get(key); - } - - public reverseGet(key: V): K { - return this.reverse.get(key); - } - - public has(key: K): boolean { - return this.forward.has(key); - } - - public reverseHas(key: V): boolean { - return this.reverse.has(key); - } - - public set(key: K, value: V): ReverseLookUpMap { - this.forward.set(key, value); - this.reverse.set(value, key); - return this; - } - - public get size(): number { - return this.forward.size; - } -} diff --git a/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts b/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts index 15ca902861..4663e0c26b 100644 --- a/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts +++ b/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts @@ -15,6 +15,7 @@ import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; import * as TaskUtilities from 'sql/workbench/common/taskUtilities'; import { ITree } from 'vs/base/parts/tree/browser/tree'; +import * as Constants from 'sql/platform/connection/common/constants'; import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService'; import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode'; import Severity from 'vs/base/common/severity'; @@ -22,7 +23,6 @@ import { ObjectExplorerActionsContext } from 'sql/parts/objectExplorer/viewlet/o import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService'; import { ConnectionViewletPanel } from 'sql/workbench/parts/dataExplorer/browser/connectionViewletPanel'; -import { UNSAVED_GROUP_ID } from 'sql/platform/connection/common/constants'; export class RefreshAction extends Action { @@ -356,13 +356,13 @@ export class DeleteConnectionAction extends Action { ) { super(id, label); this.class = 'delete-connection-action'; - if (element instanceof ConnectionProfileGroup && element.id === UNSAVED_GROUP_ID) { + if (element instanceof ConnectionProfileGroup && element.id === Constants.unsavedGroupId) { this.enabled = false; } if (element instanceof ConnectionProfile) { let parent: ConnectionProfileGroup = element.parent; - if (parent && parent.id === UNSAVED_GROUP_ID) { + if (parent && parent.id === Constants.unsavedGroupId) { this.enabled = false; } } diff --git a/src/sql/parts/objectExplorer/viewlet/dragAndDropController.ts b/src/sql/parts/objectExplorer/viewlet/dragAndDropController.ts index ab78115ceb..c8fe9e74ae 100644 --- a/src/sql/parts/objectExplorer/viewlet/dragAndDropController.ts +++ b/src/sql/parts/objectExplorer/viewlet/dragAndDropController.ts @@ -6,11 +6,12 @@ 'use strict'; import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import { ITree, IDragAndDrop, IDragOverReaction, DRAG_OVER_ACCEPT_BUBBLE_DOWN, DRAG_OVER_REJECT } from 'vs/base/parts/tree/browser/tree'; +import * as Constants from 'sql/platform/connection/common/constants'; import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; import { TreeUpdateUtils } from 'sql/parts/objectExplorer/viewlet/treeUpdateUtils'; -import { UNSAVED_GROUP_ID } from 'sql/platform/connection/common/constants'; import { IDragAndDropData } from 'vs/base/browser/dnd'; /** @@ -18,8 +19,8 @@ import { IDragAndDropData } from 'vs/base/browser/dnd'; */ export class ServerTreeDragAndDrop implements IDragAndDrop { - constructor( - @IConnectionManagementService private _connectionManagementService: IConnectionManagementService + constructor(@IConnectionManagementService private _connectionManagementService: IConnectionManagementService, + @IInstantiationService private _instantiationService: IInstantiationService ) { } @@ -142,7 +143,7 @@ export class ServerTreeDragAndDrop implements IDragAndDrop { let isDropToItself = source && targetConnectionProfileGroup && (source instanceof ConnectionProfileGroup) && source.name === targetConnectionProfileGroup.name; let isDropToSameLevel = oldParent && oldParent.equals(targetConnectionProfileGroup); - let isUnsavedDrag = source && (source instanceof ConnectionProfileGroup) && (source.id === UNSAVED_GROUP_ID); + let isUnsavedDrag = source && (source instanceof ConnectionProfileGroup) && (source.id === Constants.unsavedGroupId); return (!isDropToSameLevel && !isDropToItself && !isUnsavedDrag); } } @@ -152,6 +153,11 @@ export class ServerTreeDragAndDrop implements IDragAndDrop { */ export class RecentConnectionsDragAndDrop implements IDragAndDrop { + constructor(@IConnectionManagementService private connectionManagementService: IConnectionManagementService, + @IInstantiationService private instantiationService: IInstantiationService + ) { + } + /** * Returns a uri if the given element should be allowed to drag. * Returns null, otherwise. diff --git a/src/sql/platform/connection/common/connectionConfig.ts b/src/sql/platform/connection/common/connectionConfig.ts index f3fa23395b..83022d304a 100644 --- a/src/sql/platform/connection/common/connectionConfig.ts +++ b/src/sql/platform/connection/common/connectionConfig.ts @@ -7,16 +7,14 @@ import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; -import { UNSAVED_GROUP_ID } from 'sql/platform/connection/common/constants'; +import * as Constants from 'sql/platform/connection/common/constants'; +import { IConnectionConfig } from 'sql/platform/connection/common/iconnectionConfig'; import { IConnectionProfile, IConnectionProfileStore } from 'sql/platform/connection/common/interfaces'; import * as Utils from 'sql/platform/connection/common/utils'; import { generateUuid } from 'vs/base/common/uuid'; import * as nls from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -const GROUPS_CONFIG_KEY = 'datasource.connectionGroups'; -const CONNECTIONS_CONFIG_KEY = 'datasource.connections'; - export interface ISaveGroupResult { groups: IConnectionProfileGroup[]; newGroupId: string; @@ -25,7 +23,7 @@ export interface ISaveGroupResult { /** * Implements connection profile file storage. */ -export class ConnectionConfig { +export class ConnectionConfig implements IConnectionConfig { public constructor( private configurationService: IConfigurationService, @@ -38,7 +36,7 @@ export class ConnectionConfig { public getAllGroups(): IConnectionProfileGroup[] { let allGroups: IConnectionProfileGroup[] = []; - let { user, workspace } = this.configurationService.inspect(GROUPS_CONFIG_KEY); + let { user, workspace } = this.configurationService.inspect(Constants.connectionGroupsArrayName); if (user) { if (workspace) { @@ -47,12 +45,13 @@ export class ConnectionConfig { } allGroups = allGroups.concat(user); } - return allGroups.map(g => { + allGroups = allGroups.map(g => { if (g.parentId === '' || !g.parentId) { g.parentId = undefined; } return g; }); + return allGroups; } /** @@ -61,7 +60,7 @@ export class ConnectionConfig { public addConnection(profile: IConnectionProfile): Promise { if (profile.saveProfile) { return this.addGroupFromProfile(profile).then(groupId => { - let profiles = this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).user; + let profiles = this.configurationService.inspect(Constants.connectionsArrayName).user; if (!profiles) { profiles = []; } @@ -70,7 +69,7 @@ export class ConnectionConfig { let newProfile = ConnectionProfile.convertToProfileStore(this._capabilitiesService, connectionProfile); // Remove the profile if already set - let sameProfileInList = profiles.find(value => { + var sameProfileInList = profiles.find(value => { let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService); return providerConnectionProfile.matches(connectionProfile); }); @@ -83,7 +82,7 @@ export class ConnectionConfig { profiles.push(newProfile); } - return this.configurationService.updateValue(CONNECTIONS_CONFIG_KEY, profiles, ConfigurationTarget.USER).then(() => connectionProfile); + return this.configurationService.updateValue(Constants.connectionsArrayName, profiles, ConfigurationTarget.USER).then(() => connectionProfile); }); } else { return Promise.resolve(profile); @@ -107,11 +106,11 @@ export class ConnectionConfig { if (profile.groupId && profile.groupId !== Utils.defaultGroupId) { return Promise.resolve(profile.groupId); } else { - let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).user; + let groups = this.configurationService.inspect(Constants.connectionGroupsArrayName).user; let result = this.saveGroup(groups, profile.groupFullName, undefined, undefined); groups = result.groups; - return this.configurationService.updateValue(GROUPS_CONFIG_KEY, groups, ConfigurationTarget.USER).then(() => result.newGroupId); + return this.configurationService.updateValue(Constants.connectionGroupsArrayName, groups, ConfigurationTarget.USER).then(() => result.newGroupId); } } @@ -123,7 +122,7 @@ export class ConnectionConfig { if (profileGroup.id) { return Promise.resolve(profileGroup.id); } else { - let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).user; + let groups = this.configurationService.inspect(Constants.connectionGroupsArrayName).user; let sameNameGroup = groups ? groups.find(group => group.name === profileGroup.name) : undefined; if (sameNameGroup) { let errMessage: string = nls.localize('invalidServerName', "A server group with the same name already exists."); @@ -132,13 +131,13 @@ export class ConnectionConfig { let result = this.saveGroup(groups, profileGroup.name, profileGroup.color, profileGroup.description); groups = result.groups; - return this.configurationService.updateValue(GROUPS_CONFIG_KEY, groups, ConfigurationTarget.USER).then(() => result.newGroupId); + return this.configurationService.updateValue(Constants.connectionGroupsArrayName, groups, ConfigurationTarget.USER).then(() => result.newGroupId); } } } private getConnectionProfilesForTarget(configTarget: ConfigurationTarget): IConnectionProfileStore[] { - let configs = this.configurationService.inspect(CONNECTIONS_CONFIG_KEY); + let configs = this.configurationService.inspect(Constants.connectionsArrayName); let profiles: IConnectionProfileStore[]; if (configs) { if (configTarget === ConfigurationTarget.USER) { @@ -148,7 +147,7 @@ export class ConnectionConfig { } if (profiles) { if (this.fixConnectionIds(profiles)) { - this.configurationService.updateValue(CONNECTIONS_CONFIG_KEY, profiles, configTarget); + this.configurationService.updateValue(Constants.connectionsArrayName, profiles, configTarget); } } else { profiles = []; @@ -165,7 +164,8 @@ export class ConnectionConfig { private fixConnectionIds(profiles: IConnectionProfileStore[]): boolean { let idsCache: { [label: string]: boolean } = {}; let changed: boolean = false; - for (let profile of profiles) { + for (var index = 0; index < profiles.length; index++) { + var profile = profiles[index]; if (!profile.id) { profile.id = generateUuid(); changed = true; @@ -215,7 +215,7 @@ export class ConnectionConfig { */ public deleteConnection(profile: ConnectionProfile): Promise { // Get all connections in the settings - let profiles = this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).user; + let profiles = this.configurationService.inspect(Constants.connectionsArrayName).user; // Remove the profile from the connections profiles = profiles.filter(value => { let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService); @@ -223,7 +223,7 @@ export class ConnectionConfig { }); // Write connections back to settings - return this.configurationService.updateValue(CONNECTIONS_CONFIG_KEY, profiles, ConfigurationTarget.USER); + return this.configurationService.updateValue(Constants.connectionsArrayName, profiles, ConfigurationTarget.USER); } /** @@ -236,7 +236,7 @@ export class ConnectionConfig { // Add selected group to subgroups list subgroups.push(group); // Get all connections in the settings - let profiles = this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).user; + let profiles = this.configurationService.inspect(Constants.connectionsArrayName).user; // Remove the profiles from the connections profiles = profiles.filter(value => { let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService); @@ -244,14 +244,14 @@ export class ConnectionConfig { }); // Get all groups in the settings - let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).user; + let groups = this.configurationService.inspect(Constants.connectionGroupsArrayName).user; // Remove subgroups in the settings groups = groups.filter((grp) => { return !subgroups.some((item) => item.id === grp.id); }); return Promise.all([ - this.configurationService.updateValue(CONNECTIONS_CONFIG_KEY, profiles, ConfigurationTarget.USER), - this.configurationService.updateValue(GROUPS_CONFIG_KEY, groups, ConfigurationTarget.USER) + this.configurationService.updateValue(Constants.connectionsArrayName, profiles, ConfigurationTarget.USER), + this.configurationService.updateValue(Constants.connectionGroupsArrayName, groups, ConfigurationTarget.USER) ]).then(() => Promise.resolve()); } @@ -259,14 +259,14 @@ export class ConnectionConfig { * Moves the source group under the target group. */ public changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise { - let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).user; + let groups = this.configurationService.inspect(Constants.connectionGroupsArrayName).user; groups = groups.map(g => { if (g.id === source.id) { g.parentId = target.id; } return g; }); - return this.configurationService.updateValue(GROUPS_CONFIG_KEY, groups, ConfigurationTarget.USER); + return this.configurationService.updateValue(Constants.connectionGroupsArrayName, groups, ConfigurationTarget.USER); } /** @@ -283,10 +283,10 @@ export class ConnectionConfig { * Moves the connection under the target group with the new ID. */ private changeGroupIdForConnectionInSettings(profile: ConnectionProfile, newGroupID: string, target: ConfigurationTarget = ConfigurationTarget.USER): Promise { - let profiles = target === ConfigurationTarget.USER ? this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).user : - this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).workspace; + let profiles = target === ConfigurationTarget.USER ? this.configurationService.inspect(Constants.connectionsArrayName).user : + this.configurationService.inspect(Constants.connectionsArrayName).workspace; if (profiles) { - if (profile.parent && profile.parent.id === UNSAVED_GROUP_ID) { + if (profile.parent && profile.parent.id === Constants.unsavedGroupId) { profile.groupId = newGroupID; profiles.push(ConnectionProfile.convertToProfileStore(this._capabilitiesService, profile)); } else { @@ -298,7 +298,7 @@ export class ConnectionConfig { }); } - return this.configurationService.updateValue(CONNECTIONS_CONFIG_KEY, profiles, target); + return this.configurationService.updateValue(Constants.connectionsArrayName, profiles, target); } else { return Promise.resolve(); } @@ -320,12 +320,14 @@ export class ConnectionConfig { } public saveGroup(groups: IConnectionProfileGroup[], groupFullName: string, color: string, description: string): ISaveGroupResult { + let result: ISaveGroupResult; let groupNames = ConnectionProfileGroup.getGroupFullNameParts(groupFullName); - return this.saveGroupInTree(groups, undefined, groupNames, color, description, 0); + result = this.saveGroupInTree(groups, undefined, groupNames, color, description, 0); + return result; } public editGroup(source: ConnectionProfileGroup): Promise { - let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).user; + let groups = this.configurationService.inspect(Constants.connectionGroupsArrayName).user; let sameNameGroup = groups ? groups.find(group => group.name === source.name && group.id !== source.id) : undefined; if (sameNameGroup) { let errMessage: string = nls.localize('invalidServerName', "A server group with the same name already exists."); @@ -340,7 +342,7 @@ export class ConnectionConfig { } return g; }); - return this.configurationService.updateValue(GROUPS_CONFIG_KEY, groups, ConfigurationTarget.USER); + return this.configurationService.updateValue(Constants.connectionGroupsArrayName, groups, ConfigurationTarget.USER); } private isSameGroupName(group1: IConnectionProfileGroup, group2: IConnectionProfileGroup): boolean { diff --git a/src/sql/platform/connection/common/connectionManagementService.ts b/src/sql/platform/connection/common/connectionManagementService.ts index 2ac7b76023..07f3d11ec6 100644 --- a/src/sql/platform/connection/common/connectionManagementService.ts +++ b/src/sql/platform/connection/common/connectionManagementService.ts @@ -17,6 +17,7 @@ import { ConnectionManagementInfo } from 'sql/platform/connection/common/connect import * as Utils from 'sql/platform/connection/common/utils'; import * as Constants from 'sql/platform/connection/common/constants'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; +import { ICredentialsService } from 'sql/platform/credentials/common/credentialsService'; import * as ConnectionContracts from 'sql/parts/connection/common/connection'; import { ConnectionStatusManager } from 'sql/platform/connection/common/connectionStatusManager'; import { DashboardInput } from 'sql/parts/dashboard/dashboardInput'; @@ -43,7 +44,9 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import * as platform from 'vs/platform/registry/common/platform'; +import { Memento } from 'vs/workbench/common/memento'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IStorageService } from 'vs/platform/storage/common/storage'; import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; import { Event, Emitter } from 'vs/base/common/event'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; @@ -74,13 +77,16 @@ export class ConnectionManagementService extends Disposable implements IConnecti private _connectionGlobalStatus = new ConnectionGlobalStatus(this._statusBarService); constructor( + private _connectionMemento: Memento, private _connectionStore: ConnectionStore, + @IStorageService _storageService: IStorageService, @IConnectionDialogService private _connectionDialogService: IConnectionDialogService, @IServerGroupController private _serverGroupController: IServerGroupController, @IInstantiationService private _instantiationService: IInstantiationService, @IEditorService private _editorService: IEditorService, @ITelemetryService private _telemetryService: ITelemetryService, @IConfigurationService private _configurationService: IConfigurationService, + @ICredentialsService private _credentialsService: ICredentialsService, @ICapabilitiesService private _capabilitiesService: ICapabilitiesService, @IQuickInputService private _quickInputService: IQuickInputService, @IEditorGroupsService private _editorGroupService: IEditorGroupsService, @@ -91,8 +97,13 @@ export class ConnectionManagementService extends Disposable implements IConnecti ) { super(); + // _connectionMemento and _connectionStore are in constructor to enable this class to be more testable + if (!this._connectionMemento) { + this._connectionMemento = new Memento('ConnectionManagement', _storageService); + } if (!this._connectionStore) { - this._connectionStore = _instantiationService.createInstance(ConnectionStore); + this._connectionStore = new ConnectionStore(this._connectionMemento, + this._configurationService, this._credentialsService, this._capabilitiesService); } // Register Statusbar item @@ -124,6 +135,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti this.onConnectionChanged(() => this.refreshEditorTitles()); this.onConnect(() => this.refreshEditorTitles()); this.onDisconnect(() => this.refreshEditorTitles()); + _storageService.onWillSaveState(() => this.shutdown()); } public providerRegistered(providerId: string): boolean { @@ -612,11 +624,11 @@ export class ConnectionManagementService extends Disposable implements IConnecti } public clearRecentConnection(connectionProfile: IConnectionProfile): void { - this._connectionStore.removeRecentConnection(connectionProfile); + this._connectionStore.removeConnectionToMemento(connectionProfile, Constants.recentConnections); } public getActiveConnections(providers?: string[]): ConnectionProfile[] { - return this._connectionStatusManager.getActiveConnectionProfiles(providers); + return this._connectionStatusManager.getActiveConnectionProfiles(); } public getConnectionUriFromId(connectionId: string): string { @@ -849,8 +861,8 @@ export class ConnectionManagementService extends Disposable implements IConnecti * Add a connection to the active connections list. */ private tryAddActiveConnection(connectionManagementInfo: ConnectionManagementInfo, newConnection: IConnectionProfile, addToMru: boolean): void { - if (newConnection && addToMru) { - this._connectionStore.addRecentConnection(newConnection) + if (newConnection) { + this._connectionStore.addActiveConnection(newConnection, addToMru) .then(() => { connectionManagementInfo.connectHandler(true); }, err => { @@ -918,6 +930,11 @@ export class ConnectionManagementService extends Disposable implements IConnecti public onIntelliSenseCacheComplete(handle: number, connectionUri: string): void { } + private shutdown(): void { + this._connectionStore.clearActiveConnections(); + this._connectionMemento.saveMemento(); + } + public changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise { TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.MoveServerConnection); return this._connectionStore.changeGroupIdForConnectionGroup(source, target); @@ -1079,6 +1096,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti this.doDisconnect(uri, profile).then(result => { if (result) { this.addTelemetryForConnectionDisconnected(input); + this._connectionStore.removeActiveConnection(input); this._connectionStatusManager.removeConnection(uri); resolve(); } else { diff --git a/src/sql/platform/connection/common/connectionStore.ts b/src/sql/platform/connection/common/connectionStore.ts index 9b8bf0d6f8..f1e627daec 100644 --- a/src/sql/platform/connection/common/connectionStore.ts +++ b/src/sql/platform/connection/common/connectionStore.ts @@ -5,26 +5,21 @@ 'use strict'; -import { ReverseLookUpMap } from 'sql/base/common/map'; -import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; -import { ConnectionConfig } from 'sql/platform/connection/common/connectionConfig'; -import { fixupConnectionCredentials } from 'sql/platform/connection/common/connectionInfo'; +import * as Constants from 'sql/platform/connection/common/constants'; +import * as ConnInfo from 'sql/platform/connection/common/connectionInfo'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; -import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { ICredentialsService } from 'sql/platform/credentials/common/credentialsService'; +import { IConnectionConfig } from 'sql/platform/connection/common/iconnectionConfig'; +import { ConnectionConfig } from 'sql/platform/connection/common/connectionConfig'; +import { Memento } from 'vs/workbench/common/memento'; +import { StorageScope } from 'vs/platform/storage/common/storage'; +import { ConnectionProfileGroup, IConnectionProfileGroup } from './connectionProfileGroup'; +import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IStateService } from 'vs/platform/state/common/state'; const MAX_CONNECTIONS_DEFAULT = 25; -const RECENT_CONNECTIONS_STATE_KEY = 'recentConnections'; -const CRED_PREFIX = 'Microsoft.SqlTools'; -const CRED_SEPARATOR = '|'; -const CRED_ID_PREFIX = 'id:'; -const CRED_ITEMTYPE_PREFIX = 'itemtype:'; -const CRED_PROFILE_USER = 'Profile'; - /** * Manages the connections list including saved profiles and the most recently used connections * @@ -32,15 +27,39 @@ const CRED_PROFILE_USER = 'Profile'; * @class ConnectionStore */ export class ConnectionStore { - private groupIdMap = new ReverseLookUpMap(); - private connectionConfig = new ConnectionConfig(this.configurationService, this.capabilitiesService); + private _memento: any; + private _groupIdToFullNameMap: { [groupId: string]: string }; + private _groupFullNameToIdMap: { [groupId: string]: string }; constructor( - @IStateService private stateService: IStateService, - @IConfigurationService private configurationService: IConfigurationService, - @ICredentialsService private credentialService: ICredentialsService, - @ICapabilitiesService private capabilitiesService: ICapabilitiesService + private _context: Memento, + private _configurationService: IConfigurationService, + private _credentialService: ICredentialsService, + private _capabilitiesService: ICapabilitiesService, + private _connectionConfig?: IConnectionConfig ) { + if (_context) { + this._memento = this._context.getMemento(StorageScope.GLOBAL); + } + this._groupIdToFullNameMap = {}; + this._groupFullNameToIdMap = {}; + if (!this._connectionConfig) { + this._connectionConfig = new ConnectionConfig(this._configurationService, this._capabilitiesService); + } + } + + public static get CRED_PREFIX(): string { return 'Microsoft.SqlTools'; } + public static get CRED_SEPARATOR(): string { return '|'; } + public static get CRED_ID_PREFIX(): string { return 'id:'; } + public static get CRED_ITEMTYPE_PREFIX(): string { return 'itemtype:'; } + public static get CRED_PROFILE_USER(): string { return 'Profile'; } + + public formatCredentialIdForCred(connectionProfile: IConnectionProfile): string { + if (!connectionProfile) { + throw new Error('Missing Connection which is required'); + } + let itemTypeString: string = ConnectionStore.CRED_PROFILE_USER; + return this.formatCredentialId(connectionProfile, itemTypeString); } /** @@ -51,17 +70,26 @@ export class ConnectionStore { * @param {string} itemType type of the item (MRU or Profile) - optional * @returns {string} formatted string with server, DB and username */ - private formatCredentialId(connectionProfile: IConnectionProfile, itemType?: string): string { + public formatCredentialId(connectionProfile: IConnectionProfile, itemType?: string): string { let connectionProfileInstance: ConnectionProfile = ConnectionProfile.fromIConnectionProfile( - this.capabilitiesService, connectionProfile); - let cred: string[] = [CRED_PREFIX]; + this._capabilitiesService, connectionProfile); + if (!connectionProfileInstance.getConnectionInfoId()) { + throw new Error('Missing Id, which is required'); + } + let cred: string[] = [ConnectionStore.CRED_PREFIX]; if (!itemType) { - itemType = CRED_PROFILE_USER; + itemType = ConnectionStore.CRED_PROFILE_USER; } - cred.push(CRED_ITEMTYPE_PREFIX.concat(itemType)); - cred.push(CRED_ID_PREFIX.concat(connectionProfileInstance.getConnectionInfoId())); - return cred.join(CRED_SEPARATOR); + ConnectionStore.pushIfNonEmpty(itemType, ConnectionStore.CRED_ITEMTYPE_PREFIX, cred); + ConnectionStore.pushIfNonEmpty(connectionProfileInstance.getConnectionInfoId(), ConnectionStore.CRED_ID_PREFIX, cred); + return cred.join(ConnectionStore.CRED_SEPARATOR); + } + + private static pushIfNonEmpty(value: string, prefix: string, arr: string[]): void { + if (value) { + arr.push(prefix.concat(value)); + } } /** @@ -70,7 +98,7 @@ export class ConnectionStore { */ public isPasswordRequired(connection: IConnectionProfile): boolean { if (connection) { - let connectionProfile = ConnectionProfile.fromIConnectionProfile(this.capabilitiesService, connection); + let connectionProfile = ConnectionProfile.fromIConnectionProfile(this._capabilitiesService, connection); return connectionProfile.isPasswordRequired(); } else { return false; @@ -78,20 +106,28 @@ export class ConnectionStore { } public addSavedPassword(credentialsItem: IConnectionProfile): Promise<{ profile: IConnectionProfile, savedCred: boolean }> { - if (credentialsItem.savePassword && this.isPasswordRequired(credentialsItem) && !credentialsItem.password) { - let credentialId = this.formatCredentialId(credentialsItem, CRED_PROFILE_USER); - return this.credentialService.readCredential(credentialId) - .then(savedCred => { - if (savedCred) { - credentialsItem.password = savedCred.password; - credentialsItem.options['password'] = savedCred.password; - } - return { profile: credentialsItem, savedCred: !!savedCred }; - }); - } else { - // No need to look up the password - return Promise.resolve({ profile: credentialsItem, savedCred: credentialsItem.savePassword }); - } + let self = this; + return new Promise<{ profile: IConnectionProfile, savedCred: boolean }>((resolve, reject) => { + if (credentialsItem.savePassword && this.isPasswordRequired(credentialsItem) + && !credentialsItem.password) { + + let credentialId = this.formatCredentialIdForCred(credentialsItem); + self._credentialService.readCredential(credentialId) + .then(savedCred => { + if (savedCred) { + credentialsItem.password = savedCred.password; + credentialsItem.options['password'] = savedCred.password; + } + resolve({ profile: credentialsItem, savedCred: !!savedCred }); + }, + reason => { + reject(reason); + }); + } else { + // No need to look up the password + resolve({ profile: credentialsItem, savedCred: credentialsItem.savePassword }); + } + }); } /** @@ -103,20 +139,34 @@ export class ConnectionStore { * @returns {Promise} a Promise that returns the original profile, for help in chaining calls */ public saveProfile(profile: IConnectionProfile, forceWritePlaintextPassword?: boolean): Promise { - // Add the profile to the saved list, taking care to clear out the password field if necessary - let savedProfile = forceWritePlaintextPassword ? profile : this.getProfileWithoutPassword(profile); - return this.saveProfileToConfig(savedProfile) - .then(savedConnectionProfile => { - profile.groupId = savedConnectionProfile.groupId; - profile.id = savedConnectionProfile.id; - // Only save if we successfully added the profile - return this.saveProfilePasswordIfNeeded(profile); - }).then(() => { - // Add necessary default properties before returning - // this is needed to support immediate connections - fixupConnectionCredentials(profile); - return profile; - }); + const self = this; + return new Promise((resolve, reject) => { + // Add the profile to the saved list, taking care to clear out the password field if necessary + let savedProfile: IConnectionProfile; + if (forceWritePlaintextPassword) { + savedProfile = profile; + } else { + + savedProfile = this.getProfileWithoutPassword(profile); + } + self.saveProfileToConfig(savedProfile) + .then(savedConnectionProfile => { + profile.groupId = savedConnectionProfile.groupId; + profile.id = savedConnectionProfile.id; + // Only save if we successfully added the profile + return self.saveProfilePasswordIfNeeded(profile); + // And resolve / reject at the end of the process + }, err => { + reject(err); + }).then(resolved => { + // Add necessary default properties before returning + // this is needed to support immediate connections + ConnInfo.fixupConnectionCredentials(profile); + resolve(profile); + }, err => { + reject(err); + }); + }); } /** @@ -126,15 +176,29 @@ export class ConnectionStore { * @returns {Promise} a Promise that returns the id of connection group */ public saveProfileGroup(profile: IConnectionProfileGroup): Promise { - return this.connectionConfig.addGroup(profile); + const self = this; + return new Promise((resolve, reject) => { + self._connectionConfig.addGroup(profile).then(groupId => { + resolve(groupId); + }).catch(error => { + reject(error); + }); + }); } private saveProfileToConfig(profile: IConnectionProfile): Promise { - if (profile.saveProfile) { - return this.connectionConfig.addConnection(profile); - } else { - return Promise.resolve(profile); - } + const self = this; + return new Promise((resolve, reject) => { + if (profile.saveProfile) { + self._connectionConfig.addConnection(profile).then(savedProfile => { + resolve(savedProfile); + }).catch(error => { + reject(error); + }); + } else { + resolve(profile); + } + }); } /** @@ -144,8 +208,12 @@ export class ConnectionStore { * @returns {azdata.ConnectionInfo} the array of connections, empty if none are found */ public getRecentlyUsedConnections(providers?: string[]): ConnectionProfile[] { - let configValues = this.stateService.getItem(RECENT_CONNECTIONS_STATE_KEY, []).filter(c => !!c); + let configValues: IConnectionProfile[] = this._memento[Constants.recentConnections]; + if (!configValues) { + configValues = []; + } + configValues = configValues.filter(c => !!(c)); if (providers && providers.length > 0) { configValues = configValues.filter(c => providers.includes(c.providerName)); } @@ -155,7 +223,7 @@ export class ConnectionStore { private convertConfigValuesToConnectionProfiles(configValues: IConnectionProfile[]): ConnectionProfile[] { return configValues.map(c => { if (c) { - let connectionProfile = new ConnectionProfile(this.capabilitiesService, c); + let connectionProfile = new ConnectionProfile(this._capabilitiesService, c); if (connectionProfile.saveProfile) { if (!connectionProfile.groupFullName && connectionProfile.groupId) { connectionProfile.groupFullName = this.getGroupFullName(connectionProfile.groupId); @@ -173,9 +241,24 @@ export class ConnectionStore { }); } + /** + * Gets the list of active connections. These will not include the password - a separate call to + * {addSavedPassword} is needed to fill that before connecting + * + * @returns {azdata.ConnectionInfo} the array of connections, empty if none are found + */ + public getActiveConnections(): ConnectionProfile[] { + let configValues: IConnectionProfile[] = this._memento[Constants.activeConnections]; + if (!configValues) { + configValues = []; + } + + return this.convertConfigValuesToConnectionProfiles(configValues); + } + public getProfileWithoutPassword(conn: IConnectionProfile): ConnectionProfile { if (conn) { - let savedConn: ConnectionProfile = ConnectionProfile.fromIConnectionProfile(this.capabilitiesService, conn); + let savedConn: ConnectionProfile = ConnectionProfile.fromIConnectionProfile(this._capabilitiesService, conn); savedConn = savedConn.withoutPassword(); return savedConn; @@ -193,35 +276,71 @@ export class ConnectionStore { * @param {boolean} addToMru Whether to add this connection to the MRU * @returns {Promise} a Promise that returns when the connection was saved */ - public addRecentConnection(conn: IConnectionProfile): Promise { - let maxConnections = this.getMaxRecentConnectionsCount(); - return this.addConnectionToState(conn, RECENT_CONNECTIONS_STATE_KEY, maxConnections, conn.savePassword); - } - - private addConnectionToState(conn: IConnectionProfile, key: string, maxConnections?: number, savePassword?: boolean): Promise { - // Get all profiles - let configValues = this.getConnectionsFromState(key); - let configToSave = this.addToConnectionList(conn, configValues); - if (maxConnections) { - // Remove last element if needed - if (configToSave.length > maxConnections) { - configToSave = configToSave.slice(0, maxConnections); - } + public async addActiveConnection(conn: IConnectionProfile, addToMru: boolean): Promise { + if (addToMru) { + await this.addConnectionToMru(conn); + } + + // Only add connections we don't already know about + if (!this.getActiveConnections().some(existingConn => existingConn.id === conn.id)) { + await this.addConnectionToMemento(conn, Constants.activeConnections, undefined, conn.savePassword); } - this.stateService.setItem(key, configToSave); - return savePassword ? this.doSavePassword(conn).then() : Promise.resolve(); } - private removeConnectionFromState(conn: IConnectionProfile, key: string): void { - // Get all profiles - let configValues = this.getConnectionsFromState(key); - let configToSave = this.removeFromConnectionList(conn, configValues); - - this.stateService.setItem(key, configToSave); + /** + * Adds the specified connection to the MRU list + * @param conn The connection to add + */ + private async addConnectionToMru(conn: IConnectionProfile): Promise { + let maxConnections = this.getMaxRecentConnectionsCount(); + if (ConnectionProfile.isConnectionToDefaultDb(conn)) { + conn.databaseName = ''; + } + await this.addConnectionToMemento(conn, Constants.recentConnections, maxConnections); } - private getConnectionsFromState(mementoKey: string): ConnectionProfile[] { - return this.convertConfigValuesToConnectionProfiles(this.stateService.getItem(mementoKey, [])); + public addConnectionToMemento(conn: IConnectionProfile, mementoKey: string, maxConnections?: number, savePassword?: boolean): Promise { + const self = this; + return new Promise((resolve, reject) => { + // Get all profiles + let configValues = self.getConnectionsFromMemento(mementoKey); + let configToSave = this.addToConnectionList(conn, configValues); + if (maxConnections) { + // Remove last element if needed + if (configToSave.length > maxConnections) { + configToSave = configToSave.slice(0, maxConnections); + } + } + self._memento[mementoKey] = configToSave; + if (savePassword) { + self.doSavePassword(conn).then(result => { + resolve(undefined); + }); + } else { + resolve(undefined); + } + }); + } + + public removeConnectionToMemento(conn: IConnectionProfile, mementoKey: string): Promise { + const self = this; + return new Promise((resolve, reject) => { + // Get all profiles + let configValues = self.getConnectionsFromMemento(mementoKey); + let configToSave = this.removeFromConnectionList(conn, configValues); + + self._memento[mementoKey] = configToSave; + resolve(undefined); + }); + } + + public getConnectionsFromMemento(mementoKey: string): ConnectionProfile[] { + let configValues: IConnectionProfile[] = this._memento[mementoKey]; + if (!configValues) { + configValues = []; + } + + return this.convertConfigValuesToConnectionProfiles(configValues); } private addToConnectionList(conn: IConnectionProfile, list: ConnectionProfile[]): IConnectionProfile[] { @@ -239,7 +358,11 @@ export class ConnectionStore { list.unshift(savedProfile); - return list.filter(n => n !== undefined).map(c => c.toIConnectionProfile()); + let newList = list.map(c => { + let connectionProfile = c ? c.toIConnectionProfile() : undefined; + return connectionProfile; + }); + return newList.filter(n => n !== undefined); } private removeFromConnectionList(conn: IConnectionProfile, list: ConnectionProfile[]): IConnectionProfile[] { @@ -255,18 +378,37 @@ export class ConnectionStore { return !equal; }); - return list.filter(n => n !== undefined).map(c => c.toIConnectionProfile()); + let newList = list.map(c => { + let connectionProfile = c ? c.toIConnectionProfile() : undefined; + return connectionProfile; + }); + return newList.filter(n => n !== undefined); } /** * Clear all recently used connections from the MRU list. */ public clearRecentlyUsed(): void { - this.stateService.setItem(RECENT_CONNECTIONS_STATE_KEY, []); + this._memento[Constants.recentConnections] = []; } - public removeRecentConnection(conn: IConnectionProfile): void { - this.removeConnectionFromState(conn, RECENT_CONNECTIONS_STATE_KEY); + public clearFromMemento(name: string): void { + this._memento[name] = []; + } + + + /** + * Clear all active connections from the MRU list. + */ + public clearActiveConnections(): void { + this._memento[Constants.activeConnections] = []; + } + + /** + * Remove a connection profile from the active connections list. + */ + public removeActiveConnection(conn: IConnectionProfile): Promise { + return this.removeConnectionToMemento(conn, Constants.activeConnections); } private saveProfilePasswordIfNeeded(profile: IConnectionProfile): Promise { @@ -277,23 +419,32 @@ export class ConnectionStore { } private doSavePassword(conn: IConnectionProfile): Promise { - if (conn.password) { - let credentialId = this.formatCredentialId(conn); - return this.credentialService.saveCredential(credentialId, conn.password); - } else { - return Promise.resolve(true); - } + let self = this; + return new Promise((resolve, reject) => { + if (conn.password) { + let credentialId = this.formatCredentialId(conn); + self._credentialService.saveCredential(credentialId, conn.password) + .then((result) => { + resolve(result); + }, reason => { + // Bubble up error if there was a problem executing the set command + reject(reason); + }); + } else { + resolve(true); + } + }); } public getConnectionProfileGroups(withoutConnections?: boolean, providers?: string[]): ConnectionProfileGroup[] { let profilesInConfiguration: ConnectionProfile[]; if (!withoutConnections) { - profilesInConfiguration = this.connectionConfig.getConnections(true); + profilesInConfiguration = this._connectionConfig.getConnections(true); if (providers && providers.length > 0) { profilesInConfiguration = profilesInConfiguration.filter(x => providers.includes(x.providerName)); } } - let groups = this.connectionConfig.getAllGroups(); + let groups = this._connectionConfig.getAllGroups(); let connectionProfileGroups = this.convertToConnectionGroup(groups, profilesInConfiguration, undefined); return connectionProfileGroups; @@ -328,53 +479,74 @@ export class ConnectionStore { } public getGroupFromId(groupId: string): IConnectionProfileGroup { - let groups = this.connectionConfig.getAllGroups(); + let groups = this._connectionConfig.getAllGroups(); return groups.find(group => group.id === groupId); } private getMaxRecentConnectionsCount(): number { - return this.configurationService.getValue('sql.maxRecentConnections') || MAX_CONNECTIONS_DEFAULT; + let config = this._configurationService.getValue(Constants.sqlConfigSectionName); + + let maxConnections: number = config[Constants.configMaxRecentConnections]; + if (typeof (maxConnections) !== 'number' || maxConnections <= 0) { + maxConnections = MAX_CONNECTIONS_DEFAULT; + } + return maxConnections; } - public editGroup(group: ConnectionProfileGroup): Promise { - return this.connectionConfig.editGroup(group).then(); + public editGroup(group: ConnectionProfileGroup): Promise { + const self = this; + return new Promise((resolve, reject) => { + self._connectionConfig.editGroup(group).then(() => { + resolve(null); + }).catch(error => { + reject(error); + }); + }); } public deleteConnectionFromConfiguration(connection: ConnectionProfile): Promise { - return this.connectionConfig.deleteConnection(connection); + return this._connectionConfig.deleteConnection(connection); } public deleteGroupFromConfiguration(group: ConnectionProfileGroup): Promise { - return this.connectionConfig.deleteGroup(group); + return this._connectionConfig.deleteGroup(group); } public changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise { - return this.connectionConfig.changeGroupIdForConnectionGroup(source, target); + return this._connectionConfig.changeGroupIdForConnectionGroup(source, target); } public canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean { - return this.connectionConfig.canChangeConnectionConfig(profile, newGroupID); + return this._connectionConfig.canChangeConnectionConfig(profile, newGroupID); } public changeGroupIdForConnection(source: ConnectionProfile, targetGroupId: string): Promise { - return this.connectionConfig.changeGroupIdForConnection(source, targetGroupId).then(); + return new Promise((resolve, reject) => { + this._connectionConfig.changeGroupIdForConnection(source, targetGroupId).then(() => { + resolve(); + }, (error => { + reject(error); + })); + }); } private addGroupFullNameToMap(groupId: string, groupFullName: string): void { if (groupId) { - this.groupIdMap.set(groupId, groupFullName); + this._groupIdToFullNameMap[groupId] = groupFullName; } if (groupFullName !== undefined) { - this.groupIdMap.set(groupFullName.toUpperCase(), groupId); + this._groupFullNameToIdMap[groupFullName.toUpperCase()] = groupId; } } private getGroupFullName(groupId: string): string { - if (!this.groupIdMap.has(groupId)) { + if (groupId in this._groupIdToFullNameMap) { + return this._groupIdToFullNameMap[groupId]; + } else { // Load the cache this.getConnectionProfileGroups(true); } - return this.groupIdMap.get(groupId); + return this._groupIdToFullNameMap[groupId]; } private getGroupId(groupFullName: string): string { @@ -382,10 +554,14 @@ export class ConnectionStore { groupFullName = ''; } let key = groupFullName.toUpperCase(); - if (!this.groupIdMap.reverseHas(key)) { + let result: string = ''; + if (key in this._groupFullNameToIdMap) { + result = this._groupFullNameToIdMap[key]; + } else { // Load the cache this.getConnectionProfileGroups(true); + result = this._groupFullNameToIdMap[key]; } - return this.groupIdMap.reverseGet(key); + return result; } } diff --git a/src/sql/platform/connection/common/constants.ts b/src/sql/platform/connection/common/constants.ts index ee26e1a722..f09f28b867 100644 --- a/src/sql/platform/connection/common/constants.ts +++ b/src/sql/platform/connection/common/constants.ts @@ -7,9 +7,19 @@ export const sqlConfigSectionName = 'sql'; export const outputChannelName = 'MSSQL'; +export const connectionsArrayName = 'datasource.connections'; +export const connectionGroupsArrayName = 'datasource.connectionGroups'; + +/**Unsaved connections Id */ +export const unsavedGroupId = 'unsaved'; + /* Memento constants */ +export const activeConnections = 'ACTIVE_CONNECTIONS'; +export const recentConnections = 'RECENT_CONNECTIONS'; export const capabilitiesOptions = 'OPTIONS_METADATA'; +export const configMaxRecentConnections = 'maxRecentConnections'; + export const mssqlProviderName = 'MSSQL'; export const anyProviderName = '*'; export const connectionProviderContextKey = 'connectionProvider'; @@ -24,5 +34,3 @@ export const passwordChars = '***************'; export const sqlLogin = 'SqlLogin'; export const integrated = 'Integrated'; export const azureMFA = 'AzureMFA'; - -export const UNSAVED_GROUP_ID = 'unsaved'; diff --git a/src/sql/platform/connection/common/iconnectionConfig.ts b/src/sql/platform/connection/common/iconnectionConfig.ts new file mode 100644 index 0000000000..7e14127817 --- /dev/null +++ b/src/sql/platform/connection/common/iconnectionConfig.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; +import { IConnectionProfileGroup, ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; +import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; + +/** + * Interface for a configuration file that stores connection profiles. + * + * @export + * @interface IConnectionConfig + */ +export interface IConnectionConfig { + addConnection(profile: IConnectionProfile): Promise; + addGroup(profileGroup: IConnectionProfileGroup): Promise; + getConnections(getWorkspaceConnections: boolean): ConnectionProfile[]; + getAllGroups(): IConnectionProfileGroup[]; + changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise; + changeGroupIdForConnection(source: ConnectionProfile, targetGroupId: string): Promise; + editGroup(group: ConnectionProfileGroup): Promise; + deleteConnection(profile: ConnectionProfile): Promise; + deleteGroup(group: ConnectionProfileGroup): Promise; + canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean; +} diff --git a/src/sql/platform/connection/test/common/connectionConfig.test.ts b/src/sql/platform/connection/test/common/connectionConfig.test.ts index 73c65e74da..3471caa7be 100644 --- a/src/sql/platform/connection/test/common/connectionConfig.test.ts +++ b/src/sql/platform/connection/test/common/connectionConfig.test.ts @@ -5,20 +5,23 @@ 'use strict'; -import * as assert from 'assert'; -import * as azdata from 'azdata'; -import { ICapabilitiesService, ProviderFeatures } from 'sql/platform/capabilities/common/capabilitiesService'; +import * as TypeMoq from 'typemoq'; import { ConnectionConfig, ISaveGroupResult } from 'sql/platform/connection/common/connectionConfig'; -import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; -import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; import { IConnectionProfile, IConnectionProfileStore } from 'sql/platform/connection/common/interfaces'; -import { TestConfigurationService } from 'sql/platform/connection/test/common/testConfigurationService'; +import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import * as Constants from 'sql/platform/connection/common/constants'; +import { IConnectionProfileGroup, ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; +import * as assert from 'assert'; +import { ProviderFeatures, ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; +import * as azdata from 'azdata'; +import { Emitter } from 'vs/base/common/event'; import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes'; import { CapabilitiesTestService } from 'sqltest/stubs/capabilitiesTestService'; -import * as TypeMoq from 'typemoq'; -import { Emitter } from 'vs/base/common/event'; -import { deepClone, deepFreeze } from 'vs/base/common/objects'; -import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { TestConfigurationService } from 'sql/platform/connection/test/common/testConfigurationService'; +import { deepFreeze, deepClone } from 'vs/base/common/objects'; +import { isUndefinedOrNull } from 'vs/base/common/types'; +import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo'; suite('ConnectionConfig', () => { let capabilitiesService: TypeMoq.Mock; @@ -231,9 +234,9 @@ suite('ConnectionConfig', () => { test('getAllGroups should merge user and workspace settings correctly', () => { let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups).slice(0, 3), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups).slice(0, 3), ConfigurationTarget.USER); // we intentionally overlap these values with the expectation that the function to should return each group once - configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups).slice(2, testGroups.length), ConfigurationTarget.WORKSPACE); + configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups).slice(2, testGroups.length), ConfigurationTarget.WORKSPACE); let config = new ConnectionConfig(configurationService, capabilitiesService.object); let allGroups = config.getAllGroups(); @@ -242,7 +245,7 @@ suite('ConnectionConfig', () => { assert.ok(groupsAreEqual(allGroups, testGroups), 'the groups returned did not match expectation'); }); - test('addConnection should add the new profile to user settings', async () => { + test('addConnection should add the new profile to user settings if does not exist', async () => { let newProfile: IConnectionProfile = { serverName: 'new server', databaseName: 'database', @@ -262,15 +265,15 @@ suite('ConnectionConfig', () => { }; let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); - configurationService.updateValue('datasource.connections', deepClone(testConnections), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections), ConfigurationTarget.USER); let connectionProfile = new ConnectionProfile(capabilitiesService.object, newProfile); connectionProfile.options['databaseDisplayName'] = 'database'; let config = new ConnectionConfig(configurationService, capabilitiesService.object); let savedConnectionProfile = await config.addConnection(connectionProfile); - assert.ok(!!savedConnectionProfile.id); - assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length + 1); + assert.ok(!isUndefinedOrNull(savedConnectionProfile.id)); + assert.equal(configurationService.inspect(Constants.connectionsArrayName).user.length, testConnections.length + 1); }); test('addConnection should not add the new profile to user settings if already exists', async () => { @@ -294,8 +297,8 @@ suite('ConnectionConfig', () => { }; let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); - configurationService.updateValue('datasource.connections', deepClone(testConnections), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections), ConfigurationTarget.USER); let connectionProfile = new ConnectionProfile(capabilitiesService.object, newProfile); connectionProfile.options['databaseDisplayName'] = existingConnection.options['databaseName']; @@ -304,7 +307,7 @@ suite('ConnectionConfig', () => { let savedConnectionProfile = await config.addConnection(connectionProfile); assert.equal(savedConnectionProfile.id, existingConnection.id); - assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length); + assert.equal(configurationService.inspect(Constants.connectionsArrayName).user.length, testConnections.length); }); test('addConnection should add the new group to user settings if does not exist', async () => { @@ -327,21 +330,21 @@ suite('ConnectionConfig', () => { }; let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); - configurationService.updateValue('datasource.connections', deepClone(testConnections), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections), ConfigurationTarget.USER); let connectionProfile = new ConnectionProfile(capabilitiesService.object, newProfile); let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.addConnection(connectionProfile); - assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length + 1); - assert.equal(configurationService.inspect('datasource.connectionGroups').user.length, testGroups.length + 1); + assert.equal(configurationService.inspect(Constants.connectionsArrayName).user.length, testConnections.length + 1); + assert.equal(configurationService.inspect(Constants.connectionGroupsArrayName).user.length, testGroups.length + 1); }); test('getConnections should return connections from user and workspace settings given getWorkspaceConnections set to true', () => { let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connections', deepClone(testConnections).slice(0, 1), ConfigurationTarget.USER); - configurationService.updateValue('datasource.connections', deepClone(testConnections).slice(1, testConnections.length), ConfigurationTarget.WORKSPACE); + configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections).slice(0, 1), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections).slice(1, testConnections.length), ConfigurationTarget.WORKSPACE); let config = new ConnectionConfig(configurationService, capabilitiesService.object); let allConnections = config.getConnections(true); @@ -350,8 +353,8 @@ suite('ConnectionConfig', () => { test('getConnections should return connections from user settings given getWorkspaceConnections set to false', () => { let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connections', deepClone(testConnections).slice(0, 2), ConfigurationTarget.USER); - configurationService.updateValue('datasource.connections', deepClone(testConnections).slice(2, testConnections.length), ConfigurationTarget.WORKSPACE); + configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections).slice(0, 2), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections).slice(2, testConnections.length), ConfigurationTarget.WORKSPACE); let config = new ConnectionConfig(configurationService, capabilitiesService.object); let allConnections = config.getConnections(false); @@ -368,8 +371,8 @@ suite('ConnectionConfig', () => { return c; }); let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connections', userConnections, ConfigurationTarget.USER); - configurationService.updateValue('datasource.connections', workspaceConnections, ConfigurationTarget.WORKSPACE); + configurationService.updateValue(Constants.connectionsArrayName, userConnections, ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionsArrayName, workspaceConnections, ConfigurationTarget.WORKSPACE); let config = new ConnectionConfig(configurationService, capabilitiesService.object); let allConnections = config.getConnections(false); @@ -378,7 +381,7 @@ suite('ConnectionConfig', () => { let userConnection = testConnections.find(u => u.options['serverName'] === connection.serverName); if (userConnection !== undefined) { assert.notEqual(connection.id, connection.getOptionsKey()); - assert.ok(!!connection.id); + assert.ok(!isUndefinedOrNull(connection.id)); } else { let workspaceConnection = workspaceConnections.find(u => u.options['serverName'] === connection.serverName); assert.notEqual(connection.id, connection.getOptionsKey()); @@ -394,7 +397,7 @@ suite('ConnectionConfig', () => { let color: string = 'red'; let result: ISaveGroupResult = config.saveGroup(groups, newGroups, color, newGroups); - assert.ok(!!result); + assert.ok(!isUndefinedOrNull(result)); assert.equal(result.groups.length, testGroups.length + 2, 'The result groups length is invalid'); let newGroup = result.groups.find(g => g.name === 'new-group2'); assert.equal(result.newGroupId, newGroup.id, 'The groups id is invalid'); @@ -407,7 +410,7 @@ suite('ConnectionConfig', () => { let color: string = 'red'; let result: ISaveGroupResult = config.saveGroup(groups, newGroups, color, newGroups); - assert.ok(!!result); + assert.ok(!isUndefinedOrNull(result)); assert.equal(result.groups.length, testGroups.length + 1, 'The result groups length is invalid'); let newGroup = result.groups.find(g => g.name === 'g2-5'); assert.equal(result.newGroupId, newGroup.id, 'The groups id is invalid'); @@ -420,7 +423,7 @@ suite('ConnectionConfig', () => { let color: string = 'red'; let result: ISaveGroupResult = config.saveGroup(groups, newGroups, color, newGroups); - assert.ok(!!result); + assert.ok(!isUndefinedOrNull(result)); assert.equal(result.groups.length, testGroups.length, 'The result groups length is invalid'); let newGroup = result.groups.find(g => g.name === 'g2-1'); assert.equal(result.newGroupId, newGroup.id, 'The groups id is invalid'); @@ -445,7 +448,7 @@ suite('ConnectionConfig', () => { connectionName: undefined }; let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connections', deepClone(testConnections), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections), ConfigurationTarget.USER); let connectionProfile = new ConnectionProfile(capabilitiesService.object, newProfile); connectionProfile.options['databaseDisplayName'] = 'database'; @@ -453,7 +456,7 @@ suite('ConnectionConfig', () => { let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.deleteConnection(connectionProfile); - assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length - 1); + assert.equal(configurationService.inspect(Constants.connectionsArrayName).user.length, testConnections.length - 1); }); test('deleteConnectionGroup should remove the children connections and subgroups from config', async () => { @@ -475,8 +478,8 @@ suite('ConnectionConfig', () => { connectionName: undefined }; let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connections', deepClone(testConnections), ConfigurationTarget.USER); - configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); let connectionProfile = new ConnectionProfile(capabilitiesService.object, newProfile); connectionProfile.options['databaseDisplayName'] = 'database'; @@ -489,8 +492,8 @@ suite('ConnectionConfig', () => { let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.deleteGroup(connectionProfileGroup); - assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length - 1); - assert.equal(configurationService.inspect('datasource.connectionGroups').user.length, testGroups.length - 2); + assert.equal(configurationService.inspect(Constants.connectionsArrayName).user.length, testConnections.length - 1); + assert.equal(configurationService.inspect(Constants.connectionGroupsArrayName).user.length, testGroups.length - 2); }); test('deleteConnection should not throw error for connection not in config', async () => { @@ -512,34 +515,34 @@ suite('ConnectionConfig', () => { connectionName: undefined }; let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connections', deepClone(testConnections), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections), ConfigurationTarget.USER); let connectionProfile = new ConnectionProfile(capabilitiesService.object, newProfile); let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.deleteConnection(connectionProfile); - assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length); + assert.equal(configurationService.inspect(Constants.connectionsArrayName).user.length, testConnections.length); }); test('renameGroup should change group name', async () => { let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); let connectionProfileGroup = new ConnectionProfileGroup('g-renamed', undefined, 'g2', undefined, undefined); let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.editGroup(connectionProfileGroup); - let editedGroups = configurationService.inspect('datasource.connectionGroups').user; + let editedGroups = configurationService.inspect(Constants.connectionGroupsArrayName).user; assert.equal(editedGroups.length, testGroups.length); let editedGroup = editedGroups.find(group => group.id === 'g2'); - assert.ok(!!editedGroup); + assert.ok(!isUndefinedOrNull(editedGroup)); assert.equal(editedGroup.name, 'g-renamed'); }); test('edit group should throw if there is a confliction', async () => { let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); let sameNameGroup = new ConnectionProfileGroup('g3', undefined, 'g2', undefined, undefined); let config = new ConnectionConfig(configurationService, capabilitiesService.object); @@ -548,27 +551,27 @@ suite('ConnectionConfig', () => { await config.editGroup(sameNameGroup); assert.fail(); } catch (e) { - let groups = configurationService.inspect('datasource.connectionGroups').user; + let groups = configurationService.inspect(Constants.connectionGroupsArrayName).user; let originalGroup = groups.find(g => g.id === 'g2'); - assert.ok(!!originalGroup); + assert.ok(!isUndefinedOrNull(originalGroup)); assert.equal(originalGroup.name, 'g2'); } }); test('change group(parent) for connection group', async () => { let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); let sourceProfileGroup = new ConnectionProfileGroup('g2', undefined, 'g2', undefined, undefined); let targetProfileGroup = new ConnectionProfileGroup('g3', undefined, 'g3', undefined, undefined); let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.changeGroupIdForConnectionGroup(sourceProfileGroup, targetProfileGroup); - let editedGroups = configurationService.inspect('datasource.connectionGroups').user; + let editedGroups = configurationService.inspect(Constants.connectionGroupsArrayName).user; assert.equal(editedGroups.length, testGroups.length); let editedGroup = editedGroups.find(group => group.id === 'g2'); - assert.ok(!!editedGroup); + assert.ok(!isUndefinedOrNull(editedGroup)); assert.equal(editedGroup.parentId, 'g3'); }); @@ -612,7 +615,7 @@ suite('ConnectionConfig', () => { let _testConnections = deepClone(testConnections).concat([existingProfile, changingProfile]); let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connections', _testConnections, ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionsArrayName, _testConnections, ConfigurationTarget.USER); let connectionProfile = new ConnectionProfile(capabilitiesService.object, changingProfile); @@ -621,11 +624,11 @@ suite('ConnectionConfig', () => { await config.changeGroupIdForConnection(connectionProfile, 'test'); assert.fail(); } catch (e) { - let editedConnections = configurationService.inspect('datasource.connections').user; + let editedConnections = configurationService.inspect(Constants.connectionsArrayName).user; // two assert.equal(editedConnections.length, _testConnections.length); let editedConnection = editedConnections.find(con => con.id === 'server3-2'); - assert.ok(!!editedConnection); + assert.ok(!isUndefinedOrNull(editedConnection)); assert.equal(editedConnection.groupId, 'g3'); } }); @@ -650,7 +653,7 @@ suite('ConnectionConfig', () => { }; let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connections', deepClone(testConnections), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections), ConfigurationTarget.USER); let connectionProfile = new ConnectionProfile(capabilitiesService.object, newProfile); let newId = 'newid'; @@ -658,18 +661,18 @@ suite('ConnectionConfig', () => { let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.changeGroupIdForConnection(connectionProfile, newId); - let editedConnections = configurationService.inspect('datasource.connections').user; + let editedConnections = configurationService.inspect(Constants.connectionsArrayName).user; assert.equal(editedConnections.length, testConnections.length); let editedConnection = editedConnections.find(con => con.id === 'server3'); - assert.ok(!!editedConnection); + assert.ok(!isUndefinedOrNull(editedConnection)); assert.equal(editedConnection.groupId, 'newid'); }); test('addConnection should not move the connection when editing', async () => { // Set up the connection config let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connections', deepClone(testConnections), ConfigurationTarget.USER); - configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); let config = new ConnectionConfig(configurationService, capabilitiesService.object); // Clone a connection and modify an option @@ -699,13 +702,13 @@ suite('ConnectionConfig', () => { description: 'new group' }; let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.addGroup(newGroup); - let editGroups = configurationService.inspect('datasource.connectionGroups').user; + let editGroups = configurationService.inspect(Constants.connectionGroupsArrayName).user; assert.equal(editGroups.length, testGroups.length + 1); }); @@ -719,14 +722,14 @@ suite('ConnectionConfig', () => { description: 'new group' }; let configurationService = new TestConfigurationService(); - configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); const config = new ConnectionConfig(configurationService, capabilitiesService.object); try { await config.addGroup(existingGroupName); assert.fail(); } catch (e) { - let editGroups = configurationService.inspect('datasource.connectionGroups').user; + let editGroups = configurationService.inspect(Constants.connectionGroupsArrayName).user; assert.equal(editGroups.length, testGroups.length); } diff --git a/src/sql/platform/connection/test/common/connectionStore.test.ts b/src/sql/platform/connection/test/common/connectionStore.test.ts deleted file mode 100644 index a7ebd29b06..0000000000 --- a/src/sql/platform/connection/test/common/connectionStore.test.ts +++ /dev/null @@ -1,528 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import * as assert from 'assert'; -import * as azdata from 'azdata'; -import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; -import { IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; -import { ConnectionStore } from 'sql/platform/connection/common/connectionStore'; -import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; -import { TestConfigurationService } from 'sql/platform/connection/test/common/testConfigurationService'; -import { TestStateService } from 'sql/platform/connection/test/common/testStateService'; -import { TestCredentialsService } from 'sql/platform/credentials/test/common/testCredentialsService'; -import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes'; -import { ConnectionProviderProperties } from 'sql/workbench/parts/connection/common/connectionProviderExtension'; -import { CapabilitiesTestService } from 'sqltest/stubs/capabilitiesTestService'; -import { deepClone, deepFreeze } from 'vs/base/common/objects'; -import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; - -suite('ConnectionStore', () => { - let defaultNamedProfile: IConnectionProfile = deepFreeze({ - connectionName: 'new name', - serverName: 'namedServer', - databaseName: 'bcd', - authenticationType: 'SqlLogin', - userName: 'cde', - password: 'asdf!@#$', - savePassword: true, - groupId: '', - groupFullName: '', - getOptionsKey: undefined, - matches: undefined, - providerName: 'MSSQL', - options: {}, - saveProfile: true, - id: undefined - }); - let capabilitiesService: CapabilitiesTestService; - let maxRecent = 5; - let msSQLCapabilities: ConnectionProviderProperties; - let provider2Capabilities: ConnectionProviderProperties; - let defaultNamedConnectionProfile: ConnectionProfile; - - setup(() => { - // setup configuration to return maxRecent for the #MRU items - - capabilitiesService = new CapabilitiesTestService(); - let connectionProvider: azdata.ConnectionOption[] = [ - { - name: 'connectionName', - displayName: undefined, - description: undefined, - groupName: undefined, - categoryValues: undefined, - defaultValue: undefined, - isIdentity: true, - isRequired: true, - specialValueType: ConnectionOptionSpecialType.connectionName, - valueType: ServiceOptionType.string - }, - { - name: 'serverName', - displayName: undefined, - description: undefined, - groupName: undefined, - categoryValues: undefined, - defaultValue: undefined, - isIdentity: true, - isRequired: true, - specialValueType: ConnectionOptionSpecialType.serverName, - valueType: ServiceOptionType.string - }, - { - name: 'databaseName', - displayName: undefined, - description: undefined, - groupName: undefined, - categoryValues: undefined, - defaultValue: undefined, - isIdentity: true, - isRequired: true, - specialValueType: ConnectionOptionSpecialType.databaseName, - valueType: ServiceOptionType.string - }, - { - name: 'userName', - displayName: undefined, - description: undefined, - groupName: undefined, - categoryValues: undefined, - defaultValue: undefined, - isIdentity: true, - isRequired: true, - specialValueType: ConnectionOptionSpecialType.userName, - valueType: ServiceOptionType.string - }, - { - name: 'authenticationType', - displayName: undefined, - description: undefined, - groupName: undefined, - categoryValues: undefined, - defaultValue: undefined, - isIdentity: true, - isRequired: true, - specialValueType: ConnectionOptionSpecialType.authType, - valueType: ServiceOptionType.string - }, - { - name: 'password', - displayName: undefined, - description: undefined, - groupName: undefined, - categoryValues: undefined, - defaultValue: undefined, - isIdentity: true, - isRequired: true, - specialValueType: ConnectionOptionSpecialType.password, - valueType: ServiceOptionType.string - } - ]; - msSQLCapabilities = { - providerId: 'MSSQL', - displayName: 'MSSQL', - connectionOptions: connectionProvider - }; - - provider2Capabilities = { - providerId: 'MSSQL', - displayName: 'MSSQL', - connectionOptions: connectionProvider - }; - capabilitiesService.capabilities['MSSQL'] = { connection: msSQLCapabilities }; - capabilitiesService.capabilities['Provider2'] = { connection: provider2Capabilities }; - - defaultNamedConnectionProfile = new ConnectionProfile(capabilitiesService, defaultNamedProfile); - }); - - test('addActiveConnection should limit recent connection saves to the MaxRecentConnections amount', async () => { - // Given 5 is the max # creds - let numCreds = 6; - - let stateService = new TestStateService(); - let configurationService = new TestConfigurationService(); - let credentialsService = new TestCredentialsService(); - - await configurationService.updateValue('sql.maxRecentConnections', 5, ConfigurationTarget.USER); - - // When saving 4 connections - // Expect all of them to be saved even if size is limited to 3 - let connectionStore = new ConnectionStore(stateService, configurationService, - credentialsService, capabilitiesService); - for (let i = 0; i < numCreds; i++) { - let cred = Object.assign({}, defaultNamedProfile, { serverName: defaultNamedProfile.serverName + i }); - let connectionProfile = new ConnectionProfile(capabilitiesService, cred); - await connectionStore.addRecentConnection(connectionProfile); - let current = connectionStore.getRecentlyUsedConnections(); - if (i >= maxRecent) { - assert.equal(current.length, maxRecent, `expect only top ${maxRecent} creds to be saved`); - } else { - assert.equal(current.length, i + 1, `expect all credentials to be saved ${current.length}|${i + 1} `); - } - assert.equal(current[0].serverName, cred.serverName, 'Expect most recently saved item to be first in list'); - assert.ok(!current[0].password); - } - assert.equal(credentialsService.credentials.size, numCreds); - }); - - test('getRecentlyUsedConnections should return connection for given provider', () => { - let stateService = new TestStateService(); - let configurationService = new TestConfigurationService(); - let credentialsService = new TestCredentialsService(); - let connectionStore = new ConnectionStore(stateService, configurationService, - credentialsService, capabilitiesService); - let connections = connectionStore.getRecentlyUsedConnections(['Provider2']); - assert.ok(!!connections); - assert.ok(connections.every(c => c.providerName === 'Provider2')); - }); - - test('addActiveConnection should add same connection exactly once', async () => { - - let stateService = new TestStateService(); - let configurationService = new TestConfigurationService(); - let credentialsService = new TestCredentialsService(); - - // Given we save the same connection twice - // Then expect the only 1 instance of that connection to be listed in the MRU - let connectionStore = new ConnectionStore(stateService, configurationService, - credentialsService, capabilitiesService); - let cred = Object.assign({}, defaultNamedProfile, { serverName: defaultNamedProfile.serverName + 1 }); - let connectionProfile = new ConnectionProfile(capabilitiesService, cred); - await connectionStore.addRecentConnection(defaultNamedConnectionProfile); - await connectionStore.addRecentConnection(connectionProfile); - await connectionStore.addRecentConnection(connectionProfile); - let current = connectionStore.getRecentlyUsedConnections(); - assert.equal(current.length, 2, 'expect 2 unique credentials to have been added'); - assert.equal(current[0].serverName, cred.serverName, 'Expect most recently saved item to be first in list'); - assert.ok(!current[0].password); - }); - - test('addActiveConnection should save password to credential store', async () => { - - // Setup credential store to capture credentials sent to it - - let stateService = new TestStateService(); - let configurationService = new TestConfigurationService(); - let credentialsService = new TestCredentialsService(); - - // Given we save 1 connection with password and multiple other connections without - let connectionStore = new ConnectionStore(stateService, configurationService, - credentialsService, capabilitiesService); - let integratedCred = Object.assign({}, defaultNamedProfile, { - serverName: defaultNamedProfile.serverName + 'Integrated', - authenticationType: 'Integrated', - userName: '', - password: '' - }); - let noPwdCred = Object.assign({}, defaultNamedProfile, { - serverName: defaultNamedProfile.serverName + 'NoPwd', - password: '' - }); - let connectionProfile = new ConnectionProfile(capabilitiesService, defaultNamedProfile); - - let recentCredential: azdata.Credential; - credentialsService.onCredential(e => recentCredential = e); - - await connectionStore.addRecentConnection(connectionProfile); - - let current = connectionStore.getRecentlyUsedConnections(); - // Then verify that since its password based we save the password - assert.equal(credentialsService.credentials.size, 1); - assert.strictEqual(recentCredential.password, defaultNamedProfile.password); - assert.ok(recentCredential.credentialId.includes('Profile'), 'Expect credential to be marked as an Profile cred'); - assert.ok(!current[0].password); - // When add integrated auth connection - let integratedCredConnectionProfile = new ConnectionProfile(capabilitiesService, integratedCred); - await connectionStore.addRecentConnection(integratedCredConnectionProfile); - current = connectionStore.getRecentlyUsedConnections(); - // then expect not to have credential store called, but MRU count upped to 2 - assert.equal(credentialsService.credentials.size, 1); - assert.equal(current.length, 2); - // When add connection without password - let noPwdCredConnectionProfile = new ConnectionProfile(capabilitiesService, noPwdCred); - await connectionStore.addRecentConnection(noPwdCredConnectionProfile); - current = connectionStore.getRecentlyUsedConnections(); - // then expect not to have credential store called, but MRU count upped to 3 - assert.equal(current.length, 3); - assert.equal(credentialsService.credentials.size, 1); - }); - - test('can clear connections list', async () => { - let stateService = new TestStateService(); - let configurationService = new TestConfigurationService(); - let credentialsService = new TestCredentialsService(); - - let connectionStore = new ConnectionStore(stateService, configurationService, - credentialsService, capabilitiesService); - - await connectionStore.addRecentConnection(defaultNamedProfile); - let result = connectionStore.getRecentlyUsedConnections(); - assert.equal(result.length, 1); - connectionStore.clearRecentlyUsed(); - result = connectionStore.getRecentlyUsedConnections(); - assert.equal(result.length, 0); - // Then test is complete - }); - - test('isPasswordRequired should return true for MSSQL SqlLogin', () => { - let stateService = new TestStateService(); - let configurationService = new TestConfigurationService(); - let credentialsService = new TestCredentialsService(); - - let connectionStore = new ConnectionStore(stateService, configurationService, - credentialsService, capabilitiesService); - - assert.ok(connectionStore.isPasswordRequired(defaultNamedProfile)); - }); - - test('isPasswordRequired should return true for MSSQL SqlLogin for connection profile object', () => { - let stateService = new TestStateService(); - let configurationService = new TestConfigurationService(); - let credentialsService = new TestCredentialsService(); - - let connectionStore = new ConnectionStore(stateService, configurationService, - credentialsService, capabilitiesService); - let connectionProfile = new ConnectionProfile(capabilitiesService, defaultNamedProfile); - - assert.ok(connectionStore.isPasswordRequired(connectionProfile)); - }); - - test('isPasswordRequired should return false if the password is not required in capabilities', () => { - let stateService = new TestStateService(); - let configurationService = new TestConfigurationService(); - let credentialsService = new TestCredentialsService(); - - let providerName: string = 'providername'; - let connectionProvider = msSQLCapabilities.connectionOptions.map(o => { - if (o.name === 'password') { - o.isRequired = false; - } - return o; - }); - let providerCapabilities = { - providerId: providerName, - displayName: providerName, - connectionOptions: connectionProvider - }; - - capabilitiesService.capabilities[providerName] = { connection: providerCapabilities }; - - let connectionStore = new ConnectionStore(stateService, configurationService, - credentialsService, capabilitiesService); - let connectionProfile: IConnectionProfile = Object.assign({}, defaultNamedProfile, { providerName: providerName }); - - assert.ok(!connectionStore.isPasswordRequired(connectionProfile)); - }); - - test('saveProfile should save the password after the profile is saved', async () => { - let stateService = new TestStateService(); - let configurationService = new TestConfigurationService(); - let credentialsService = new TestCredentialsService(); - - let password: string = 'asdf!@#$'; - let connectionProfile: IConnectionProfile = Object.assign({}, defaultNamedProfile, { password }); - - let connectionStore = new ConnectionStore(stateService, configurationService, - credentialsService, capabilitiesService); - - let profile = await connectionStore.saveProfile(connectionProfile); - // add connection should be called with a profile without password - assert.equal(profile.password, password, 'The returned profile should still keep the password'); - assert.ok(!!profile.groupId, 'Group id should be set in the profile'); - }); - - test('getGroupFromId returns undefined when there is no group with the given ID', () => { - let stateService = new TestStateService(); - let configurationService = new TestConfigurationService(); - let credentialsService = new TestCredentialsService(); - - let connectionStore = new ConnectionStore(stateService, configurationService, - credentialsService, capabilitiesService); - let group = connectionStore.getGroupFromId('invalidId'); - assert.equal(group, undefined, 'Returned group was not undefined when there was no group with the given ID'); - }); - - test('getGroupFromId returns the group that has the given ID', () => { - let stateService = new TestStateService(); - let configurationService = new TestConfigurationService(); - let credentialsService = new TestCredentialsService(); - - let parentGroupId = 'parentGroup'; - let childGroupId = 'childGroup'; - - let groups: IConnectionProfileGroup[] = [ - { - id: parentGroupId, - name: parentGroupId, - color: undefined, - description: '', - parentId: '' - }, - { - id: childGroupId, - name: childGroupId, - color: undefined, - description: '', - parentId: parentGroupId - } - ]; - - configurationService.updateValue('datasource.connectionGroups', groups, ConfigurationTarget.USER); - let connectionStore = new ConnectionStore(stateService, configurationService, - credentialsService, capabilitiesService); - - // If I look up the parent group using its ID, then I get back the correct group - let actualGroup = connectionStore.getGroupFromId(parentGroupId); - assert.equal(actualGroup.id, parentGroupId, 'Did not get the parent group when looking it up with its ID'); - - // If I look up the child group using its ID, then I get back the correct group - actualGroup = connectionStore.getGroupFromId(childGroupId); - assert.equal(actualGroup.id, childGroupId, 'Did not get the child group when looking it up with its ID'); - }); - - test('getProfileWithoutPassword can return the profile without credentials in the password property or options dictionary', () => { - let stateService = new TestStateService(); - let configurationService = new TestConfigurationService(); - let credentialsService = new TestCredentialsService(); - - let connectionStore = new ConnectionStore(stateService, configurationService, - credentialsService, capabilitiesService); - let profile = deepClone(defaultNamedProfile); - profile.options['password'] = profile.password; - profile.id = 'testId'; - let expectedProfile = Object.assign({}, profile); - expectedProfile.password = ''; - expectedProfile.options['password'] = ''; - expectedProfile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, expectedProfile).toIConnectionProfile(); - let profileWithoutCredentials = connectionStore.getProfileWithoutPassword(profile); - assert.deepEqual(profileWithoutCredentials.toIConnectionProfile(), expectedProfile); - }); - - test('addPassword gets the password from the credentials service', async () => { - let stateService = new TestStateService(); - let configurationService = new TestConfigurationService(); - let credentialsService = new TestCredentialsService(); - - let profile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, Object.assign({}, defaultNamedProfile, { password: undefined })); - - let credId = `Microsoft.SqlTools|itemtype:Profile|id:${profile.getConnectionInfoId()}`; - let password: string = 'asdf!@#$'; - - await credentialsService.saveCredential(credId, password); - - let connectionStore = new ConnectionStore(stateService, configurationService, - credentialsService, capabilitiesService); - - let passwordProfile = (await connectionStore.addSavedPassword(profile)).profile; - - assert.equal(passwordProfile.password, password); - }); - - test('getConnectionProfileGroups', async () => { - let stateService = new TestStateService(); - let configurationService = new TestConfigurationService(); - let credentialsService = new TestCredentialsService(); - - let parentGroupId = 'parentGroup'; - let childGroupId = 'childGroup'; - let groups: IConnectionProfileGroup[] = [ - { - id: parentGroupId, - name: parentGroupId, - color: undefined, - description: '', - parentId: '' - }, - { - id: childGroupId, - name: childGroupId, - color: undefined, - description: '', - parentId: parentGroupId - } - ]; - - configurationService.updateValue('datasource.connectionGroups', groups, ConfigurationTarget.USER); - - let connectionStore = new ConnectionStore(stateService, configurationService, - credentialsService, capabilitiesService); - - let connectionGroups = connectionStore.getConnectionProfileGroups(); - - for (let group of connectionGroups) { - let foundGroup = groups.find(g => g.id === group.id); - assert.ok(foundGroup); - } - }); - - test('removing connection correctly removes', async () => { - let stateService = new TestStateService(); - let configurationService = new TestConfigurationService(); - let credentialsService = new TestCredentialsService(); - - let connectionStore = new ConnectionStore(stateService, configurationService, - credentialsService, capabilitiesService); - - for (let i = 0; i < 5; i++) { - let cred = Object.assign({}, defaultNamedProfile, { serverName: defaultNamedProfile.serverName + i }); - let connectionProfile = new ConnectionProfile(capabilitiesService, cred); - await connectionStore.addRecentConnection(connectionProfile); - let current = connectionStore.getRecentlyUsedConnections(); - assert.equal(current.length, i + 1); - } - - for (let i = 0; i < 5; i++) { - let cred = Object.assign({}, defaultNamedProfile, { serverName: defaultNamedProfile.serverName + i }); - let connectionProfile = new ConnectionProfile(capabilitiesService, cred); - await connectionStore.removeRecentConnection(connectionProfile); - let current = connectionStore.getRecentlyUsedConnections(); - assert.equal(current.length, 4 - i); - } - }); - - test('getRecentlyUsedConnections correctly fills in group names', async () => { - let stateService = new TestStateService(); - let configurationService = new TestConfigurationService(); - let credentialsService = new TestCredentialsService(); - - let connectionStore = new ConnectionStore(stateService, configurationService, - credentialsService, capabilitiesService); - - let parentGroupId = 'parentGroup'; - let parentGroupName = 'parentGroupName'; - let group: IConnectionProfileGroup = { - id: parentGroupId, - name: parentGroupName, - color: undefined, - description: '', - parentId: '' - }; - let connection: azdata.IConnectionProfile = { - options: [], - connectionName: '', - serverName: 'server1', - databaseName: 'database', - userName: 'user', - password: 'password', - authenticationType: '', - providerName: 'MSSQL', - groupId: parentGroupId, - groupFullName: '', - savePassword: true, - saveProfile: true, - id: 'server1' - }; - - configurationService.updateValue('datasource.connectionGroups', [group], ConfigurationTarget.USER); - configurationService.updateValue('datasource.connections', [connection], ConfigurationTarget.USER); - - connectionStore.addRecentConnection(ConnectionProfile.fromIConnectionProfile(capabilitiesService, connection)); - - let connections = connectionStore.getRecentlyUsedConnections(); - - assert.equal(connections[0].groupFullName, parentGroupName); - }); -}); diff --git a/src/sql/platform/connection/test/common/testConfigurationService.ts b/src/sql/platform/connection/test/common/testConfigurationService.ts index e2e930d7a6..232fa6eb6e 100644 --- a/src/sql/platform/connection/test/common/testConfigurationService.ts +++ b/src/sql/platform/connection/test/common/testConfigurationService.ts @@ -17,8 +17,13 @@ export class TestConfigurationService implements IConfigurationService { return Promise.resolve(this.getValue()); } - public getValue(arg1?: any): any { - return getConfigurationValue(this.configuration.user, arg1); + public getValue(arg1?: any, arg2?: any): any { + let configuration; + configuration = configuration ? configuration : this.configuration; + if (arg1 && typeof arg1 === 'string') { + return getConfigurationValue(configuration, arg1); + } + return configuration; } public updateValue(key: string, value: any, target?: any): Promise { diff --git a/src/sql/platform/connection/test/common/testStateService.ts b/src/sql/platform/connection/test/common/testStateService.ts deleted file mode 100644 index c2ff91a2c7..0000000000 --- a/src/sql/platform/connection/test/common/testStateService.ts +++ /dev/null @@ -1,30 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import { IStateService } from 'vs/platform/state/common/state'; - -export class TestStateService implements IStateService { - _serviceBrand: any; - - private storage = {}; - - constructor() { } - - getItem(key: string, defaultValue: T): T; - getItem(key: string, defaultValue: T | undefined): T | undefined; - getItem(key: string, defaultValue?: T): T | undefined { - return this.storage[key] || defaultValue; - } - - setItem(key: string, data: any): void { - this.storage[key] = data; - } - - removeItem(key: string): void { - delete this.storage[key]; - } -} diff --git a/src/sql/platform/credentials/common/credentialsService.ts b/src/sql/platform/credentials/common/credentialsService.ts index b2e3f4a067..c7879a2ea3 100644 --- a/src/sql/platform/credentials/common/credentialsService.ts +++ b/src/sql/platform/credentials/common/credentialsService.ts @@ -25,11 +25,11 @@ export const ICredentialsService = createDecorator(SERVICE_ export interface ICredentialsService { _serviceBrand: any; - saveCredential(credentialId: string, password: string): Promise; + saveCredential(credentialId: string, password: string): Thenable; - readCredential(credentialId: string): Promise; + readCredential(credentialId: string): Thenable; - deleteCredential(credentialId: string): Promise; + deleteCredential(credentialId: string): Thenable; addEventListener(handle: number, events: CredentialManagementEvents): IDisposable; } @@ -62,15 +62,15 @@ export class CredentialsService implements ICredentialsService { }; } - public saveCredential(credentialId: string, password: string): Promise { + public saveCredential(credentialId: string, password: string): Thenable { return this._onServerEventsReady.promise.then(() => this._serverEvents[this._lastHandle].onSaveCredential(credentialId, password)); } - public readCredential(credentialId: string): Promise { + public readCredential(credentialId: string): Thenable { return this._onServerEventsReady.promise.then(() => this._serverEvents[this._lastHandle].onReadCredential(credentialId)); } - public deleteCredential(credentialId: string): Promise { + public deleteCredential(credentialId: string): Thenable { return this._onServerEventsReady.promise.then(() => this._serverEvents[this._lastHandle].onDeleteCredential(credentialId)); } diff --git a/src/sql/platform/credentials/test/common/testCredentialsService.ts b/src/sql/platform/credentials/test/common/testCredentialsService.ts deleted file mode 100644 index c1bea76556..0000000000 --- a/src/sql/platform/credentials/test/common/testCredentialsService.ts +++ /dev/null @@ -1,44 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import { ICredentialsService, CredentialManagementEvents } from 'sql/platform/credentials/common/credentialsService'; -import { Credential } from 'azdata'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { Emitter } from 'vs/base/common/event'; - -export class TestCredentialsService implements ICredentialsService { - _serviceBrand: any; - - public credentials = new Map(); - - private _onCredential = new Emitter(); - public readonly onCredential = this._onCredential.event; - - saveCredential(credentialId: string, password: string): Promise { - let credential = { credentialId, password }; - this.credentials.set(credentialId, credential); - this._onCredential.fire(credential); - return Promise.resolve(true); - } - - readCredential(credentialId: string): Promise { - let cred = this.credentials.get(credentialId); - if (cred) { - return Promise.resolve(cred); - } else { - return Promise.reject(''); - } - } - - deleteCredential(credentialId: string): Promise { - return Promise.resolve(this.credentials.delete(credentialId)); - } - - addEventListener(handle: number, events: CredentialManagementEvents): IDisposable { - throw new Error('Method not implemented.'); - } -} diff --git a/src/sqltest/parts/connection/connectionManagementService.test.ts b/src/sqltest/parts/connection/connectionManagementService.test.ts index 1a732e2598..4efc223046 100644 --- a/src/sqltest/parts/connection/connectionManagementService.test.ts +++ b/src/sqltest/parts/connection/connectionManagementService.test.ts @@ -99,7 +99,7 @@ suite('SQL ConnectionManagementService tests', () => { connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(none)); connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), undefined, TypeMoq.It.isAny())).returns(() => Promise.resolve(none)); - connectionStore.setup(x => x.addRecentConnection(TypeMoq.It.isAny())).returns(() => Promise.resolve()); + connectionStore.setup(x => x.addActiveConnection(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve()); connectionStore.setup(x => x.saveProfile(TypeMoq.It.isAny())).returns(() => Promise.resolve(connectionProfile)); workbenchEditorService.setup(x => x.openEditor(undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); connectionStore.setup(x => x.addSavedPassword(TypeMoq.It.is( @@ -147,13 +147,16 @@ suite('SQL ConnectionManagementService tests', () => { function createConnectionManagementService(): ConnectionManagementService { let connectionManagementService = new ConnectionManagementService( + undefined, connectionStore.object, + new TestStorageService(), connectionDialogService.object, undefined, undefined, workbenchEditorService.object, undefined, workspaceConfigurationServiceMock.object, + undefined, capabilitiesService, undefined, editorGroupService.object, diff --git a/src/sqltest/parts/connection/connectionStore.test.ts b/src/sqltest/parts/connection/connectionStore.test.ts new file mode 100644 index 0000000000..a47048bde5 --- /dev/null +++ b/src/sqltest/parts/connection/connectionStore.test.ts @@ -0,0 +1,508 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as TypeMoq from 'typemoq'; +import { ConnectionConfig } from 'sql/platform/connection/common/connectionConfig'; +import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; +import { WorkspaceConfigurationTestService } from 'sqltest/stubs/workspaceConfigurationTestService'; +import * as Constants from 'sql/platform/connection/common/constants'; +import { StorageTestService } from 'sqltest/stubs/storageTestService'; +import { ConnectionStore } from 'sql/platform/connection/common/connectionStore'; +import { CredentialsService } from 'sql/platform/credentials/common/credentialsService'; +import * as assert from 'assert'; +import { Memento } from 'vs/workbench/common/memento'; +import * as azdata from 'azdata'; +import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; +import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; +import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes'; +import { CapabilitiesTestService } from '../../stubs/capabilitiesTestService'; +import { ConnectionProviderProperties } from 'sql/workbench/parts/connection/common/connectionProviderExtension'; + +suite('SQL ConnectionStore tests', () => { + let defaultNamedProfile: IConnectionProfile; + let context: TypeMoq.Mock; + let credentialStore: TypeMoq.Mock; + let connectionConfig: TypeMoq.Mock; + let workspaceConfigurationServiceMock: TypeMoq.Mock; + let storageServiceMock: TypeMoq.Mock; + let capabilitiesService: CapabilitiesTestService; + let mementoArray: any = []; + let maxRecent = 5; + let msSQLCapabilities: ConnectionProviderProperties; + let provider2Capabilities: ConnectionProviderProperties; + let defaultNamedConnectionProfile: ConnectionProfile; + + setup(() => { + defaultNamedProfile = Object.assign({}, { + connectionName: 'new name', + serverName: 'namedServer', + databaseName: 'bcd', + authenticationType: 'SqlLogin', + userName: 'cde', + password: 'asdf!@#$', + savePassword: true, + groupId: '', + groupFullName: '', + getOptionsKey: undefined, + matches: undefined, + providerName: 'MSSQL', + options: {}, + saveProfile: true, + id: undefined + }); + + storageServiceMock = TypeMoq.Mock.ofType(StorageTestService); + + let momento = new Memento('ConnectionManagement', storageServiceMock.object); + context = TypeMoq.Mock.ofInstance(momento); + context.setup(x => x.getMemento(TypeMoq.It.isAny())).returns(() => mementoArray); + + credentialStore = TypeMoq.Mock.ofType(CredentialsService); + connectionConfig = TypeMoq.Mock.ofType(ConnectionConfig); + + // setup configuration to return maxRecent for the #MRU items + + let configResult: { [key: string]: any } = {}; + configResult[Constants.configMaxRecentConnections] = maxRecent; + + workspaceConfigurationServiceMock = TypeMoq.Mock.ofType(WorkspaceConfigurationTestService); + workspaceConfigurationServiceMock.setup(x => x.getValue(Constants.sqlConfigSectionName)) + .returns(() => configResult); + + capabilitiesService = new CapabilitiesTestService(); + let connectionProvider: azdata.ConnectionOption[] = [ + { + name: 'connectionName', + displayName: undefined, + description: undefined, + groupName: undefined, + categoryValues: undefined, + defaultValue: undefined, + isIdentity: true, + isRequired: true, + specialValueType: ConnectionOptionSpecialType.connectionName, + valueType: ServiceOptionType.string + }, + { + name: 'serverName', + displayName: undefined, + description: undefined, + groupName: undefined, + categoryValues: undefined, + defaultValue: undefined, + isIdentity: true, + isRequired: true, + specialValueType: ConnectionOptionSpecialType.serverName, + valueType: ServiceOptionType.string + }, + { + name: 'databaseName', + displayName: undefined, + description: undefined, + groupName: undefined, + categoryValues: undefined, + defaultValue: undefined, + isIdentity: true, + isRequired: true, + specialValueType: ConnectionOptionSpecialType.databaseName, + valueType: ServiceOptionType.string + }, + { + name: 'userName', + displayName: undefined, + description: undefined, + groupName: undefined, + categoryValues: undefined, + defaultValue: undefined, + isIdentity: true, + isRequired: true, + specialValueType: ConnectionOptionSpecialType.userName, + valueType: ServiceOptionType.string + }, + { + name: 'authenticationType', + displayName: undefined, + description: undefined, + groupName: undefined, + categoryValues: undefined, + defaultValue: undefined, + isIdentity: true, + isRequired: true, + specialValueType: ConnectionOptionSpecialType.authType, + valueType: ServiceOptionType.string + }, + { + name: 'password', + displayName: undefined, + description: undefined, + groupName: undefined, + categoryValues: undefined, + defaultValue: undefined, + isIdentity: true, + isRequired: true, + specialValueType: ConnectionOptionSpecialType.password, + valueType: ServiceOptionType.string + } + ]; + msSQLCapabilities = { + providerId: 'MSSQL', + displayName: 'MSSQL', + connectionOptions: connectionProvider + }; + + provider2Capabilities = { + providerId: 'MSSQL', + displayName: 'MSSQL', + connectionOptions: connectionProvider + }; + capabilitiesService.capabilities['MSSQL'] = { connection: msSQLCapabilities }; + capabilitiesService.capabilities['Provider2'] = { connection: provider2Capabilities }; + let groups: IConnectionProfileGroup[] = [ + { + id: 'root', + name: 'root', + parentId: '', + color: '', + description: '' + }, + { + id: 'g1', + name: 'g1', + parentId: 'root', + color: 'blue', + description: 'g1' + } + ]; + connectionConfig.setup(x => x.getAllGroups()).returns(() => groups); + + defaultNamedConnectionProfile = new ConnectionProfile(capabilitiesService, defaultNamedProfile); + }); + + test('addActiveConnection should limit recent connection saves to the MaxRecentConnections amount', async () => { + // Given 5 is the max # creds + let numCreds = 6; + + // setup memento for MRU to return a list we have access to + credentialStore.setup(x => x.saveCredential(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => Promise.resolve(true)); + + // When saving 4 connections + // Expect all of them to be saved even if size is limited to 3 + let connectionStore = new ConnectionStore(context.object, workspaceConfigurationServiceMock.object, + credentialStore.object, capabilitiesService, connectionConfig.object); + for (let i = 0; i < numCreds; i++) { + let cred = Object.assign({}, defaultNamedProfile, { serverName: defaultNamedProfile.serverName + i }); + let connectionProfile = new ConnectionProfile(capabilitiesService, cred); + await connectionStore.addActiveConnection(connectionProfile, true); + + let current = connectionStore.getRecentlyUsedConnections(); + if (i >= maxRecent) { + assert.equal(current.length, maxRecent, `expect only top ${maxRecent} creds to be saved to MRU`); + } else { + assert.equal(current.length, i + 1, `expect all credentials to be saved to MRU ${current.length}|${i + 1} `); + } + assert.equal(current[0].serverName, cred.serverName, 'Expect most recently saved item to be first in list'); + assert.ok(!current[0].password); + } + credentialStore.verify(x => x.saveCredential(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(numCreds)); + let recentConnections = connectionStore.getActiveConnections(); + assert.equal(numCreds, recentConnections.length, `expect number of active connection ${numCreds}|${recentConnections.length} `); + }); + + test('addActiveConnection with addToMru as false should not add any recent connections', async () => { + // setup memento for MRU to return a list we have access to + credentialStore.setup(x => x.saveCredential(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => Promise.resolve(true)); + + const numCreds = 3; + let connectionStore = new ConnectionStore(context.object, workspaceConfigurationServiceMock.object, + credentialStore.object, capabilitiesService, connectionConfig.object); + connectionStore.clearActiveConnections(); + connectionStore.clearRecentlyUsed(); + + for (let i = 0; i < 3; i++) { + let cred = Object.assign({}, defaultNamedProfile, { serverName: defaultNamedProfile.serverName + i }); + let connectionProfile = new ConnectionProfile(capabilitiesService, cred); + await connectionStore.addActiveConnection(connectionProfile, false); + + let recentConnections = connectionStore.getRecentlyUsedConnections(); + let activeConnections = connectionStore.getActiveConnections(); + assert.equal(recentConnections.length, 0, `expect no entries to be saved to MRU`); + assert.equal(activeConnections.length, i + 1, `expect all credentials to be saved to activeConnections ${activeConnections.length}|${i + 1} `); + } + credentialStore.verify(x => x.saveCredential(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(numCreds)); + }); + + test('getRecentlyUsedConnections should return connection for given provider', () => { + let connectionStore = new ConnectionStore(context.object, workspaceConfigurationServiceMock.object, + credentialStore.object, capabilitiesService, connectionConfig.object); + let connections = connectionStore.getRecentlyUsedConnections(['Provider2']); + assert.notEqual(connections, undefined); + assert.equal(connections.every(c => c.providerName === 'Provider2'), true); + }); + + test('addActiveConnection should add same connection exactly once', async () => { + // setup memento for MRU to return a list we have access to + credentialStore.setup(x => x.saveCredential(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => Promise.resolve(true)); + + // Given we save the same connection twice + // Then expect the only 1 instance of that connection to be listed in the MRU + let connectionStore = new ConnectionStore(context.object, workspaceConfigurationServiceMock.object, + credentialStore.object, capabilitiesService, connectionConfig.object); + connectionStore.clearActiveConnections(); + connectionStore.clearRecentlyUsed(); + let cred = Object.assign({}, defaultNamedProfile, { serverName: defaultNamedProfile.serverName + 1 }); + let connectionProfile = new ConnectionProfile(capabilitiesService, cred); + await connectionStore.addActiveConnection(defaultNamedConnectionProfile, true); + await connectionStore.addActiveConnection(connectionProfile, true); + await connectionStore.addActiveConnection(connectionProfile, true); + + let recentConnections = connectionStore.getRecentlyUsedConnections(); + assert.equal(recentConnections.length, 2, 'expect 2 unique credentials to have been added'); + assert.equal(recentConnections[0].serverName, cred.serverName, 'Expect most recently saved item to be first in list'); + assert.ok(!recentConnections[0].password); + }); + + test('addActiveConnection should save password to credential store', async () => { + // Setup credential store to capture credentials sent to it + let capturedCreds: any; + credentialStore.setup(x => x.saveCredential(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .callback((cred: string, pass: any) => { + capturedCreds = { + 'credentialId': cred, + 'password': pass + }; + }) + .returns(() => Promise.resolve(true)); + + // Given we save 1 connection with password and multiple other connections without + let connectionStore = new ConnectionStore(context.object, workspaceConfigurationServiceMock.object, + credentialStore.object, capabilitiesService, connectionConfig.object); + connectionStore.clearActiveConnections(); + connectionStore.clearRecentlyUsed(); + + let integratedCred = Object.assign({}, defaultNamedProfile, { + serverName: defaultNamedProfile.serverName + 'Integrated', + authenticationType: 'Integrated', + userName: '', + password: '' + }); + let noPwdCred = Object.assign({}, defaultNamedProfile, { + serverName: defaultNamedProfile.serverName + 'NoPwd', + password: '' + }); + let connectionProfile = new ConnectionProfile(capabilitiesService, defaultNamedProfile); + + let expectedCredCount = 0; + expectedCredCount++; + // Connection with stored password + await connectionStore.addActiveConnection(connectionProfile, true); + let recentConnections = connectionStore.getActiveConnections(); + + // Then verify that saveCredential was called and correctly stored the password + credentialStore.verify(x => x.saveCredential(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once()); + assert.strictEqual(capturedCreds.password, defaultNamedProfile.password); + let credId: string = capturedCreds.credentialId; + assert.ok(credId.includes(ConnectionStore.CRED_PROFILE_USER), 'Expect credential to be marked as an Profile cred'); + assert.ok(!recentConnections[0].password); + + // Integrated auth + expectedCredCount++; + let integratedCredConnectionProfile = new ConnectionProfile(capabilitiesService, integratedCred); + await connectionStore.addActiveConnection(integratedCredConnectionProfile, true); + + recentConnections = connectionStore.getActiveConnections(); + // We shouldn't see an increase in the calls to saveCredential, but the MRU should be increased + credentialStore.verify(x => x.saveCredential(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once()); + assert.equal(recentConnections.length, expectedCredCount, `expect ${expectedCredCount} unique credentials to have been added`); + + // Connection with blank (no) password + expectedCredCount++; + let noPwdCredConnectionProfile = new ConnectionProfile(capabilitiesService, noPwdCred); + await connectionStore.addActiveConnection(noPwdCredConnectionProfile, true); + + recentConnections = connectionStore.getActiveConnections(); + // We shouldn't see an increase in the calls to saveCredential, but the MRU should be increased + credentialStore.verify(x => x.saveCredential(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once()); + assert.equal(recentConnections.length, expectedCredCount, `expect ${expectedCredCount} unique credentials to have been added`); + + }); + + test('can clear connections list', (done) => { + connectionConfig.setup(x => x.getConnections(TypeMoq.It.isAny())).returns(() => []); + + let connectionStore = new ConnectionStore(context.object, workspaceConfigurationServiceMock.object, + credentialStore.object, capabilitiesService, connectionConfig.object); + + // When we clear the connections list and get the list of available connection items + connectionStore.clearActiveConnections(); + connectionStore.clearRecentlyUsed(); + // Expect no connection items + let result = connectionStore.getActiveConnections(); + let expectedCount = 0; // 1 for create connection profile + assert.equal(result.length, expectedCount); + result = connectionStore.getRecentlyUsedConnections(); + assert.equal(result.length, expectedCount); + // Then test is complete + done(); + }); + + test('isPasswordRequired should return true for MSSQL SqlLogin', () => { + + let connectionStore = new ConnectionStore(context.object, workspaceConfigurationServiceMock.object, + credentialStore.object, capabilitiesService, connectionConfig.object); + + let expected: boolean = true; + let actual = connectionStore.isPasswordRequired(defaultNamedProfile); + + assert.equal(expected, actual); + }); + + test('isPasswordRequired should return true for MSSQL SqlLogin for connection profile object', () => { + let connectionStore = new ConnectionStore(context.object, workspaceConfigurationServiceMock.object, + credentialStore.object, capabilitiesService, connectionConfig.object); + let connectionProfile = new ConnectionProfile(capabilitiesService, defaultNamedProfile); + let expected: boolean = true; + let actual = connectionStore.isPasswordRequired(connectionProfile); + + assert.equal(expected, actual); + }); + + test('isPasswordRequired should return false if the password is not required in capabilities', () => { + let providerName: string = 'providername'; + let connectionProvider = msSQLCapabilities.connectionOptions.map(o => { + if (o.name === 'password') { + o.isRequired = false; + } + return o; + }); + let providerCapabilities = { + providerId: providerName, + displayName: providerName, + connectionOptions: connectionProvider + }; + + capabilitiesService.capabilities[providerName] = { connection: providerCapabilities }; + + let connectionStore = new ConnectionStore(context.object, workspaceConfigurationServiceMock.object, + credentialStore.object, capabilitiesService, connectionConfig.object); + let connectionProfile: IConnectionProfile = Object.assign({}, defaultNamedProfile, { providerName: providerName }); + let expected: boolean = false; + let actual = connectionStore.isPasswordRequired(connectionProfile); + + assert.equal(expected, actual); + }); + + test('saveProfile should save the password after the profile is saved', done => { + let password: string = 'asdf!@#$'; + let groupId: string = 'group id'; + let connectionProfile: IConnectionProfile = Object.assign({}, defaultNamedProfile, { password: password }); + let savedConnection: IConnectionProfile = Object.assign({}, connectionProfile, { groupId: groupId, password: '' }); + connectionConfig.setup(x => x.addConnection(TypeMoq.It.isAny())).returns(() => Promise.resolve(savedConnection)); + credentialStore.setup(x => x.saveCredential(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(true)); + + let connectionStore = new ConnectionStore(context.object, workspaceConfigurationServiceMock.object, + credentialStore.object, capabilitiesService, connectionConfig.object); + + connectionStore.saveProfile(connectionProfile).then(profile => { + // add connection should be called with a profile without password + connectionConfig.verify(x => x.addConnection(TypeMoq.It.is(c => c.password === '')), TypeMoq.Times.once()); + credentialStore.verify(x => x.saveCredential(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once()); + assert.equal(profile.password, password, 'The returned profile should still keep the password'); + assert.equal(profile.groupId, groupId, 'Group id should be set in the profile'); + done(); + }).catch(err => { + assert.fail(err); + done(err); + }); + }); + + test('addConnectionToMemento should not add duplicate items', () => { + let connectionStore = new ConnectionStore(context.object, workspaceConfigurationServiceMock.object, + credentialStore.object, capabilitiesService, connectionConfig.object); + let mementoKey = 'RECENT_CONNECTIONS2'; + connectionStore.clearFromMemento(mementoKey); + let connectionProfile: IConnectionProfile = Object.assign({}, defaultNamedProfile); + connectionStore.addConnectionToMemento(connectionProfile, mementoKey); + + connectionProfile = Object.assign({}, defaultNamedProfile, { authenticationType: 'Integrated', userName: '' }); + connectionStore.addConnectionToMemento(connectionProfile, mementoKey); + + let currentList = connectionStore.getConnectionsFromMemento(mementoKey); + assert.equal(currentList.length, 2, 'Adding same connection with different auth'); + + connectionProfile = Object.assign({}, defaultNamedProfile, { groupFullName: 'new group' }); + connectionStore.addConnectionToMemento(connectionProfile, mementoKey); + + currentList = connectionStore.getConnectionsFromMemento(mementoKey); + assert.equal(currentList.length, 3, 'Adding same connection with different group name'); + + connectionProfile = Object.assign({}, defaultNamedProfile, + { groupFullName: defaultNamedProfile.groupFullName.toUpperCase() }); + connectionStore.addConnectionToMemento(connectionProfile, mementoKey); + + currentList = connectionStore.getConnectionsFromMemento(mementoKey); + assert.equal(currentList.length, 3, 'Adding same connection with same group name but uppercase'); + + connectionProfile = Object.assign({}, defaultNamedProfile, + { groupFullName: '' }); + connectionStore.addConnectionToMemento(connectionProfile, mementoKey); + + currentList = connectionStore.getConnectionsFromMemento(mementoKey); + assert.equal(currentList.length, 3, 'Adding same connection with group empty string'); + + connectionProfile = Object.assign({}, defaultNamedProfile, + { groupFullName: '/' }); + connectionStore.addConnectionToMemento(connectionProfile, mementoKey); + + currentList = connectionStore.getConnectionsFromMemento(mementoKey); + assert.equal(currentList.length, 3, 'Adding same connection with group /'); + }); + + test('getGroupFromId returns undefined when there is no group with the given ID', () => { + let connectionStore = new ConnectionStore(context.object, workspaceConfigurationServiceMock.object, + credentialStore.object, capabilitiesService, connectionConfig.object); + let group = connectionStore.getGroupFromId('invalidId'); + assert.equal(group, undefined, 'Returned group was not undefined when there was no group with the given ID'); + }); + + test('getGroupFromId returns the group that has the given ID', () => { + // Set up the server groups with an additional group that contains a child group + let groups: IConnectionProfileGroup[] = connectionConfig.object.getAllGroups(); + let parentGroupId = 'parentGroup'; + let childGroupId = 'childGroup'; + let parentGroup = new ConnectionProfileGroup(parentGroupId, undefined, parentGroupId, '', ''); + let childGroup = new ConnectionProfileGroup(childGroupId, parentGroup, childGroupId, '', ''); + groups.push(parentGroup, childGroup); + let newConnectionConfig = TypeMoq.Mock.ofType(ConnectionConfig); + newConnectionConfig.setup(x => x.getAllGroups()).returns(() => groups); + let connectionStore = new ConnectionStore(context.object, workspaceConfigurationServiceMock.object, + credentialStore.object, capabilitiesService, newConnectionConfig.object); + + // If I look up the parent group using its ID, then I get back the correct group + let actualGroup = connectionStore.getGroupFromId(parentGroupId); + assert.equal(actualGroup.id, parentGroupId, 'Did not get the parent group when looking it up with its ID'); + + // If I look up the child group using its ID, then I get back the correct group + actualGroup = connectionStore.getGroupFromId(childGroupId); + assert.equal(actualGroup.id, childGroupId, 'Did not get the child group when looking it up with its ID'); + }); + + test('getProfileWithoutPassword can return the profile without credentials in the password property or options dictionary', () => { + let connectionStore = new ConnectionStore(context.object, workspaceConfigurationServiceMock.object, + credentialStore.object, capabilitiesService, connectionConfig.object); + let profile = Object.assign({}, defaultNamedProfile); + profile.options['password'] = profile.password; + profile.id = 'testId'; + let expectedProfile = Object.assign({}, profile); + expectedProfile.password = ''; + expectedProfile.options['password'] = ''; + expectedProfile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, expectedProfile).toIConnectionProfile(); + let profileWithoutCredentials = connectionStore.getProfileWithoutPassword(profile); + assert.deepEqual(profileWithoutCredentials.toIConnectionProfile(), expectedProfile); + }); +}); diff --git a/src/sqltest/parts/connection/connectionTreeActions.test.ts b/src/sqltest/parts/connection/connectionTreeActions.test.ts index 2c067fffbb..19b5ba5acd 100644 --- a/src/sqltest/parts/connection/connectionTreeActions.test.ts +++ b/src/sqltest/parts/connection/connectionTreeActions.test.ts @@ -18,6 +18,7 @@ import { TestConnectionManagementService } from 'sqltest/stubs/connectionManagem import { ErrorMessageServiceStub } from 'sqltest/stubs/errorMessageServiceStub'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServerTreeView } from 'sql/parts/objectExplorer/viewlet/serverTreeView'; +import * as Constants from 'sql/platform/connection/common/constants'; import * as LocalizedConstants from 'sql/parts/connection/common/localizedConstants'; import { ObjectExplorerService, ObjectExplorerNodeEventArgs } from 'sql/workbench/services/objectExplorer/common/objectExplorerService'; import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode'; @@ -31,7 +32,6 @@ import { ObjectExplorerActionsContext, ManageConnectionAction } from 'sql/parts/ import { IConnectionResult, IConnectionParams } from 'sql/platform/connection/common/connectionManagement'; import { TreeSelectionHandler } from 'sql/parts/objectExplorer/viewlet/treeSelectionHandler'; import { CapabilitiesTestService } from 'sqltest/stubs/capabilitiesTestService'; -import { UNSAVED_GROUP_ID } from 'sql/platform/connection/common/constants'; suite('SQL Connection Tree Action tests', () => { let errorMessageService: TypeMoq.Mock; @@ -328,7 +328,7 @@ suite('SQL Connection Tree Action tests', () => { saveProfile: true, id: 'testId' }); - connection.parent = new ConnectionProfileGroup(LocalizedConstants.unsavedGroupLabel, undefined, UNSAVED_GROUP_ID, undefined, undefined); + connection.parent = new ConnectionProfileGroup(LocalizedConstants.unsavedGroupLabel, undefined, Constants.unsavedGroupId, undefined, undefined); let connectionAction: DeleteConnectionAction = new DeleteConnectionAction(DeleteConnectionAction.ID, DeleteConnectionAction.DELETE_CONNECTION_LABEL, connection, @@ -513,4 +513,4 @@ suite('SQL Connection Tree Action tests', () => { }).then(() => done(), (err) => done(err)); }); -}); +}); \ No newline at end of file diff --git a/src/sqltest/parts/query/editor/queryActions.test.ts b/src/sqltest/parts/query/editor/queryActions.test.ts index 11e95398ee..9a02e435f0 100644 --- a/src/sqltest/parts/query/editor/queryActions.test.ts +++ b/src/sqltest/parts/query/editor/queryActions.test.ts @@ -124,7 +124,7 @@ suite('SQL QueryAction Tests', () => { .returns(() => Promise.resolve(none)); // ... Mock "isConnected" in ConnectionManagementService - let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, connectionDialogService.object); + let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, {}, new TestStorageService(), connectionDialogService.object); connectionManagementService.callBase = true; connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected); @@ -182,7 +182,7 @@ suite('SQL QueryAction Tests', () => { queryEditor.setup(x => x.isSelectionEmpty()).returns(() => isSelectionEmpty); // ... Mock "isConnected" in ConnectionManagementService - let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}); + let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, {}, new TestStorageService()); connectionManagementService.callBase = true; connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => true); @@ -249,7 +249,7 @@ suite('SQL QueryAction Tests', () => { }); // ... Mock "isConnected" in ConnectionManagementService - let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, connectionDialogService.object); + let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, {}, new TestStorageService(), connectionDialogService.object); connectionManagementService.callBase = true; connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected); @@ -315,7 +315,7 @@ suite('SQL QueryAction Tests', () => { let calledCancelQuery: boolean = false; // ... Mock "isConnected" in ConnectionManagementService - let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}); + let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, {}, new TestStorageService()); connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected); // ... Mock QueryModelService @@ -348,7 +348,7 @@ suite('SQL QueryAction Tests', () => { let countCalledDisconnectEditor: number = 0; // ... Mock "isConnected" and "disconnectEditor" in ConnectionManagementService - let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}); + let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, {}, new TestStorageService()); connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected); connectionManagementService.setup(x => x.disconnectEditor(TypeMoq.It.isAny())).callback(() => { countCalledDisconnectEditor++; @@ -387,7 +387,7 @@ suite('SQL QueryAction Tests', () => { .returns(() => Promise.resolve(none)); // ... Mock "isConnected" in ConnectionManagementService - let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, connectionDialogService.object); + let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, {}, new TestStorageService(), connectionDialogService.object); connectionManagementService.callBase = true; connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected); @@ -433,7 +433,7 @@ suite('SQL QueryAction Tests', () => { .returns(() => Promise.resolve(none)); // ... Mock "isConnected" in ConnectionManagementService - let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, connectionDialogService.object); + let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, {}, new TestStorageService(), connectionDialogService.object); connectionManagementService.callBase = true; connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected); @@ -468,7 +468,7 @@ suite('SQL QueryAction Tests', () => { let databaseName: string = undefined; // ... Mock "isConnected" in ConnectionManagementService - let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}); + let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, {}, new TestStorageService()); connectionManagementService.callBase = true; connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected); connectionManagementService.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => { @@ -505,7 +505,7 @@ suite('SQL QueryAction Tests', () => { // ... Create mock connection management service let databaseName = 'foobar'; - let cms = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}); + let cms = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, {}, new TestStorageService()); cms.callBase = true; cms.setup(x => x.onConnectionChanged).returns(() => dbChangedEmitter.event); cms.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => { databaseName: databaseName }); diff --git a/src/sqltest/stubs/credentialsTestStubs.ts b/src/sqltest/stubs/credentialsTestStubs.ts index 48dedb152a..fe375ff890 100644 --- a/src/sqltest/stubs/credentialsTestStubs.ts +++ b/src/sqltest/stubs/credentialsTestStubs.ts @@ -36,15 +36,15 @@ export class CredentialsTestProvider implements azdata.CredentialProvider { export class CredentialsTestService implements ICredentialsService { _serviceBrand: any; - saveCredential(credentialId: string, password: string): Promise { + saveCredential(credentialId: string, password: string): Thenable { return undefined; } - readCredential(credentialId: string): Promise { + readCredential(credentialId: string): Thenable { return undefined; } - deleteCredential(credentialId: string): Promise { + deleteCredential(credentialId: string): Thenable { return undefined; } @@ -52,4 +52,4 @@ export class CredentialsTestService implements ICredentialsService { return undefined; } -} +} \ No newline at end of file