diff --git a/src/sql/base/common/map.ts b/src/sql/base/common/map.ts index d13a792806..a3f06ef5be 100644 --- a/src/sql/base/common/map.ts +++ b/src/sql/base/common/map.ts @@ -15,3 +15,48 @@ 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 4663e0c26b..15ca902861 100644 --- a/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts +++ b/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts @@ -15,7 +15,6 @@ 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'; @@ -23,6 +22,7 @@ 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 === Constants.unsavedGroupId) { + if (element instanceof ConnectionProfileGroup && element.id === UNSAVED_GROUP_ID) { this.enabled = false; } if (element instanceof ConnectionProfile) { let parent: ConnectionProfileGroup = element.parent; - if (parent && parent.id === Constants.unsavedGroupId) { + if (parent && parent.id === UNSAVED_GROUP_ID) { this.enabled = false; } } diff --git a/src/sql/parts/objectExplorer/viewlet/dragAndDropController.ts b/src/sql/parts/objectExplorer/viewlet/dragAndDropController.ts index c8fe9e74ae..ab78115ceb 100644 --- a/src/sql/parts/objectExplorer/viewlet/dragAndDropController.ts +++ b/src/sql/parts/objectExplorer/viewlet/dragAndDropController.ts @@ -6,12 +6,11 @@ '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'; /** @@ -19,8 +18,8 @@ import { IDragAndDropData } from 'vs/base/browser/dnd'; */ export class ServerTreeDragAndDrop implements IDragAndDrop { - constructor(@IConnectionManagementService private _connectionManagementService: IConnectionManagementService, - @IInstantiationService private _instantiationService: IInstantiationService + constructor( + @IConnectionManagementService private _connectionManagementService: IConnectionManagementService ) { } @@ -143,7 +142,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 === Constants.unsavedGroupId); + let isUnsavedDrag = source && (source instanceof ConnectionProfileGroup) && (source.id === UNSAVED_GROUP_ID); return (!isDropToSameLevel && !isDropToItself && !isUnsavedDrag); } } @@ -153,11 +152,6 @@ 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 83022d304a..f3fa23395b 100644 --- a/src/sql/platform/connection/common/connectionConfig.ts +++ b/src/sql/platform/connection/common/connectionConfig.ts @@ -7,14 +7,16 @@ 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 * as Constants from 'sql/platform/connection/common/constants'; -import { IConnectionConfig } from 'sql/platform/connection/common/iconnectionConfig'; +import { UNSAVED_GROUP_ID } from 'sql/platform/connection/common/constants'; 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; @@ -23,7 +25,7 @@ export interface ISaveGroupResult { /** * Implements connection profile file storage. */ -export class ConnectionConfig implements IConnectionConfig { +export class ConnectionConfig { public constructor( private configurationService: IConfigurationService, @@ -36,7 +38,7 @@ export class ConnectionConfig implements IConnectionConfig { public getAllGroups(): IConnectionProfileGroup[] { let allGroups: IConnectionProfileGroup[] = []; - let { user, workspace } = this.configurationService.inspect(Constants.connectionGroupsArrayName); + let { user, workspace } = this.configurationService.inspect(GROUPS_CONFIG_KEY); if (user) { if (workspace) { @@ -45,13 +47,12 @@ export class ConnectionConfig implements IConnectionConfig { } allGroups = allGroups.concat(user); } - allGroups = allGroups.map(g => { + return allGroups.map(g => { if (g.parentId === '' || !g.parentId) { g.parentId = undefined; } return g; }); - return allGroups; } /** @@ -60,7 +61,7 @@ export class ConnectionConfig implements IConnectionConfig { public addConnection(profile: IConnectionProfile): Promise { if (profile.saveProfile) { return this.addGroupFromProfile(profile).then(groupId => { - let profiles = this.configurationService.inspect(Constants.connectionsArrayName).user; + let profiles = this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).user; if (!profiles) { profiles = []; } @@ -69,7 +70,7 @@ export class ConnectionConfig implements IConnectionConfig { let newProfile = ConnectionProfile.convertToProfileStore(this._capabilitiesService, connectionProfile); // Remove the profile if already set - var sameProfileInList = profiles.find(value => { + let sameProfileInList = profiles.find(value => { let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService); return providerConnectionProfile.matches(connectionProfile); }); @@ -82,7 +83,7 @@ export class ConnectionConfig implements IConnectionConfig { profiles.push(newProfile); } - return this.configurationService.updateValue(Constants.connectionsArrayName, profiles, ConfigurationTarget.USER).then(() => connectionProfile); + return this.configurationService.updateValue(CONNECTIONS_CONFIG_KEY, profiles, ConfigurationTarget.USER).then(() => connectionProfile); }); } else { return Promise.resolve(profile); @@ -106,11 +107,11 @@ export class ConnectionConfig implements IConnectionConfig { if (profile.groupId && profile.groupId !== Utils.defaultGroupId) { return Promise.resolve(profile.groupId); } else { - let groups = this.configurationService.inspect(Constants.connectionGroupsArrayName).user; + let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).user; let result = this.saveGroup(groups, profile.groupFullName, undefined, undefined); groups = result.groups; - return this.configurationService.updateValue(Constants.connectionGroupsArrayName, groups, ConfigurationTarget.USER).then(() => result.newGroupId); + return this.configurationService.updateValue(GROUPS_CONFIG_KEY, groups, ConfigurationTarget.USER).then(() => result.newGroupId); } } @@ -122,7 +123,7 @@ export class ConnectionConfig implements IConnectionConfig { if (profileGroup.id) { return Promise.resolve(profileGroup.id); } else { - let groups = this.configurationService.inspect(Constants.connectionGroupsArrayName).user; + let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).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."); @@ -131,13 +132,13 @@ export class ConnectionConfig implements IConnectionConfig { let result = this.saveGroup(groups, profileGroup.name, profileGroup.color, profileGroup.description); groups = result.groups; - return this.configurationService.updateValue(Constants.connectionGroupsArrayName, groups, ConfigurationTarget.USER).then(() => result.newGroupId); + return this.configurationService.updateValue(GROUPS_CONFIG_KEY, groups, ConfigurationTarget.USER).then(() => result.newGroupId); } } } private getConnectionProfilesForTarget(configTarget: ConfigurationTarget): IConnectionProfileStore[] { - let configs = this.configurationService.inspect(Constants.connectionsArrayName); + let configs = this.configurationService.inspect(CONNECTIONS_CONFIG_KEY); let profiles: IConnectionProfileStore[]; if (configs) { if (configTarget === ConfigurationTarget.USER) { @@ -147,7 +148,7 @@ export class ConnectionConfig implements IConnectionConfig { } if (profiles) { if (this.fixConnectionIds(profiles)) { - this.configurationService.updateValue(Constants.connectionsArrayName, profiles, configTarget); + this.configurationService.updateValue(CONNECTIONS_CONFIG_KEY, profiles, configTarget); } } else { profiles = []; @@ -164,8 +165,7 @@ export class ConnectionConfig implements IConnectionConfig { private fixConnectionIds(profiles: IConnectionProfileStore[]): boolean { let idsCache: { [label: string]: boolean } = {}; let changed: boolean = false; - for (var index = 0; index < profiles.length; index++) { - var profile = profiles[index]; + for (let profile of profiles) { if (!profile.id) { profile.id = generateUuid(); changed = true; @@ -215,7 +215,7 @@ export class ConnectionConfig implements IConnectionConfig { */ public deleteConnection(profile: ConnectionProfile): Promise { // Get all connections in the settings - let profiles = this.configurationService.inspect(Constants.connectionsArrayName).user; + let profiles = this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).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 implements IConnectionConfig { }); // Write connections back to settings - return this.configurationService.updateValue(Constants.connectionsArrayName, profiles, ConfigurationTarget.USER); + return this.configurationService.updateValue(CONNECTIONS_CONFIG_KEY, profiles, ConfigurationTarget.USER); } /** @@ -236,7 +236,7 @@ export class ConnectionConfig implements IConnectionConfig { // Add selected group to subgroups list subgroups.push(group); // Get all connections in the settings - let profiles = this.configurationService.inspect(Constants.connectionsArrayName).user; + let profiles = this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).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 implements IConnectionConfig { }); // Get all groups in the settings - let groups = this.configurationService.inspect(Constants.connectionGroupsArrayName).user; + let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).user; // Remove subgroups in the settings groups = groups.filter((grp) => { return !subgroups.some((item) => item.id === grp.id); }); return Promise.all([ - this.configurationService.updateValue(Constants.connectionsArrayName, profiles, ConfigurationTarget.USER), - this.configurationService.updateValue(Constants.connectionGroupsArrayName, groups, ConfigurationTarget.USER) + this.configurationService.updateValue(CONNECTIONS_CONFIG_KEY, profiles, ConfigurationTarget.USER), + this.configurationService.updateValue(GROUPS_CONFIG_KEY, groups, ConfigurationTarget.USER) ]).then(() => Promise.resolve()); } @@ -259,14 +259,14 @@ export class ConnectionConfig implements IConnectionConfig { * Moves the source group under the target group. */ public changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise { - let groups = this.configurationService.inspect(Constants.connectionGroupsArrayName).user; + let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).user; groups = groups.map(g => { if (g.id === source.id) { g.parentId = target.id; } return g; }); - return this.configurationService.updateValue(Constants.connectionGroupsArrayName, groups, ConfigurationTarget.USER); + return this.configurationService.updateValue(GROUPS_CONFIG_KEY, groups, ConfigurationTarget.USER); } /** @@ -283,10 +283,10 @@ export class ConnectionConfig implements IConnectionConfig { * 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(Constants.connectionsArrayName).user : - this.configurationService.inspect(Constants.connectionsArrayName).workspace; + let profiles = target === ConfigurationTarget.USER ? this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).user : + this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).workspace; if (profiles) { - if (profile.parent && profile.parent.id === Constants.unsavedGroupId) { + if (profile.parent && profile.parent.id === UNSAVED_GROUP_ID) { profile.groupId = newGroupID; profiles.push(ConnectionProfile.convertToProfileStore(this._capabilitiesService, profile)); } else { @@ -298,7 +298,7 @@ export class ConnectionConfig implements IConnectionConfig { }); } - return this.configurationService.updateValue(Constants.connectionsArrayName, profiles, target); + return this.configurationService.updateValue(CONNECTIONS_CONFIG_KEY, profiles, target); } else { return Promise.resolve(); } @@ -320,14 +320,12 @@ export class ConnectionConfig implements IConnectionConfig { } public saveGroup(groups: IConnectionProfileGroup[], groupFullName: string, color: string, description: string): ISaveGroupResult { - let result: ISaveGroupResult; let groupNames = ConnectionProfileGroup.getGroupFullNameParts(groupFullName); - result = this.saveGroupInTree(groups, undefined, groupNames, color, description, 0); - return result; + return this.saveGroupInTree(groups, undefined, groupNames, color, description, 0); } public editGroup(source: ConnectionProfileGroup): Promise { - let groups = this.configurationService.inspect(Constants.connectionGroupsArrayName).user; + let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).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."); @@ -342,7 +340,7 @@ export class ConnectionConfig implements IConnectionConfig { } return g; }); - return this.configurationService.updateValue(Constants.connectionGroupsArrayName, groups, ConfigurationTarget.USER); + return this.configurationService.updateValue(GROUPS_CONFIG_KEY, 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 07f3d11ec6..2ac7b76023 100644 --- a/src/sql/platform/connection/common/connectionManagementService.ts +++ b/src/sql/platform/connection/common/connectionManagementService.ts @@ -17,7 +17,6 @@ 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'; @@ -44,9 +43,7 @@ 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'; @@ -77,16 +74,13 @@ 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, @@ -97,13 +91,8 @@ 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 = new ConnectionStore(this._connectionMemento, - this._configurationService, this._credentialsService, this._capabilitiesService); + this._connectionStore = _instantiationService.createInstance(ConnectionStore); } // Register Statusbar item @@ -135,7 +124,6 @@ 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 { @@ -624,11 +612,11 @@ export class ConnectionManagementService extends Disposable implements IConnecti } public clearRecentConnection(connectionProfile: IConnectionProfile): void { - this._connectionStore.removeConnectionToMemento(connectionProfile, Constants.recentConnections); + this._connectionStore.removeRecentConnection(connectionProfile); } public getActiveConnections(providers?: string[]): ConnectionProfile[] { - return this._connectionStatusManager.getActiveConnectionProfiles(); + return this._connectionStatusManager.getActiveConnectionProfiles(providers); } public getConnectionUriFromId(connectionId: string): string { @@ -861,8 +849,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) { - this._connectionStore.addActiveConnection(newConnection, addToMru) + if (newConnection && addToMru) { + this._connectionStore.addRecentConnection(newConnection) .then(() => { connectionManagementInfo.connectHandler(true); }, err => { @@ -930,11 +918,6 @@ 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); @@ -1096,7 +1079,6 @@ 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 f1e627daec..9b8bf0d6f8 100644 --- a/src/sql/platform/connection/common/connectionStore.ts +++ b/src/sql/platform/connection/common/connectionStore.ts @@ -5,21 +5,26 @@ 'use strict'; -import * as Constants from 'sql/platform/connection/common/constants'; -import * as ConnInfo from 'sql/platform/connection/common/connectionInfo'; +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 { 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 * @@ -27,39 +32,15 @@ const MAX_CONNECTIONS_DEFAULT = 25; * @class ConnectionStore */ export class ConnectionStore { - private _memento: any; - private _groupIdToFullNameMap: { [groupId: string]: string }; - private _groupFullNameToIdMap: { [groupId: string]: string }; + private groupIdMap = new ReverseLookUpMap(); + private connectionConfig = new ConnectionConfig(this.configurationService, this.capabilitiesService); constructor( - private _context: Memento, - private _configurationService: IConfigurationService, - private _credentialService: ICredentialsService, - private _capabilitiesService: ICapabilitiesService, - private _connectionConfig?: IConnectionConfig + @IStateService private stateService: IStateService, + @IConfigurationService private configurationService: IConfigurationService, + @ICredentialsService private credentialService: ICredentialsService, + @ICapabilitiesService private capabilitiesService: ICapabilitiesService ) { - 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); } /** @@ -70,26 +51,17 @@ export class ConnectionStore { * @param {string} itemType type of the item (MRU or Profile) - optional * @returns {string} formatted string with server, DB and username */ - public formatCredentialId(connectionProfile: IConnectionProfile, itemType?: string): string { + private formatCredentialId(connectionProfile: IConnectionProfile, itemType?: string): string { let connectionProfileInstance: ConnectionProfile = ConnectionProfile.fromIConnectionProfile( - this._capabilitiesService, connectionProfile); - if (!connectionProfileInstance.getConnectionInfoId()) { - throw new Error('Missing Id, which is required'); - } - let cred: string[] = [ConnectionStore.CRED_PREFIX]; + this.capabilitiesService, connectionProfile); + let cred: string[] = [CRED_PREFIX]; if (!itemType) { - itemType = ConnectionStore.CRED_PROFILE_USER; + itemType = CRED_PROFILE_USER; } - 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)); - } + cred.push(CRED_ITEMTYPE_PREFIX.concat(itemType)); + cred.push(CRED_ID_PREFIX.concat(connectionProfileInstance.getConnectionInfoId())); + return cred.join(CRED_SEPARATOR); } /** @@ -98,7 +70,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; @@ -106,28 +78,20 @@ export class ConnectionStore { } public addSavedPassword(credentialsItem: IConnectionProfile): Promise<{ profile: IConnectionProfile, savedCred: boolean }> { - 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 }); - } - }); + 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 }); + } } /** @@ -139,34 +103,20 @@ export class ConnectionStore { * @returns {Promise} a Promise that returns the original profile, for help in chaining calls */ public saveProfile(profile: IConnectionProfile, forceWritePlaintextPassword?: boolean): Promise { - 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); - }); - }); + // 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; + }); } /** @@ -176,29 +126,15 @@ export class ConnectionStore { * @returns {Promise} a Promise that returns the id of connection group */ public saveProfileGroup(profile: IConnectionProfileGroup): Promise { - const self = this; - return new Promise((resolve, reject) => { - self._connectionConfig.addGroup(profile).then(groupId => { - resolve(groupId); - }).catch(error => { - reject(error); - }); - }); + return this.connectionConfig.addGroup(profile); } private saveProfileToConfig(profile: IConnectionProfile): Promise { - 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); - } - }); + if (profile.saveProfile) { + return this.connectionConfig.addConnection(profile); + } else { + return Promise.resolve(profile); + } } /** @@ -208,12 +144,8 @@ export class ConnectionStore { * @returns {azdata.ConnectionInfo} the array of connections, empty if none are found */ public getRecentlyUsedConnections(providers?: string[]): ConnectionProfile[] { - let configValues: IConnectionProfile[] = this._memento[Constants.recentConnections]; - if (!configValues) { - configValues = []; - } + let configValues = this.stateService.getItem(RECENT_CONNECTIONS_STATE_KEY, []).filter(c => !!c); - configValues = configValues.filter(c => !!(c)); if (providers && providers.length > 0) { configValues = configValues.filter(c => providers.includes(c.providerName)); } @@ -223,7 +155,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); @@ -241,24 +173,9 @@ 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; @@ -276,71 +193,35 @@ 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 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); - } - } - - /** - * Adds the specified connection to the MRU list - * @param conn The connection to add - */ - private async addConnectionToMru(conn: IConnectionProfile): Promise { + public addRecentConnection(conn: IConnectionProfile): Promise { let maxConnections = this.getMaxRecentConnectionsCount(); - if (ConnectionProfile.isConnectionToDefaultDb(conn)) { - conn.databaseName = ''; - } - await this.addConnectionToMemento(conn, Constants.recentConnections, maxConnections); + return this.addConnectionToState(conn, RECENT_CONNECTIONS_STATE_KEY, maxConnections, conn.savePassword); } - 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); - } + 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); } - 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 = []; } + this.stateService.setItem(key, configToSave); + return savePassword ? this.doSavePassword(conn).then() : Promise.resolve(); + } - return this.convertConfigValuesToConnectionProfiles(configValues); + 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); + } + + private getConnectionsFromState(mementoKey: string): ConnectionProfile[] { + return this.convertConfigValuesToConnectionProfiles(this.stateService.getItem(mementoKey, [])); } private addToConnectionList(conn: IConnectionProfile, list: ConnectionProfile[]): IConnectionProfile[] { @@ -358,11 +239,7 @@ export class ConnectionStore { list.unshift(savedProfile); - let newList = list.map(c => { - let connectionProfile = c ? c.toIConnectionProfile() : undefined; - return connectionProfile; - }); - return newList.filter(n => n !== undefined); + return list.filter(n => n !== undefined).map(c => c.toIConnectionProfile()); } private removeFromConnectionList(conn: IConnectionProfile, list: ConnectionProfile[]): IConnectionProfile[] { @@ -378,37 +255,18 @@ export class ConnectionStore { return !equal; }); - let newList = list.map(c => { - let connectionProfile = c ? c.toIConnectionProfile() : undefined; - return connectionProfile; - }); - return newList.filter(n => n !== undefined); + return list.filter(n => n !== undefined).map(c => c.toIConnectionProfile()); } /** * Clear all recently used connections from the MRU list. */ public clearRecentlyUsed(): void { - this._memento[Constants.recentConnections] = []; + this.stateService.setItem(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); + public removeRecentConnection(conn: IConnectionProfile): void { + this.removeConnectionFromState(conn, RECENT_CONNECTIONS_STATE_KEY); } private saveProfilePasswordIfNeeded(profile: IConnectionProfile): Promise { @@ -419,32 +277,23 @@ export class ConnectionStore { } private doSavePassword(conn: IConnectionProfile): Promise { - 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); - } - }); + if (conn.password) { + let credentialId = this.formatCredentialId(conn); + return this.credentialService.saveCredential(credentialId, conn.password); + } else { + return Promise.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; @@ -479,74 +328,53 @@ 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 { - 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; + return this.configurationService.getValue('sql.maxRecentConnections') || MAX_CONNECTIONS_DEFAULT; } - 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 editGroup(group: ConnectionProfileGroup): Promise { + return this.connectionConfig.editGroup(group).then(); } 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 new Promise((resolve, reject) => { - this._connectionConfig.changeGroupIdForConnection(source, targetGroupId).then(() => { - resolve(); - }, (error => { - reject(error); - })); - }); + return this.connectionConfig.changeGroupIdForConnection(source, targetGroupId).then(); } private addGroupFullNameToMap(groupId: string, groupFullName: string): void { if (groupId) { - this._groupIdToFullNameMap[groupId] = groupFullName; + this.groupIdMap.set(groupId, groupFullName); } if (groupFullName !== undefined) { - this._groupFullNameToIdMap[groupFullName.toUpperCase()] = groupId; + this.groupIdMap.set(groupFullName.toUpperCase(), groupId); } } private getGroupFullName(groupId: string): string { - if (groupId in this._groupIdToFullNameMap) { - return this._groupIdToFullNameMap[groupId]; - } else { + if (!this.groupIdMap.has(groupId)) { // Load the cache this.getConnectionProfileGroups(true); } - return this._groupIdToFullNameMap[groupId]; + return this.groupIdMap.get(groupId); } private getGroupId(groupFullName: string): string { @@ -554,14 +382,10 @@ export class ConnectionStore { groupFullName = ''; } let key = groupFullName.toUpperCase(); - let result: string = ''; - if (key in this._groupFullNameToIdMap) { - result = this._groupFullNameToIdMap[key]; - } else { + if (!this.groupIdMap.reverseHas(key)) { // Load the cache this.getConnectionProfileGroups(true); - result = this._groupFullNameToIdMap[key]; } - return result; + return this.groupIdMap.reverseGet(key); } } diff --git a/src/sql/platform/connection/common/constants.ts b/src/sql/platform/connection/common/constants.ts index f09f28b867..ee26e1a722 100644 --- a/src/sql/platform/connection/common/constants.ts +++ b/src/sql/platform/connection/common/constants.ts @@ -7,19 +7,9 @@ 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'; @@ -34,3 +24,5 @@ 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 deleted file mode 100644 index 7e14127817..0000000000 --- a/src/sql/platform/connection/common/iconnectionConfig.ts +++ /dev/null @@ -1,29 +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 { 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 3471caa7be..73c65e74da 100644 --- a/src/sql/platform/connection/test/common/connectionConfig.test.ts +++ b/src/sql/platform/connection/test/common/connectionConfig.test.ts @@ -5,23 +5,20 @@ 'use strict'; -import * as TypeMoq from 'typemoq'; -import { ConnectionConfig, ISaveGroupResult } from 'sql/platform/connection/common/connectionConfig'; -import { IConnectionProfile, IConnectionProfileStore } from 'sql/platform/connection/common/interfaces'; -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 { ICapabilitiesService, ProviderFeatures } from 'sql/platform/capabilities/common/capabilitiesService'; +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 { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes'; import { CapabilitiesTestService } from 'sqltest/stubs/capabilitiesTestService'; -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'; +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'; suite('ConnectionConfig', () => { let capabilitiesService: TypeMoq.Mock; @@ -234,9 +231,9 @@ suite('ConnectionConfig', () => { test('getAllGroups should merge user and workspace settings correctly', () => { let configurationService = new TestConfigurationService(); - configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups).slice(0, 3), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connectionGroups', 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(Constants.connectionGroupsArrayName, deepClone(testGroups).slice(2, testGroups.length), ConfigurationTarget.WORKSPACE); + configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups).slice(2, testGroups.length), ConfigurationTarget.WORKSPACE); let config = new ConnectionConfig(configurationService, capabilitiesService.object); let allGroups = config.getAllGroups(); @@ -245,7 +242,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 if does not exist', async () => { + test('addConnection should add the new profile to user settings', async () => { let newProfile: IConnectionProfile = { serverName: 'new server', databaseName: 'database', @@ -265,15 +262,15 @@ suite('ConnectionConfig', () => { }; let configurationService = new TestConfigurationService(); - configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); - configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connections', 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(!isUndefinedOrNull(savedConnectionProfile.id)); - assert.equal(configurationService.inspect(Constants.connectionsArrayName).user.length, testConnections.length + 1); + assert.ok(!!savedConnectionProfile.id); + assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length + 1); }); test('addConnection should not add the new profile to user settings if already exists', async () => { @@ -297,8 +294,8 @@ suite('ConnectionConfig', () => { }; let configurationService = new TestConfigurationService(); - configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); - configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connections', deepClone(testConnections), ConfigurationTarget.USER); let connectionProfile = new ConnectionProfile(capabilitiesService.object, newProfile); connectionProfile.options['databaseDisplayName'] = existingConnection.options['databaseName']; @@ -307,7 +304,7 @@ suite('ConnectionConfig', () => { let savedConnectionProfile = await config.addConnection(connectionProfile); assert.equal(savedConnectionProfile.id, existingConnection.id); - assert.equal(configurationService.inspect(Constants.connectionsArrayName).user.length, testConnections.length); + assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length); }); test('addConnection should add the new group to user settings if does not exist', async () => { @@ -330,21 +327,21 @@ suite('ConnectionConfig', () => { }; let configurationService = new TestConfigurationService(); - configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); - configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connections', 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(Constants.connectionsArrayName).user.length, testConnections.length + 1); - assert.equal(configurationService.inspect(Constants.connectionGroupsArrayName).user.length, testGroups.length + 1); + assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length + 1); + assert.equal(configurationService.inspect('datasource.connectionGroups').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(Constants.connectionsArrayName, deepClone(testConnections).slice(0, 1), ConfigurationTarget.USER); - configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections).slice(1, testConnections.length), ConfigurationTarget.WORKSPACE); + configurationService.updateValue('datasource.connections', deepClone(testConnections).slice(0, 1), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connections', deepClone(testConnections).slice(1, testConnections.length), ConfigurationTarget.WORKSPACE); let config = new ConnectionConfig(configurationService, capabilitiesService.object); let allConnections = config.getConnections(true); @@ -353,8 +350,8 @@ suite('ConnectionConfig', () => { test('getConnections should return connections from user settings given getWorkspaceConnections set to false', () => { let configurationService = new TestConfigurationService(); - configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections).slice(0, 2), ConfigurationTarget.USER); - configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections).slice(2, testConnections.length), ConfigurationTarget.WORKSPACE); + configurationService.updateValue('datasource.connections', deepClone(testConnections).slice(0, 2), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connections', deepClone(testConnections).slice(2, testConnections.length), ConfigurationTarget.WORKSPACE); let config = new ConnectionConfig(configurationService, capabilitiesService.object); let allConnections = config.getConnections(false); @@ -371,8 +368,8 @@ suite('ConnectionConfig', () => { return c; }); let configurationService = new TestConfigurationService(); - configurationService.updateValue(Constants.connectionsArrayName, userConnections, ConfigurationTarget.USER); - configurationService.updateValue(Constants.connectionsArrayName, workspaceConnections, ConfigurationTarget.WORKSPACE); + configurationService.updateValue('datasource.connections', userConnections, ConfigurationTarget.USER); + configurationService.updateValue('datasource.connections', workspaceConnections, ConfigurationTarget.WORKSPACE); let config = new ConnectionConfig(configurationService, capabilitiesService.object); let allConnections = config.getConnections(false); @@ -381,7 +378,7 @@ suite('ConnectionConfig', () => { let userConnection = testConnections.find(u => u.options['serverName'] === connection.serverName); if (userConnection !== undefined) { assert.notEqual(connection.id, connection.getOptionsKey()); - assert.ok(!isUndefinedOrNull(connection.id)); + assert.ok(!!connection.id); } else { let workspaceConnection = workspaceConnections.find(u => u.options['serverName'] === connection.serverName); assert.notEqual(connection.id, connection.getOptionsKey()); @@ -397,7 +394,7 @@ suite('ConnectionConfig', () => { let color: string = 'red'; let result: ISaveGroupResult = config.saveGroup(groups, newGroups, color, newGroups); - assert.ok(!isUndefinedOrNull(result)); + assert.ok(!!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'); @@ -410,7 +407,7 @@ suite('ConnectionConfig', () => { let color: string = 'red'; let result: ISaveGroupResult = config.saveGroup(groups, newGroups, color, newGroups); - assert.ok(!isUndefinedOrNull(result)); + assert.ok(!!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'); @@ -423,7 +420,7 @@ suite('ConnectionConfig', () => { let color: string = 'red'; let result: ISaveGroupResult = config.saveGroup(groups, newGroups, color, newGroups); - assert.ok(!isUndefinedOrNull(result)); + assert.ok(!!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'); @@ -448,7 +445,7 @@ suite('ConnectionConfig', () => { connectionName: undefined }; let configurationService = new TestConfigurationService(); - configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connections', deepClone(testConnections), ConfigurationTarget.USER); let connectionProfile = new ConnectionProfile(capabilitiesService.object, newProfile); connectionProfile.options['databaseDisplayName'] = 'database'; @@ -456,7 +453,7 @@ suite('ConnectionConfig', () => { let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.deleteConnection(connectionProfile); - assert.equal(configurationService.inspect(Constants.connectionsArrayName).user.length, testConnections.length - 1); + assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length - 1); }); test('deleteConnectionGroup should remove the children connections and subgroups from config', async () => { @@ -478,8 +475,8 @@ suite('ConnectionConfig', () => { connectionName: undefined }; let configurationService = new TestConfigurationService(); - configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections), ConfigurationTarget.USER); - configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connections', deepClone(testConnections), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); let connectionProfile = new ConnectionProfile(capabilitiesService.object, newProfile); connectionProfile.options['databaseDisplayName'] = 'database'; @@ -492,8 +489,8 @@ suite('ConnectionConfig', () => { let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.deleteGroup(connectionProfileGroup); - assert.equal(configurationService.inspect(Constants.connectionsArrayName).user.length, testConnections.length - 1); - assert.equal(configurationService.inspect(Constants.connectionGroupsArrayName).user.length, testGroups.length - 2); + assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length - 1); + assert.equal(configurationService.inspect('datasource.connectionGroups').user.length, testGroups.length - 2); }); test('deleteConnection should not throw error for connection not in config', async () => { @@ -515,34 +512,34 @@ suite('ConnectionConfig', () => { connectionName: undefined }; let configurationService = new TestConfigurationService(); - configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connections', 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(Constants.connectionsArrayName).user.length, testConnections.length); + assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length); }); test('renameGroup should change group name', async () => { let configurationService = new TestConfigurationService(); - configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connectionGroups', 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(Constants.connectionGroupsArrayName).user; + let editedGroups = configurationService.inspect('datasource.connectionGroups').user; assert.equal(editedGroups.length, testGroups.length); let editedGroup = editedGroups.find(group => group.id === 'g2'); - assert.ok(!isUndefinedOrNull(editedGroup)); + assert.ok(!!editedGroup); assert.equal(editedGroup.name, 'g-renamed'); }); test('edit group should throw if there is a confliction', async () => { let configurationService = new TestConfigurationService(); - configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); let sameNameGroup = new ConnectionProfileGroup('g3', undefined, 'g2', undefined, undefined); let config = new ConnectionConfig(configurationService, capabilitiesService.object); @@ -551,27 +548,27 @@ suite('ConnectionConfig', () => { await config.editGroup(sameNameGroup); assert.fail(); } catch (e) { - let groups = configurationService.inspect(Constants.connectionGroupsArrayName).user; + let groups = configurationService.inspect('datasource.connectionGroups').user; let originalGroup = groups.find(g => g.id === 'g2'); - assert.ok(!isUndefinedOrNull(originalGroup)); + assert.ok(!!originalGroup); assert.equal(originalGroup.name, 'g2'); } }); test('change group(parent) for connection group', async () => { let configurationService = new TestConfigurationService(); - configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connectionGroups', 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(Constants.connectionGroupsArrayName).user; + let editedGroups = configurationService.inspect('datasource.connectionGroups').user; assert.equal(editedGroups.length, testGroups.length); let editedGroup = editedGroups.find(group => group.id === 'g2'); - assert.ok(!isUndefinedOrNull(editedGroup)); + assert.ok(!!editedGroup); assert.equal(editedGroup.parentId, 'g3'); }); @@ -615,7 +612,7 @@ suite('ConnectionConfig', () => { let _testConnections = deepClone(testConnections).concat([existingProfile, changingProfile]); let configurationService = new TestConfigurationService(); - configurationService.updateValue(Constants.connectionsArrayName, _testConnections, ConfigurationTarget.USER); + configurationService.updateValue('datasource.connections', _testConnections, ConfigurationTarget.USER); let connectionProfile = new ConnectionProfile(capabilitiesService.object, changingProfile); @@ -624,11 +621,11 @@ suite('ConnectionConfig', () => { await config.changeGroupIdForConnection(connectionProfile, 'test'); assert.fail(); } catch (e) { - let editedConnections = configurationService.inspect(Constants.connectionsArrayName).user; + let editedConnections = configurationService.inspect('datasource.connections').user; // two assert.equal(editedConnections.length, _testConnections.length); let editedConnection = editedConnections.find(con => con.id === 'server3-2'); - assert.ok(!isUndefinedOrNull(editedConnection)); + assert.ok(!!editedConnection); assert.equal(editedConnection.groupId, 'g3'); } }); @@ -653,7 +650,7 @@ suite('ConnectionConfig', () => { }; let configurationService = new TestConfigurationService(); - configurationService.updateValue(Constants.connectionsArrayName, deepClone(testConnections), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connections', deepClone(testConnections), ConfigurationTarget.USER); let connectionProfile = new ConnectionProfile(capabilitiesService.object, newProfile); let newId = 'newid'; @@ -661,18 +658,18 @@ suite('ConnectionConfig', () => { let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.changeGroupIdForConnection(connectionProfile, newId); - let editedConnections = configurationService.inspect(Constants.connectionsArrayName).user; + let editedConnections = configurationService.inspect('datasource.connections').user; assert.equal(editedConnections.length, testConnections.length); let editedConnection = editedConnections.find(con => con.id === 'server3'); - assert.ok(!isUndefinedOrNull(editedConnection)); + assert.ok(!!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(Constants.connectionsArrayName, deepClone(testConnections), ConfigurationTarget.USER); - configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connections', deepClone(testConnections), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); let config = new ConnectionConfig(configurationService, capabilitiesService.object); // Clone a connection and modify an option @@ -702,13 +699,13 @@ suite('ConnectionConfig', () => { description: 'new group' }; let configurationService = new TestConfigurationService(); - configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.addGroup(newGroup); - let editGroups = configurationService.inspect(Constants.connectionGroupsArrayName).user; + let editGroups = configurationService.inspect('datasource.connectionGroups').user; assert.equal(editGroups.length, testGroups.length + 1); }); @@ -722,14 +719,14 @@ suite('ConnectionConfig', () => { description: 'new group' }; let configurationService = new TestConfigurationService(); - configurationService.updateValue(Constants.connectionGroupsArrayName, deepClone(testGroups), ConfigurationTarget.USER); + configurationService.updateValue('datasource.connectionGroups', deepClone(testGroups), ConfigurationTarget.USER); const config = new ConnectionConfig(configurationService, capabilitiesService.object); try { await config.addGroup(existingGroupName); assert.fail(); } catch (e) { - let editGroups = configurationService.inspect(Constants.connectionGroupsArrayName).user; + let editGroups = configurationService.inspect('datasource.connectionGroups').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 new file mode 100644 index 0000000000..a7ebd29b06 --- /dev/null +++ b/src/sql/platform/connection/test/common/connectionStore.test.ts @@ -0,0 +1,528 @@ +/*--------------------------------------------------------------------------------------------- + * 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 232fa6eb6e..e2e930d7a6 100644 --- a/src/sql/platform/connection/test/common/testConfigurationService.ts +++ b/src/sql/platform/connection/test/common/testConfigurationService.ts @@ -17,13 +17,8 @@ export class TestConfigurationService implements IConfigurationService { return Promise.resolve(this.getValue()); } - 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 getValue(arg1?: any): any { + return getConfigurationValue(this.configuration.user, arg1); } 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 new file mode 100644 index 0000000000..c2ff91a2c7 --- /dev/null +++ b/src/sql/platform/connection/test/common/testStateService.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * 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 c7879a2ea3..b2e3f4a067 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): Thenable; + saveCredential(credentialId: string, password: string): Promise; - readCredential(credentialId: string): Thenable; + readCredential(credentialId: string): Promise; - deleteCredential(credentialId: string): Thenable; + deleteCredential(credentialId: string): Promise; addEventListener(handle: number, events: CredentialManagementEvents): IDisposable; } @@ -62,15 +62,15 @@ export class CredentialsService implements ICredentialsService { }; } - public saveCredential(credentialId: string, password: string): Thenable { + public saveCredential(credentialId: string, password: string): Promise { return this._onServerEventsReady.promise.then(() => this._serverEvents[this._lastHandle].onSaveCredential(credentialId, password)); } - public readCredential(credentialId: string): Thenable { + public readCredential(credentialId: string): Promise { return this._onServerEventsReady.promise.then(() => this._serverEvents[this._lastHandle].onReadCredential(credentialId)); } - public deleteCredential(credentialId: string): Thenable { + public deleteCredential(credentialId: string): Promise { 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 new file mode 100644 index 0000000000..c1bea76556 --- /dev/null +++ b/src/sql/platform/credentials/test/common/testCredentialsService.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * 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 4efc223046..1a732e2598 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.addActiveConnection(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve()); + connectionStore.setup(x => x.addRecentConnection(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,16 +147,13 @@ 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 deleted file mode 100644 index a47048bde5..0000000000 --- a/src/sqltest/parts/connection/connectionStore.test.ts +++ /dev/null @@ -1,508 +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 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 19b5ba5acd..2c067fffbb 100644 --- a/src/sqltest/parts/connection/connectionTreeActions.test.ts +++ b/src/sqltest/parts/connection/connectionTreeActions.test.ts @@ -18,7 +18,6 @@ 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'; @@ -32,6 +31,7 @@ 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, Constants.unsavedGroupId, undefined, undefined); + connection.parent = new ConnectionProfileGroup(LocalizedConstants.unsavedGroupLabel, undefined, UNSAVED_GROUP_ID, 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 9a02e435f0..11e95398ee 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, {}, {}, new TestStorageService(), connectionDialogService.object); + let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, 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, {}, {}, new TestStorageService()); + let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}); 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, {}, {}, new TestStorageService(), connectionDialogService.object); + let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, 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, {}, {}, new TestStorageService()); + let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}); 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, {}, {}, new TestStorageService()); + let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}); 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, {}, {}, new TestStorageService(), connectionDialogService.object); + let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, 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, {}, {}, new TestStorageService(), connectionDialogService.object); + let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, 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, {}, {}, new TestStorageService()); + let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}); 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, {}, {}, new TestStorageService()); + let cms = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}); 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 fe375ff890..48dedb152a 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): Thenable { + saveCredential(credentialId: string, password: string): Promise { return undefined; } - readCredential(credentialId: string): Thenable { + readCredential(credentialId: string): Promise { return undefined; } - deleteCredential(credentialId: string): Thenable { + deleteCredential(credentialId: string): Promise { return undefined; } @@ -52,4 +52,4 @@ export class CredentialsTestService implements ICredentialsService { return undefined; } -} \ No newline at end of file +}