New UI for deploying SQL project to a new Azure server (#18833)

This commit is contained in:
Leila Lali
2022-04-29 15:39:21 -07:00
committed by GitHub
parent 14a63977c8
commit d95aff1d3c
17 changed files with 1453 additions and 367 deletions

View File

@@ -0,0 +1,109 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as should from 'should';
import { AzureSqlClient } from '../../models/deploy/azureSqlClient';
import { IAccount, IAzureAccountService, IAzureAccountSession, IAzureResourceService, azure } from 'vscode-mssql';
export interface TestContext {
azureAccountService: IAzureAccountService;
azureResourceService: IAzureResourceService;
accounts: IAccount[];
session: IAzureAccountSession;
subscriptions: azure.subscription.Subscription[];
locations: azure.subscription.Location[];
groups: azure.resources.ResourceGroup[];
}
export function createContext(): TestContext {
const accounts = [{
key: undefined!,
displayInfo: undefined!,
properties: {
tenants: [{
id: '',
displayName: ''
}]
},
isStale: false,
isSignedIn: true
}];
const subscriptions: azure.subscription.Subscription[] = [{ subscriptionId: 'id1' }, { subscriptionId: 'id2' }];
const locations: azure.subscription.Location[] = [{ id: 'id1' }, { id: 'id2' }];
const groups: azure.resources.ResourceGroup[] = [{ id: 'id1', location: 'l1' }, { id: 'id2', location: 'l2' }];
const session: IAzureAccountSession = {
account: accounts[0],
subscription: subscriptions[0],
tenantId: 'tenantId',
token: {
key: '',
token: '',
tokenType: '',
}
};
return {
groups: groups,
locations: locations,
subscriptions: subscriptions,
session: session,
accounts: accounts,
azureAccountService: {
addAccount: () => Promise.resolve(accounts[0]),
getAccounts: () => Promise.resolve(accounts),
getAccountSecurityToken: () => Promise.resolve({
key: '',
token: '',
tokenType: ''
}),
getAccountSessions: () => Promise.resolve([session])
},
azureResourceService: {
getLocations: () => Promise.resolve(locations),
getResourceGroups: () => Promise.resolve(groups),
createOrUpdateServer: () => Promise.resolve('new_server')
}
};
}
describe('Azure SQL client', function (): void {
it('Should return accounts successfully', async function (): Promise<void> {
const testContext = createContext();
const azureSqlClient = new AzureSqlClient(() => Promise.resolve(testContext.azureAccountService));
const accounts = await azureSqlClient.getAccounts();
should(accounts.length).equal(testContext.accounts.length);
});
it('Should create and return new account successfully', async function (): Promise<void> {
const testContext = createContext();
const azureSqlClient = new AzureSqlClient(() => Promise.resolve(testContext.azureAccountService));
const account = await azureSqlClient.getAccount();
should(account.key).equal(testContext.accounts[0].key);
});
it('Should return subscriptions successfully', async function (): Promise<void> {
const testContext = createContext();
const azureSqlClient = new AzureSqlClient(() => Promise.resolve(testContext.azureAccountService));
const result = await azureSqlClient.getSessions(testContext.accounts[0]);
should(result[0].subscription.id).deepEqual(testContext.subscriptions[0].id);
});
it('Should return locations successfully', async function (): Promise<void> {
const testContext = createContext();
const azureSqlClient = new AzureSqlClient(() => Promise.resolve(testContext.azureAccountService), () => Promise.resolve(testContext.azureResourceService));
const result = await azureSqlClient.getLocations(testContext.session);
should(result.length).deepEqual(testContext.locations.length);
});
it('Should return resource groups successfully', async function (): Promise<void> {
const testContext = createContext();
const azureSqlClient = new AzureSqlClient(() => Promise.resolve(testContext.azureAccountService), () => Promise.resolve(testContext.azureResourceService));
const result = await azureSqlClient.getResourceGroups(testContext.session);
should(result.length).deepEqual(testContext.groups.length);
should(result[0].location).deepEqual(testContext.groups[0].location);
});
});

View File

@@ -11,16 +11,18 @@ import { DeployService } from '../../models/deploy/deployService';
import { Project } from '../../models/project';
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import { AppSettingType, IDeployProfile } from '../../models/deploy/deployProfile';
import { AppSettingType, 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';
export interface TestContext {
outputChannel: vscode.OutputChannel;
azureSqlClient: TypeMoq.IMock<AzureSqlClient>;
}
export const mockConnectionResult: azdata.ConnectionResult = {
@@ -47,7 +49,8 @@ export function createContext(): TestContext {
show: () => { },
hide: () => { },
dispose: () => { }
}
},
azureSqlClient: TypeMoq.Mock.ofType(AzureSqlClient)
};
}
@@ -68,7 +71,7 @@ describe('deploy service', function (): void {
it('Should deploy a database to docker container successfully', async function (): Promise<void> {
const testContext = createContext();
const deployProfile: IDeployProfile = {
const deployProfile: ILocalDbDeployProfile = {
localDbSetting: {
dbName: 'test',
password: 'PLACEHOLDER',
@@ -84,21 +87,21 @@ describe('deploy service', function (): void {
const project1 = await Project.openProject(vscode.Uri.file(projFilePath).fsPath);
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.outputChannel, shellExecutionHelper.object);
undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('id'));
const deployService = new DeployService(testContext.azureSqlClient.object, testContext.outputChannel, shellExecutionHelper.object);
sandbox.stub(azdata.connection, 'connect').returns(Promise.resolve(mockConnectionResult));
sandbox.stub(azdata.connection, 'getUriForConnection').returns(Promise.resolve('connection'));
sandbox.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve(constants.yesString));
sandbox.stub(azdata.tasks, 'startBackgroundOperation').callThrough();
let connection = await deployService.deploy(deployProfile, project1);
let connection = await deployService.deployToContainer(deployProfile, project1);
should(connection).equals('connection');
});
it('Should fail the deploy if docker is not running', async function (): Promise<void> {
const testContext = createContext();
const deployProfile: IDeployProfile = {
const deployProfile: ILocalDbDeployProfile = {
localDbSetting: {
dbName: 'test',
password: 'PLACEHOLDER',
@@ -114,11 +117,11 @@ describe('deploy service', function (): void {
const project1 = await Project.openProject(vscode.Uri.file(projFilePath).fsPath);
const shellExecutionHelper = TypeMoq.Mock.ofType(ShellExecutionHelper);
shellExecutionHelper.setup(x => x.runStreamedCommand(TypeMoq.It.isAny(),
undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.reject('error'));
const deployService = new DeployService(testContext.outputChannel, shellExecutionHelper.object);
undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.reject('error'));
const deployService = new DeployService(testContext.azureSqlClient.object, testContext.outputChannel, shellExecutionHelper.object);
sandbox.stub(azdata.tasks, 'startBackgroundOperation').callThrough();
await should(deployService.deploy(deployProfile, project1)).rejected();
await should(deployService.deployToContainer(deployProfile, project1)).rejected();
});
it('Should retry connecting to the server', async function (): Promise<void> {
@@ -136,8 +139,8 @@ 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.outputChannel, shellExecutionHelper.object);
undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('id'));
const deployService = new DeployService(testContext.azureSqlClient.object, testContext.outputChannel, shellExecutionHelper.object);
let connectionStub = sandbox.stub(azdata.connection, 'connect');
connectionStub.onFirstCall().returns(Promise.resolve(mockFailedConnectionResult));
connectionStub.onSecondCall().returns(Promise.resolve(mockConnectionResult));
@@ -173,7 +176,7 @@ describe('deploy service', function (): void {
const filePath = path.join(project1.projectFolderPath, 'local.settings.json');
await fse.writeFile(filePath, settingContent);
const deployProfile: IDeployProfile = {
const deployProfile: ILocalDbDeployProfile = {
localDbSetting: {
dbName: 'test',
password: 'PLACEHOLDER',
@@ -194,8 +197,8 @@ 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.outputChannel, shellExecutionHelper.object);
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'));
@@ -228,7 +231,7 @@ describe('deploy service', function (): void {
const filePath = path.join(project1.projectFolderPath, 'local.settings.json');
await fse.writeFile(filePath, settingContent);
const deployProfile: IDeployProfile = {
const deployProfile: ILocalDbDeployProfile = {
deploySettings: {
connectionUri: 'connection',
@@ -245,8 +248,8 @@ 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.outputChannel, shellExecutionHelper.object);
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));
@@ -260,10 +263,10 @@ describe('deploy service', function (): void {
const testContext = createContext();
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
undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(`id
id2
id3`));
const deployService = new DeployService(testContext.outputChannel, shellExecutionHelper.object);
const deployService = new DeployService(testContext.azureSqlClient.object, testContext.outputChannel, shellExecutionHelper.object);
const ids = await deployService.getCurrentDockerContainer('label');
await deployService.cleanDockerObjects(ids, ['docker stop', 'docker rm']);
shellExecutionHelper.verify(x => x.runStreamedCommand(TypeMoq.It.isAny(), undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(7));
@@ -271,7 +274,7 @@ describe('deploy service', function (): void {
it('Should create docker image info correctly', () => {
const testContext = createContext();
const deployService = new DeployService(testContext.outputChannel);
const deployService = new DeployService(testContext.azureSqlClient.object, testContext.outputChannel);
const id = UUID.generateUuid().toLocaleLowerCase();
const baseImage = 'baseImage:latest';
const tag = baseImage.replace(':', '-').replace(constants.sqlServerDockerRegistry, '').replace(/[^a-zA-Z0-9_,\-]/g, '').toLocaleLowerCase();
@@ -311,4 +314,53 @@ describe('deploy service', function (): void {
tag: `${constants.dockerImageNamePrefix}-${imageProjectName}-${tag}`
});
});
it('Should create a new Azure SQL server successfully', async function (): Promise<void> {
const testContext = createContext();
const deployProfile: ISqlDbDeployProfile = {
sqlDbSetting: {
dbName: 'test',
password: 'PLACEHOLDER',
port: 1433,
serverName: 'localhost',
userName: 'sa',
connectionRetryTimeout: 1,
resourceGroupName: 'resourceGroups',
session: {
subscription: {
subscriptionId: 'subscriptionId',
},token: {
key: '',
token: '',
tokenType: '',
},
tenantId: '',
account: undefined!
},
location: 'location'
}
};
const fullyQualifiedDomainName = 'servername';
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 session = deployProfile?.sqlDbSetting?.session;
if (deployProfile?.sqlDbSetting?.session && session) {
testContext.azureSqlClient.setup(x => x.createOrUpdateServer(
session,
deployProfile.sqlDbSetting?.resourceGroupName || '',
deployProfile.sqlDbSetting?.serverName || '',
{
location: deployProfile?.sqlDbSetting?.location || '',
administratorLogin: deployProfile?.sqlDbSetting?.userName,
administratorLoginPassword: deployProfile?.sqlDbSetting?.password
})).returns(() => Promise.resolve(fullyQualifiedDomainName));
}
sandbox.stub(azdata.connection, 'connect').returns(Promise.resolve(mockConnectionResult));
sandbox.stub(azdata.connection, 'getUriForConnection').returns(Promise.resolve('connection'));
const deployService = new DeployService(testContext.azureSqlClient.object, testContext.outputChannel, shellExecutionHelper.object);
let connection = await deployService.createNewAzureSqlServer(deployProfile);
should(deployProfile.sqlDbSetting?.serverName).equal(fullyQualifiedDomainName);
should(connection).equals('connection');
});
});