SQL Project: Opening connection viewlet after deploying database (#19544)

This commit is contained in:
Leila Lali
2022-06-03 11:44:58 -07:00
committed by GitHub
parent de9d73c948
commit e8158d4374
5 changed files with 202 additions and 336 deletions

View File

@@ -47,6 +47,7 @@ import { ILocalDbDeployProfile, ISqlDbDeployProfile } from '../models/deploy/dep
import { EntryType, FileProjectEntry, IDatabaseReferenceProjectEntry, SqlProjectReferenceProjectEntry } from '../models/projectEntry';
import { UpdateProjectAction, UpdateProjectDataModel } from '../models/api/updateProject';
import { AzureSqlClient } from '../models/deploy/azureSqlClient';
import { ConnectionService } from '../models/connections/connectionService';
const maxTableLength = 10;
@@ -74,6 +75,7 @@ export class ProjectsController {
private buildInfo: DashboardData[] = [];
private publishInfo: PublishData[] = [];
private deployService: DeployService;
private connectionService: ConnectionService;
private azureSqlClient: AzureSqlClient;
private autorestHelper: AutorestHelper;
@@ -84,6 +86,7 @@ export class ProjectsController {
this.buildHelper = new BuildHelper();
this.azureSqlClient = new AzureSqlClient();
this.deployService = new DeployService(this.azureSqlClient, this._outputChannel);
this.connectionService = new ConnectionService(this._outputChannel);
this.autorestHelper = new AutorestHelper(this._outputChannel);
}
@@ -293,7 +296,7 @@ export class ProjectsController {
if (deployProfile.sqlDbSetting) {
// Connecting to the deployed db to add the profile to connection viewlet
await this.deployService.getConnection(deployProfile.sqlDbSetting, true, deployProfile.sqlDbSetting.dbName);
await this.connectionService.getConnection(deployProfile.sqlDbSetting, true, deployProfile.sqlDbSetting.dbName);
}
void vscode.window.showInformationMessage(constants.publishProjectSucceed);
} else {
@@ -335,7 +338,7 @@ export class ProjectsController {
const publishResult = await this.publishOrScriptProject(project, deployProfile.deploySettings, true);
if (publishResult && publishResult.success) {
if (deployProfile.localDbSetting) {
await this.deployService.getConnection(deployProfile.localDbSetting, true, deployProfile.localDbSetting.dbName);
await this.connectionService.getConnection(deployProfile.localDbSetting, true, deployProfile.localDbSetting.dbName);
}
void vscode.window.showInformationMessage(constants.publishProjectSucceed);
} else {

View File

@@ -0,0 +1,185 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as constants from '../../common/constants';
import * as utils from '../../common/utils';
import * as vscode from 'vscode';
import { ConnectionResult } from 'azdata';
import { IFireWallRuleError } from 'vscode-mssql';
export interface ISqlConnectionProperties {
tenantId?: string,
accountId?: string
serverName: string,
userName: string,
password: string,
port: number,
dbName: string,
profileName?: string,
connectionRetryTimeout?: number
}
/**
* Includes methods to open connections and interact with connection views
*/
export class ConnectionService {
constructor(private _outputChannel: vscode.OutputChannel) {
}
private defaultSqlRetryTimeoutInSec: number = 10;
private defaultSqlNumberOfRetries: number = 3;
/**
* Connects to a database
* @param profile connection profile
* @param saveConnectionAndPassword if true, connection will be saved in the connection view
* @param database database name
* @returns
*/
private async connectToDatabase(profile: ISqlConnectionProperties, saveConnectionAndPassword: boolean, database: string): Promise<ConnectionResult | string | undefined> {
const azdataApi = utils.getAzdataApi();
const vscodeMssqlApi = azdataApi ? undefined : await utils.getVscodeMssqlApi();
if (azdataApi) {
const connectionProfile = {
password: profile.password,
serverName: `${profile.serverName},${profile.port}`,
database: database,
savePassword: saveConnectionAndPassword,
userName: profile.userName,
providerName: 'MSSQL',
saveProfile: false,
id: '',
connectionName: profile.profileName,
options: [],
authenticationType: 'SqlLogin'
};
return await azdataApi.connection.connect(connectionProfile, saveConnectionAndPassword, false);
} else if (vscodeMssqlApi) {
const connectionProfile = {
password: profile.password,
server: `${profile.serverName}`,
port: profile.port,
database: database,
savePassword: saveConnectionAndPassword,
user: profile.userName,
authenticationType: 'SqlLogin',
encrypt: false,
connectTimeout: 30,
applicationName: 'SQL Database Project',
accountId: profile.accountId,
azureAccountToken: undefined,
applicationIntent: undefined,
attachDbFilename: undefined,
connectRetryCount: undefined,
connectRetryInterval: undefined,
connectionString: undefined,
currentLanguage: undefined,
email: undefined,
failoverPartner: undefined,
loadBalanceTimeout: undefined,
maxPoolSize: undefined,
minPoolSize: undefined,
multiSubnetFailover: undefined,
multipleActiveResultSets: undefined,
packetSize: undefined,
persistSecurityInfo: undefined,
pooling: undefined,
replication: undefined,
trustServerCertificate: undefined,
typeSystemVersion: undefined,
workstationId: undefined,
profileName: profile.profileName,
expiresOn: undefined,
tenantId: profile.tenantId
};
let connectionUrl = '';
try {
connectionUrl = await vscodeMssqlApi.connect(connectionProfile, saveConnectionAndPassword);
} catch (err) {
const firewallRuleError = <IFireWallRuleError>err;
if (firewallRuleError?.connectionUri) {
await vscodeMssqlApi.promptForFirewallRule(err.connectionUri, connectionProfile);
} else {
throw err;
}
}
// If connected successfully and saved the connection in the view, open the connection view
if (saveConnectionAndPassword && connectionUrl) {
await vscode.commands.executeCommand('objectExplorer.focus');
}
return connectionUrl;
} else {
return undefined;
}
}
/**
* Validates the connection result. If using azdata API, verifies connection was successful and connection id is returns
* If using vscode API, verifies the connection url is returns
* @param connection connection result or connection Id
* @returns validation result
*/
private async validateConnection(connection: ConnectionResult | string | undefined): Promise<utils.ValidationResult> {
const azdataApi = utils.getAzdataApi();
if (!connection) {
return { validated: false, errorMessage: constants.connectionFailedError('No result returned') };
} else if (azdataApi) {
const connectionResult = <ConnectionResult>connection;
if (connectionResult) {
const connected = connectionResult !== undefined && connectionResult.connected && connectionResult.connectionId !== undefined;
return { validated: connected, errorMessage: connected ? '' : constants.connectionFailedError(connectionResult?.errorMessage!) };
} else {
return { validated: false, errorMessage: constants.connectionFailedError('') };
}
} else {
return { validated: connection !== undefined, errorMessage: constants.connectionFailedError('') };
}
}
/**
* Formats connection result to string to be able to add to log
* @param connection connection result or connection Id
* @returns formatted connection result
*/
private async formatConnectionResult(connection: ConnectionResult | string | undefined): Promise<string> {
const azdataApi = utils.getAzdataApi();
const connectionResult = connection !== undefined && azdataApi ? <ConnectionResult>connection : undefined;
return connectionResult?.connected ? connectionResult.connectionId! : <string>connection;
}
/**
* Opens a connections and returns the connection id
* @param profile connection profile
* @param saveConnectionAndPassword is set to true the connection will be saved in the connection view
* @param database database name
* @returns connection id
*/
public async getConnection(profile: ISqlConnectionProperties, saveConnectionAndPassword: boolean, database: string): Promise<string | undefined> {
const azdataApi = utils.getAzdataApi();
let connection = await utils.retry(
constants.connectingToSqlServerMessage,
async () => {
return await this.connectToDatabase(profile, saveConnectionAndPassword, database);
},
this.validateConnection,
this.formatConnectionResult,
this._outputChannel,
this.defaultSqlNumberOfRetries, profile.connectionRetryTimeout || this.defaultSqlRetryTimeoutInSec);
if (connection) {
const connectionResult = <ConnectionResult>connection;
if (azdataApi) {
utils.throwIfNotConnected(connectionResult);
return azdataApi.connection.getUriForConnection(connectionResult.connectionId!);
} else {
return <string>connection;
}
}
return undefined;
}
}

View File

@@ -6,6 +6,7 @@
import { IDeploySettings } from '../IDeploySettings';
import type * as azdataType from 'azdata';
import { IAzureAccountSession } from 'vscode-mssql';
import { ISqlConnectionProperties } from '../connections/connectionService';
export enum AppSettingType {
None,
@@ -38,17 +39,7 @@ export interface ILocalDbSetting extends ISqlConnectionProperties {
dockerBaseImageEula: string,
}
export interface ISqlConnectionProperties {
tenantId?: string,
accountId?: string
serverName: string,
userName: string,
password: string,
port: number,
dbName: string,
profileName?: string,
connectionRetryTimeout?: number
}
export interface DockerImageInfo {
name: string,

View File

@@ -3,18 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AppSettingType, IDeployAppIntegrationProfile, ILocalDbDeployProfile, ILocalDbSetting, ISqlConnectionProperties, ISqlDbDeployProfile } from './deployProfile';
import { ILocalDbDeployProfile, ILocalDbSetting, ISqlDbDeployProfile } from './deployProfile';
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
import { Project } from '../project';
import * as constants from '../../common/constants';
import * as utils from '../../common/utils';
import * as fse from 'fs-extra';
import * as vscode from 'vscode';
import { ConnectionResult } from 'azdata';
import * as templates from '../../templates/templates';
import { ShellExecutionHelper } from '../../tools/shellExecutionHelper';
import { AzureSqlClient } from './azureSqlClient';
import { IFireWallRuleError } from 'vscode-mssql';
import { ConnectionService } from '../connections/connectionService';
interface DockerImageSpec {
label: string;
@@ -25,73 +22,11 @@ export class DeployService {
constructor(private _azureSqlClient = new AzureSqlClient(), private _outputChannel: vscode.OutputChannel, shellExecutionHelper: ShellExecutionHelper | undefined = undefined) {
this._shellExecutionHelper = shellExecutionHelper ?? new ShellExecutionHelper(this._outputChannel);
this._connectionService = new ConnectionService(this._outputChannel);
}
private _shellExecutionHelper: ShellExecutionHelper;
private DefaultSqlRetryTimeoutInSec: number = 10;
private DefaultSqlNumberOfRetries: number = 3;
private createConnectionStringTemplate(runtime: string | undefined): string {
switch (runtime?.toLocaleLowerCase()) {
case 'dotnet':
return constants.defaultConnectionStringTemplate;
break;
// TODO: add connection strings for other languages
default:
break;
}
return '';
}
private findAppRuntime(profile: IDeployAppIntegrationProfile, appSettingContent: any): string | undefined {
switch (profile.appSettingType) {
case AppSettingType.AzureFunction:
return <string>appSettingContent?.Values['FUNCTIONS_WORKER_RUNTIME'];
default:
}
return undefined;
}
public async updateAppSettings(profile: IDeployAppIntegrationProfile, deployProfile: ILocalDbDeployProfile | undefined): Promise<void> {
// Update app settings
//
if (!profile.appSettingFile) {
return;
}
this.logToOutput(constants.deployAppSettingUpdating(profile.appSettingFile));
// TODO: handle parsing errors
let content = JSON.parse(fse.readFileSync(profile.appSettingFile, 'utf8'));
if (content && content.Values) {
let connectionString: string | undefined = '';
if (deployProfile && deployProfile.localDbSetting) {
// Find the runtime and generate the connection string for the runtime
//
const runtime = this.findAppRuntime(profile, content);
let connectionStringTemplate = this.createConnectionStringTemplate(runtime);
const macroDict: Record<string, string> = {
'SERVER': deployProfile?.localDbSetting?.serverName || '',
'PORT': deployProfile?.localDbSetting?.port?.toString() || '',
'USER': deployProfile?.localDbSetting?.userName || '',
'SA_PASSWORD': deployProfile?.localDbSetting?.password || '',
'DATABASE': deployProfile?.localDbSetting?.dbName || '',
};
connectionString = templates.macroExpansion(connectionStringTemplate, macroDict);
} else if (deployProfile?.deploySettings?.connectionUri) {
connectionString = await this.getConnectionString(deployProfile?.deploySettings?.connectionUri);
}
if (connectionString && profile.envVariableName) {
content.Values[profile.envVariableName] = connectionString;
fse.writeFileSync(profile.appSettingFile, JSON.stringify(content, undefined, 4));
this.logToOutput(`app setting '${profile.appSettingFile}' has been updated. env variable name: ${profile.envVariableName} connection String: ${connectionString}`);
} else {
this.logToOutput(constants.deployAppSettingUpdateFailed(profile.appSettingFile));
}
}
}
private _connectionService: ConnectionService;
private async verifyDocker(): Promise<void> {
try {
@@ -149,7 +84,7 @@ export class DeployService {
this.logToOutput(constants.azureSqlServerCreated(profile?.sqlDbSetting?.serverName));
// Connect to the server
return await this.getConnection(profile.sqlDbSetting, false, constants.master);
return await this._connectionService.getConnection(profile.sqlDbSetting, false, constants.master);
}
return undefined;
}
@@ -209,7 +144,7 @@ export class DeployService {
if (runningDockerId) {
this.logToOutput(constants.dockerContainerCreatedMessage(runningDockerId));
return await this.getConnection(profile.localDbSetting, false, 'master');
return await this._connectionService.getConnection(profile.localDbSetting, false, 'master');
} else {
this.logToOutput(constants.dockerContainerFailedToRunErrorMessage);
@@ -235,145 +170,6 @@ export class DeployService {
return await this.executeCommand(`docker ps -q -a --filter label=${dockerImageSpec.label} -q`);
}
private async getConnectionString(connectionUri: string): Promise<string | undefined> {
const azdataApi = utils.getAzdataApi();
if (azdataApi) {
const connection = await azdataApi.connection.getConnection(connectionUri);
if (connection) {
return await azdataApi.connection.getConnectionString(connection.connectionId, true);
}
}
// TODO: vscode connections string
return undefined;
}
// Connects to a database
private async connectToDatabase(profile: ISqlConnectionProperties, saveConnectionAndPassword: boolean, database: string): Promise<ConnectionResult | string | undefined> {
const azdataApi = utils.getAzdataApi();
const vscodeMssqlApi = azdataApi ? undefined : await utils.getVscodeMssqlApi();
if (azdataApi) {
const connectionProfile = {
password: profile.password,
serverName: `${profile.serverName},${profile.port}`,
database: database,
savePassword: saveConnectionAndPassword,
userName: profile.userName,
providerName: 'MSSQL',
saveProfile: false,
id: '',
connectionName: profile.profileName,
options: [],
authenticationType: 'SqlLogin'
};
return await azdataApi.connection.connect(connectionProfile, saveConnectionAndPassword, false);
} else if (vscodeMssqlApi) {
const connectionProfile = {
password: profile.password,
server: `${profile.serverName}`,
port: profile.port,
database: database,
savePassword: saveConnectionAndPassword,
user: profile.userName,
authenticationType: 'SqlLogin',
encrypt: false,
connectTimeout: 30,
applicationName: 'SQL Database Project',
accountId: profile.accountId,
azureAccountToken: undefined,
applicationIntent: undefined,
attachDbFilename: undefined,
connectRetryCount: undefined,
connectRetryInterval: undefined,
connectionString: undefined,
currentLanguage: undefined,
email: undefined,
failoverPartner: undefined,
loadBalanceTimeout: undefined,
maxPoolSize: undefined,
minPoolSize: undefined,
multiSubnetFailover: undefined,
multipleActiveResultSets: undefined,
packetSize: undefined,
persistSecurityInfo: undefined,
pooling: undefined,
replication: undefined,
trustServerCertificate: undefined,
typeSystemVersion: undefined,
workstationId: undefined,
profileName: profile.profileName,
expiresOn: undefined,
tenantId: profile.tenantId
};
let connectionUrl = '';
try {
connectionUrl = await vscodeMssqlApi.connect(connectionProfile, saveConnectionAndPassword);
} catch (err) {
const firewallRuleError = <IFireWallRuleError>err;
if (firewallRuleError?.connectionUri) {
await vscodeMssqlApi.promptForFirewallRule(err.connectionUri, connectionProfile);
} else {
throw err;
}
}
return connectionUrl;
} else {
return undefined;
}
}
// Validates the connection result. If using azdata API, verifies connection was successful and connection id is returns
// If using vscode API, verifies the connection url is returns
private async validateConnection(connection: ConnectionResult | string | undefined): Promise<utils.ValidationResult> {
const azdataApi = utils.getAzdataApi();
if (!connection) {
return { validated: false, errorMessage: constants.connectionFailedError('No result returned') };
} else if (azdataApi) {
const connectionResult = <ConnectionResult>connection;
if (connectionResult) {
const connected = connectionResult !== undefined && connectionResult.connected && connectionResult.connectionId !== undefined;
return { validated: connected, errorMessage: connected ? '' : constants.connectionFailedError(connectionResult?.errorMessage!) };
} else {
return { validated: false, errorMessage: constants.connectionFailedError('') };
}
} else {
return { validated: connection !== undefined, errorMessage: constants.connectionFailedError('') };
}
}
// Formats connection result to string to be able to add to log
private async formatConnectionResult(connection: ConnectionResult | string | undefined): Promise<string> {
const azdataApi = utils.getAzdataApi();
const connectionResult = connection !== undefined && azdataApi ? <ConnectionResult>connection : undefined;
return connectionResult?.connected ? connectionResult.connectionId! : <string>connection;
}
public async getConnection(profile: ISqlConnectionProperties, saveConnectionAndPassword: boolean, database: string): Promise<string | undefined> {
const azdataApi = utils.getAzdataApi();
let connection = await utils.retry(
constants.connectingToSqlServerMessage,
async () => {
return await this.connectToDatabase(profile, saveConnectionAndPassword, database);
},
this.validateConnection,
this.formatConnectionResult,
this._outputChannel,
this.DefaultSqlNumberOfRetries, profile.connectionRetryTimeout || this.DefaultSqlRetryTimeoutInSec);
if (connection) {
const connectionResult = <ConnectionResult>connection;
if (azdataApi) {
utils.throwIfNotConnected(connectionResult);
return azdataApi.connection.getUriForConnection(connectionResult.connectionId!);
} else {
return <string>connection;
}
}
return undefined;
}
private async executeTask<T>(taskName: string, task: () => Promise<T>): Promise<T> {
const azdataApi = utils.getAzdataApi();
if (azdataApi) {

View File

@@ -11,14 +11,13 @@ import { DeployService } from '../../models/deploy/deployService';
import { Project } from '../../models/project';
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import { AppSettingType, ILocalDbDeployProfile, ISqlDbDeployProfile } from '../../models/deploy/deployProfile';
import { ILocalDbDeployProfile, ISqlDbDeployProfile } from '../../models/deploy/deployProfile';
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
import * as fse from 'fs-extra';
import * as path from 'path';
import * as constants from '../../common/constants';
import { ShellExecutionHelper } from '../../tools/shellExecutionHelper';
import * as TypeMoq from 'typemoq';
import { AzureSqlClient } from '../../models/deploy/azureSqlClient';
import { ConnectionService } from '../../models/connections/connectionService';
export interface TestContext {
outputChannel: vscode.OutputChannel;
@@ -140,125 +139,17 @@ describe('deploy service', function (): void {
const shellExecutionHelper = TypeMoq.Mock.ofType(ShellExecutionHelper);
shellExecutionHelper.setup(x => x.runStreamedCommand(TypeMoq.It.isAny(),
undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('id'));
const deployService = new DeployService(testContext.azureSqlClient.object, testContext.outputChannel, shellExecutionHelper.object);
const connectionService = new ConnectionService(testContext.outputChannel);
let connectionStub = sandbox.stub(azdata.connection, 'connect');
connectionStub.onFirstCall().returns(Promise.resolve(mockFailedConnectionResult));
connectionStub.onSecondCall().returns(Promise.resolve(mockConnectionResult));
sandbox.stub(azdata.connection, 'getUriForConnection').returns(Promise.resolve('connection'));
sandbox.stub(azdata.tasks, 'startBackgroundOperation').callThrough();
let connection = await deployService.getConnection(localDbSettings, false, 'master');
let connection = await connectionService.getConnection(localDbSettings, false, 'master');
should(connection).equals('connection');
});
it('Should update app settings successfully', async function (): Promise<void> {
const testContext = createContext();
const projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
const project1 = await Project.openProject(vscode.Uri.file(projFilePath).fsPath);
const jsondData =
{
IsEncrypted: false,
Values: {
AzureWebJobsStorage: 'UseDevelopmentStorage=true',
FUNCTIONS_WORKER_RUNTIME: 'dotnet'
}
};
let settingContent = JSON.stringify(jsondData, undefined, 4);
const expected =
{
IsEncrypted: false,
Values: {
AzureWebJobsStorage: 'UseDevelopmentStorage=true',
FUNCTIONS_WORKER_RUNTIME: 'dotnet',
SQLConnectionString: 'Data Source=localhost,1433;Initial Catalog=test;User id=sa;Password=PLACEHOLDER;'
}
};
const filePath = path.join(project1.projectFolderPath, 'local.settings.json');
await fse.writeFile(filePath, settingContent);
const deployProfile: ILocalDbDeployProfile = {
localDbSetting: {
dbName: 'test',
password: 'PLACEHOLDER',
port: 1433,
serverName: 'localhost',
userName: 'sa',
dockerBaseImage: 'image',
dockerBaseImageEula: ''
}
};
const appInteg = {
appSettingType: AppSettingType.AzureFunction,
appSettingFile: filePath,
deploySettings: undefined,
envVariableName: 'SQLConnectionString'
};
const shellExecutionHelper = TypeMoq.Mock.ofType(ShellExecutionHelper);
shellExecutionHelper.setup(x => x.runStreamedCommand(TypeMoq.It.isAny(),
undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('id'));
const deployService = new DeployService(testContext.azureSqlClient.object, testContext.outputChannel, shellExecutionHelper.object);
await deployService.updateAppSettings(appInteg, deployProfile);
let newContent = JSON.parse(fse.readFileSync(filePath, 'utf8'));
should(newContent).deepEqual(expected);
});
it('Should update app settings using connection uri if there are no local settings', async function (): Promise<void> {
const testContext = createContext();
const projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
const project1 = await Project.openProject(vscode.Uri.file(projFilePath).fsPath);
const jsondData =
{
IsEncrypted: false,
Values: {
AzureWebJobsStorage: 'UseDevelopmentStorage=true',
FUNCTIONS_WORKER_RUNTIME: 'dotnet'
}
};
let settingContent = JSON.stringify(jsondData, undefined, 4);
const expected =
{
IsEncrypted: false,
Values: {
AzureWebJobsStorage: 'UseDevelopmentStorage=true',
FUNCTIONS_WORKER_RUNTIME: 'dotnet',
SQLConnectionString: 'connectionString'
}
};
const filePath = path.join(project1.projectFolderPath, 'local.settings.json');
await fse.writeFile(filePath, settingContent);
const deployProfile: ILocalDbDeployProfile = {
deploySettings: {
connectionUri: 'connection',
databaseName: 'test',
serverName: 'test'
},
localDbSetting: undefined
};
const appInteg = {
appSettingType: AppSettingType.AzureFunction,
appSettingFile: filePath,
envVariableName: 'SQLConnectionString',
};
const shellExecutionHelper = TypeMoq.Mock.ofType(ShellExecutionHelper);
shellExecutionHelper.setup(x => x.runStreamedCommand(TypeMoq.It.isAny(),
undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('id'));
const deployService = new DeployService(testContext.azureSqlClient.object, testContext.outputChannel, shellExecutionHelper.object);
let connection = new azdata.connection.ConnectionProfile();
sandbox.stub(azdata.connection, 'getConnection').returns(Promise.resolve(connection));
sandbox.stub(azdata.connection, 'getConnectionString').returns(Promise.resolve('connectionString'));
await deployService.updateAppSettings(appInteg, deployProfile);
let newContent = JSON.parse(fse.readFileSync(filePath, 'utf8'));
should(newContent).deepEqual(expected);
});
it('Should clean a list of docker images successfully', async function (): Promise<void> {
const testContext = createContext();
const shellExecutionHelper = TypeMoq.Mock.ofType(ShellExecutionHelper);