Add arc tree data provider tests (#12758)

* Add arc tree data provider tests

* Generic ResourceModel

* no message

* undo

* Fix compile error
This commit is contained in:
Charles Gagnon
2020-10-06 11:26:56 -07:00
committed by GitHub
parent 1dea5f8f7b
commit 288288df03
7 changed files with 151 additions and 16 deletions

View File

@@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdataExt from 'azdata-ext';
/**
* Simple fake Azdata Api used to mock the API during tests
*/
export class FakeAzdataApi implements azdataExt.IAzdataApi {
public postgresInstances: azdataExt.PostgresServerListResult[] = [];
public miaaInstances: azdataExt.SqlMiListResult[] = [];
//
// API Implementation
//
public get arc() {
const self = this;
return {
dc: {
create(_namespace: string, _name: string, _connectivityMode: string, _resourceGroup: string, _location: string, _subscription: string, _profileName?: string, _storageClass?: string): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); },
endpoint: {
async list(): Promise<azdataExt.AzdataOutput<azdataExt.DcEndpointListResult[]>> { return <any>{ result: [] }; }
},
config: {
list(): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigListResult[]>> { throw new Error('Method not implemented.'); },
async show(): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigShowResult>> { return <any>{ result: undefined! }; }
}
},
postgres: {
server: {
delete(_name: string): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); },
async list(): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerListResult[]>> { return <any>{ result: self.postgresInstances }; },
show(_name: string): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerShowResult>> { throw new Error('Method not implemented.'); },
edit(
_name: string,
_args: {
adminPassword?: boolean,
coresLimit?: string,
coresRequest?: string,
engineSettings?: string,
extensions?: string,
memoryLimit?: string,
memoryRequest?: string,
noWait?: boolean,
port?: number,
replaceEngineSettings?: boolean,
workers?: number
},
_additionalEnvVars?: { [key: string]: string }): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); }
}
},
sql: {
mi: {
delete(_name: string): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); },
async list(): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiListResult[]>> { return <any>{ result: self.miaaInstances }; },
show(_name: string): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiShowResult>> { throw new Error('Method not implemented.'); }
}
}
};
}
getPath(): Promise<string> {
throw new Error('Method not implemented.');
}
login(_endpoint: string, _username: string, _password: string): Promise<azdataExt.AzdataOutput<any>> {
return <any>undefined;
}
version(): Promise<azdataExt.AzdataOutput<string>> {
throw new Error('Method not implemented.');
}
getSemVersion(): any {
throw new Error('Method not implemented.');
}
}

View File

@@ -3,16 +3,21 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ControllerInfo } from 'arc';
import { ControllerInfo, ResourceType } from 'arc';
import 'mocha';
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import * as sinon from 'sinon';
import { v4 as uuid } from 'uuid';
import * as vscode from 'vscode';
import * as azdataExt from 'azdata-ext';
import { ControllerModel } from '../../../models/controllerModel';
import { MiaaModel } from '../../../models/miaaModel';
import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider';
import { ControllerTreeNode } from '../../../ui/tree/controllerTreeNode';
import { MiaaTreeNode } from '../../../ui/tree/miaaTreeNode';
import { FakeControllerModel } from '../../mocks/fakeControllerModel';
import { FakeAzdataApi } from '../../mocks/fakeAzdataApi';
describe('AzureArcTreeDataProvider tests', function (): void {
let treeDataProvider: AzureArcTreeDataProvider;
@@ -84,6 +89,27 @@ describe('AzureArcTreeDataProvider tests', function (): void {
let children = await treeDataProvider.getChildren();
should(children.length).equal(0, 'After loading we should have 0 children');
});
it('should return all children of controller after loading', async function (): Promise<void> {
const mockArcExtension = TypeMoq.Mock.ofType<vscode.Extension<any>>();
const mockArcApi = TypeMoq.Mock.ofType<azdataExt.IExtension>();
mockArcExtension.setup(x => x.exports).returns(() => {
return mockArcApi.object;
});
const fakeAzdataApi = new FakeAzdataApi();
fakeAzdataApi.postgresInstances = [{ name: 'pg1', state: '', workers: 0 }];
fakeAzdataApi.miaaInstances = [{ name: 'miaa1', state: '', replicas: '', serverEndpoint: '' }];
mockArcApi.setup(x => x.azdata).returns(() => fakeAzdataApi);
sinon.stub(vscode.extensions, 'getExtension').returns(mockArcExtension.object);
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }, 'mypassword');
await treeDataProvider.addOrUpdateController(controllerModel, '');
const controllerNode = treeDataProvider.getControllerNode(controllerModel);
const children = await treeDataProvider.getChildren(controllerNode);
should(children.filter(c => c.label === fakeAzdataApi.postgresInstances[0].name).length).equal(1, 'Should have a Postgres child');
should(children.filter(c => c.label === fakeAzdataApi.miaaInstances[0].name).length).equal(1, 'Should have a MIAA child');
should(children.length).equal(2, 'Should have excatly 2 children');
});
});
describe('removeController', function (): void {
@@ -104,4 +130,31 @@ describe('AzureArcTreeDataProvider tests', function (): void {
should((await treeDataProvider.getChildren()).length).equal(0, 'Removing other node again should do nothing');
});
});
describe('openResourceDashboard', function (): void {
it('Opening dashboard for nonexistent controller node throws', async function (): Promise<void> {
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
await should(openDashboardPromise).be.rejected();
});
it('Opening dashboard for nonexistent resource throws', async function (): Promise<void> {
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
await treeDataProvider.addOrUpdateController(controllerModel, '');
const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
await should(openDashboardPromise).be.rejected();
});
it('Opening dashboard for existing resource node succeeds', async function (): Promise<void> {
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const miaaModel = new MiaaModel(controllerModel, { name: 'miaa-1', resourceType: ResourceType.sqlManagedInstances }, undefined!, treeDataProvider);
await treeDataProvider.addOrUpdateController(controllerModel, '');
const controllerNode = treeDataProvider.getControllerNode(controllerModel)!;
const resourceNode = new MiaaTreeNode(miaaModel, controllerModel);
sinon.stub(controllerNode, 'getResourceNode').returns(resourceNode);
const showDashboardStub = sinon.stub(resourceNode, 'openDashboard');
await treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
should(showDashboardStub.calledOnce).be.true('showDashboard should have been called exactly once');
});
});
});

View File

@@ -135,10 +135,14 @@ export class AzureArcTreeDataProvider implements vscode.TreeDataProvider<TreeNod
if (resourceNode) {
await resourceNode.openDashboard();
} else {
console.log(`Couldn't find resource node for ${name} (${resourceType})`);
const errMsg = `Couldn't find resource node for ${name} (${resourceType})`;
console.log(errMsg);
throw new Error(errMsg);
}
} else {
console.log('Couldn\'t find controller node for opening dashboard');
const errMsg = 'Couldn\'t find controller node for opening dashboard';
console.log(errMsg);
throw new Error(errMsg);
}
}
}

View File

@@ -10,6 +10,7 @@ import * as loc from '../../localizedConstants';
import { ControllerModel, Registration } from '../../models/controllerModel';
import { MiaaModel } from '../../models/miaaModel';
import { PostgresModel } from '../../models/postgresModel';
import { ResourceModel } from '../../models/resourceModel';
import { ControllerDashboard } from '../dashboards/controller/controllerDashboard';
import { AzureArcTreeDataProvider } from './azureArcTreeDataProvider';
import { MiaaTreeNode } from './miaaTreeNode';
@@ -24,7 +25,7 @@ import { TreeNode } from './treeNode';
*/
export class ControllerTreeNode extends TreeNode {
private _children: ResourceTreeNode[] = [];
private _children: ResourceTreeNode<ResourceModel>[] = [];
constructor(public model: ControllerModel, private _context: vscode.ExtensionContext, private _treeDataProvider: AzureArcTreeDataProvider) {
super(model.label, vscode.TreeItemCollapsibleState.Collapsed, ResourceType.dataControllers);
@@ -69,14 +70,14 @@ export class ControllerTreeNode extends TreeNode {
* @param resourceType The resourceType of the node
* @param name The name of the node
*/
public getResourceNode(resourceType: string, name: string): ResourceTreeNode | undefined {
public getResourceNode(resourceType: string, name: string): ResourceTreeNode<ResourceModel> | undefined {
return this._children.find(c =>
c.model?.info.resourceType === resourceType &&
c.model.info.name === name);
}
private updateChildren(registrations: Registration[]): void {
const newChildren: ResourceTreeNode[] = [];
const newChildren: ResourceTreeNode<ResourceModel>[] = [];
registrations.forEach(registration => {
if (!registration.instanceName) {
console.warn('Registration is missing required name value, skipping');

View File

@@ -8,15 +8,15 @@ import * as vscode from 'vscode';
import { ControllerModel } from '../../models/controllerModel';
import { MiaaModel } from '../../models/miaaModel';
import { MiaaDashboard } from '../dashboards/miaa/miaaDashboard';
import { TreeNode } from './treeNode';
import { ResourceTreeNode } from './resourceTreeNode';
/**
* The TreeNode for displaying a SQL Managed Instance on Azure Arc
*/
export class MiaaTreeNode extends TreeNode {
export class MiaaTreeNode extends ResourceTreeNode<MiaaModel> {
constructor(public model: MiaaModel, private _controllerModel: ControllerModel) {
super(model.info.name, vscode.TreeItemCollapsibleState.None, ResourceType.sqlManagedInstances);
constructor(model: MiaaModel, private _controllerModel: ControllerModel) {
super(model.info.name, vscode.TreeItemCollapsibleState.None, ResourceType.sqlManagedInstances, model);
}
public async openDashboard(): Promise<void> {

View File

@@ -13,14 +13,14 @@ import { ResourceTreeNode } from './resourceTreeNode';
/**
* The TreeNode for displaying an Postgres Server group
*/
export class PostgresTreeNode extends ResourceTreeNode {
export class PostgresTreeNode extends ResourceTreeNode<PostgresModel> {
constructor(private _model: PostgresModel, private _controllerModel: ControllerModel, private _context: vscode.ExtensionContext) {
super(_model.info.name, vscode.TreeItemCollapsibleState.None, ResourceType.postgresInstances, _model);
constructor(model: PostgresModel, private _controllerModel: ControllerModel, private _context: vscode.ExtensionContext) {
super(model.info.name, vscode.TreeItemCollapsibleState.None, ResourceType.postgresInstances, model);
}
public async openDashboard(): Promise<void> {
const postgresDashboard = new PostgresDashboard(this._context, this._controllerModel, this._model);
const postgresDashboard = new PostgresDashboard(this._context, this._controllerModel, this.model);
await postgresDashboard.showDashboard();
}
}

View File

@@ -10,8 +10,8 @@ import { TreeNode } from './treeNode';
/**
* A TreeNode belonging to a child of a Controller
*/
export abstract class ResourceTreeNode extends TreeNode {
constructor(label: string, collapsibleState: vscode.TreeItemCollapsibleState, resourceType?: string, public model?: ResourceModel) {
export abstract class ResourceTreeNode<M extends ResourceModel> extends TreeNode {
constructor(label: string, collapsibleState: vscode.TreeItemCollapsibleState, resourceType: string, public model: M) {
super(label, collapsibleState, resourceType);
}
}