Arc updates for March release (#14970)

* Updated Postgres Spec for where to find engine version, removed calling calling -ev in edit commands (#14735)

* Added spec.engine.version, took out calling engine version with edit calls

* Added text wrong place

* missed updates

* PR fix

* Update Arc Postgres troubleshooting notebook

Co-authored-by: Brian Bergeron <brberger@microsoft.com>

* Remove AzdataSession from azdata commands (#14856)

* remove session

* Add in controller-context support

* Revert "Add in controller-context support"

This reverts commit 3b39b968efbf6054041cb01cb2d8443532643a82.

* Add azdataContext to login

* Undo book change

* Undo change correctly

* Add controller context support (#14862)

* remove session

* Add in controller-context support

* Add params to fake

* Fix tests

* Add info and placeholder for controller URL/name (#14887)

* Add info and placeholder for controller URL

* add period + update name

* update memento and allow editing of namespace/URL

* vBump

* vBump

* Fix tests

Co-authored-by: nasc17 <69922333+nasc17@users.noreply.github.com>
Co-authored-by: Brian Bergeron <brian.e.bergeron@gmail.com>
Co-authored-by: Brian Bergeron <brberger@microsoft.com>
This commit is contained in:
Charles Gagnon
2021-04-05 11:47:36 -07:00
committed by GitHub
parent 71b91c3890
commit febd8b29c9
44 changed files with 525 additions and 740 deletions

View File

@@ -51,7 +51,7 @@ describe('KubeUtils', function (): void {
contexts[1].name.should.equal('kubernetes-admin@kubernetes', `test: ${testName} failed`);
contexts[1].isCurrentContext.should.be.false(`test: ${testName} failed`);
};
verifyContexts(await getKubeConfigClusterContexts(configFile), 'getKubeConfigClusterContexts');
verifyContexts(getKubeConfigClusterContexts(configFile), 'getKubeConfigClusterContexts');
});
it('throws error when unable to load config file', async () => {
const error = new Error('unknown error accessing file');

View File

@@ -23,9 +23,9 @@ export class FakeAzdataApi implements azdataExt.IAzdataApi {
},
postgres: {
server: {
postgresInstances: [],
postgresInstances: <azdataExt.PostgresServerListResult[]>[],
delete(_name: string): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); },
async list(): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerListResult[]>> { return <any>{ result: this.postgresInstances }; },
async list(): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerListResult[]>> { return { result: this.postgresInstances, logs: [], stdout: [], stderr: [] }; },
show(_name: string): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerShowResult>> { throw new Error('Method not implemented.'); },
edit(
_name: string,
@@ -42,16 +42,15 @@ export class FakeAzdataApi implements azdataExt.IAzdataApi {
replaceEngineSettings?: boolean,
workers?: number
},
_engineVersion?: string,
_additionalEnvVars?: azdataExt.AdditionalEnvVars
): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); }
}
},
sql: {
mi: {
miaaInstances: [],
miaaInstances: <azdataExt.SqlMiListResult[]>[],
delete(_name: string): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); },
async list(): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiListResult[]>> { return <any>{ result: this.miaaInstances }; },
async list(): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiListResult[]>> { return { logs: [], stdout: [], stderr: [], result: this.miaaInstances }; },
show(_name: string): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiShowResult>> { throw new Error('Method not implemented.'); },
edit(
_name: string,
@@ -66,17 +65,14 @@ export class FakeAzdataApi implements azdataExt.IAzdataApi {
}
};
// public postgresInstances: azdataExt.PostgresServerListResult[] = [];
public set postgresInstances(instances: azdataExt.PostgresServerListResult[]) {
this._arcApi.postgres.server.postgresInstances = <any>instances;
this._arcApi.postgres.server.postgresInstances = instances;
}
public set miaaInstances(instances: azdataExt.SqlMiListResult[]) {
this._arcApi.sql.mi.miaaInstances = <any>instances;
this._arcApi.sql.mi.miaaInstances = instances;
}
// public miaaInstances: azdataExt.SqlMiListResult[] = [];
//
// API Implementation
//
@@ -86,12 +82,9 @@ export class FakeAzdataApi implements azdataExt.IAzdataApi {
getPath(): Promise<string> {
throw new Error('Method not implemented.');
}
login(_endpoint: string, _username: string, _password: string): Promise<azdataExt.AzdataOutput<void>> {
login(_endpointOrNamespace: azdataExt.EndpointOrNamespace, _username: string, _password: string, _additionalEnvVars: azdataExt.AdditionalEnvVars = {}, _azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> {
return <any>undefined;
}
acquireSession(_endpoint: string, _username: string, _password: string): Promise<azdataExt.AzdataSession> {
return Promise.resolve({ dispose: () => { } });
}
version(): Promise<azdataExt.AzdataOutput<string>> {
throw new Error('Method not implemented.');
}

View File

@@ -11,7 +11,7 @@ import { AzureArcTreeDataProvider } from '../../ui/tree/azureArcTreeDataProvider
export class FakeControllerModel extends ControllerModel {
constructor(treeDataProvider?: AzureArcTreeDataProvider, info?: Partial<ControllerInfo>, password?: string) {
const _info: ControllerInfo = Object.assign({ id: uuid(), url: '', kubeConfigFilePath: '', kubeClusterContext: '', name: '', username: '', rememberPassword: false, resources: [] }, info);
const _info: ControllerInfo = Object.assign({ id: uuid(), endpoint: '', kubeConfigFilePath: '', kubeClusterContext: '', name: '', namespace: '', username: '', rememberPassword: false, resources: [] }, info);
super(treeDataProvider!, _info, password);
}

View File

@@ -22,6 +22,20 @@ interface ExtensionGlobalMemento extends vscode.Memento {
setKeysForSync(keys: string[]): void;
}
function getDefaultControllerInfo(): ControllerInfo {
return {
id: uuid(),
endpoint: '127.0.0.1',
kubeConfigFilePath: '/path/to/.kube/config',
kubeClusterContext: 'currentCluster',
username: 'admin',
name: 'arc',
namespace: 'arc-ns',
rememberPassword: true,
resources: []
};
}
describe('ControllerModel', function (): void {
afterEach(function (): void {
sinon.restore();
@@ -39,15 +53,15 @@ describe('ControllerModel', function (): void {
beforeEach(function (): void {
sinon.stub(ConnectToControllerDialog.prototype, 'showDialog');
sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').resolves([{ name: 'currentCluster', isCurrentContext: true }]);
sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').returns([{ name: 'currentCluster', isCurrentContext: true }]);
sinon.stub(vscode.window, 'showErrorMessage').resolves(<any>loc.yes);
});
it('Rejected with expected error when user cancels', async function (): Promise<void> {
// Returning an undefined model here indicates that the dialog closed without clicking "Ok" - usually through the user clicking "Cancel"
sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(undefined));
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
await should(model.acquireAzdataSession()).be.rejectedWith(new UserCancelledError(loc.userCancelledError));
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
await should(model.login()).be.rejectedWith(new UserCancelledError(loc.userCancelledError));
});
it('Reads password from cred store', async function (): Promise<void> {
@@ -62,13 +76,13 @@ describe('ControllerModel', function (): void {
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
azdataMock.setup(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
await model.acquireAzdataSession();
azdataMock.verify(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
await model.login();
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
});
it('Prompt for password when not in cred store', async function (): Promise<void> {
@@ -83,18 +97,18 @@ describe('ControllerModel', function (): void {
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
azdataMock.setup(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
// Set up dialog to return new model with our password
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password);
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), password);
sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
await model.acquireAzdataSession();
azdataMock.verify(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
await model.login();
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
});
it('Prompt for password when rememberPassword is true but prompt reconnect is true', async function (): Promise<void> {
@@ -108,19 +122,19 @@ describe('ControllerModel', function (): void {
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
azdataMock.setup(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
// Set up dialog to return new model with our new password from the reprompt
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password);
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), password);
const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
await model.acquireAzdataSession(true);
await model.login(true);
should(waitForCloseStub.called).be.true('waitForClose should have been called');
azdataMock.verify(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
});
it('Prompt for password when we already have a password but prompt reconnect is true', async function (): Promise<void> {
@@ -134,20 +148,20 @@ describe('ControllerModel', function (): void {
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
azdataMock.setup(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
// Set up dialog to return new model with our new password from the reprompt
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password);
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), password);
const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
// Set up original model with a password
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, 'originalPassword');
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), 'originalPassword');
await model.acquireAzdataSession(true);
await model.login(true);
should(waitForCloseStub.called).be.true('waitForClose should have been called');
azdataMock.verify(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
});
it('Model values are updated correctly when modified during reconnect', async function (): Promise<void> {
@@ -162,7 +176,7 @@ describe('ControllerModel', function (): void {
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
azdataMock.setup(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
@@ -170,27 +184,19 @@ describe('ControllerModel', function (): void {
const originalPassword = 'originalPassword';
const model = new ControllerModel(
treeDataProvider,
{
id: uuid(),
url: '127.0.0.1',
kubeConfigFilePath: '/path/to/.kube/config',
kubeClusterContext: 'currentCluster',
username: 'admin',
name: 'arc',
rememberPassword: false,
resources: []
},
getDefaultControllerInfo(),
originalPassword
);
await treeDataProvider.addOrUpdateController(model, originalPassword);
const newInfo: ControllerInfo = {
id: model.info.id, // The ID stays the same since we're just re-entering information for the same model
url: 'newUrl',
endpoint: 'newUrl',
kubeConfigFilePath: '/path/to/.kube/config',
kubeClusterContext: 'currentCluster',
username: 'newUser',
name: 'newName',
namespace: 'newNamespace',
rememberPassword: true,
resources: []
};
@@ -203,7 +209,7 @@ describe('ControllerModel', function (): void {
const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(
{ controllerModel: newModel, password: newPassword }));
await model.acquireAzdataSession(true);
await model.login(true);
should(waitForCloseStub.called).be.true('waitForClose should have been called');
should((await treeDataProvider.getChildren()).length).equal(1, 'Tree Data provider should still only have 1 node');
should(model.info).deepEqual(newInfo, 'Model info should have been updated');

View File

@@ -40,7 +40,8 @@ export const FakePostgresServerShowOutput: azdataExt.AzdataOutput<azdataExt.Post
extensions: [{ name: '' }],
settings: {
default: { ['']: '' }
}
},
version: ''
},
scale: {
shards: 0,
@@ -114,7 +115,7 @@ describe('PostgresModel', function (): void {
controllerModel = new FakeControllerModel();
//Stub calling azdata login and acquiring session
sinon.stub(controllerModel, 'acquireAzdataSession').returns(Promise.resolve(vscode.Disposable.from()));
sinon.stub(controllerModel, 'login').returns(Promise.resolve());
// Stub the azdata CLI API
azdataApi = new FakeAzdataApi();

View File

@@ -38,7 +38,8 @@ export const FakePostgresServerShowOutput: azdataExt.AzdataOutput<azdataExt.Post
extensions: [{ name: '' }],
settings: {
default: { ['']: '' }
}
},
version: '12'
},
scale: {
shards: 0,
@@ -121,7 +122,7 @@ describe('postgresConnectionStringsPage', function (): void {
controllerModel = new FakeControllerModel();
//Stub calling azdata login and acquiring session
sinon.stub(controllerModel, 'acquireAzdataSession').returns(Promise.resolve(vscode.Disposable.from()));
sinon.stub(controllerModel, 'login').returns(Promise.resolve());
// Setup PostgresModel
const postgresResource: PGResourceInfo = { name: 'pgt', resourceType: '' };

View File

@@ -78,7 +78,7 @@ describe('postgresOverviewPage', () => {
beforeEach(() => {
sinon.stub(utils, 'promptForInstanceDeletion').returns(Promise.resolve(true));
sinon.stub(controllerModel, 'acquireAzdataSession').returns(Promise.resolve(vscode.Disposable.from()));
sinon.stub(controllerModel, 'login').returns(Promise.resolve());
refreshTreeNode = sinon.stub(controllerModel, 'refreshTreeNode');
});

View File

@@ -18,8 +18,8 @@ describe('ConnectControllerDialog', function (): void {
(<{ info: ControllerInfo | undefined, description: string }[]>[
{ info: undefined, description: 'all input' },
{ info: { url: '127.0.0.1' }, description: 'all but URL' },
{ info: { url: '127.0.0.1', username: 'sa' }, description: 'all but URL and password' }]).forEach(test => {
{ info: { endpoint: '127.0.0.1' }, description: 'all but URL' },
{ info: { endpoint: '127.0.0.1', username: 'sa' }, description: 'all but URL and password' }]).forEach(test => {
it(`Validate returns false when ${test.description} is empty`, async function (): Promise<void> {
const connectControllerDialog = new ConnectToControllerDialog(undefined!);
connectControllerDialog.showDialog(test.info, undefined);
@@ -32,7 +32,7 @@ describe('ConnectControllerDialog', function (): void {
it('validate returns false if controller refresh fails', async function (): Promise<void> {
sinon.stub(ControllerModel.prototype, 'refresh').returns(Promise.reject('Controller refresh failed'));
const connectControllerDialog = new ConnectToControllerDialog(undefined!);
const info = { id: uuid(), url: 'https://127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] };
const info: ControllerInfo = { id: uuid(), endpoint: 'https://127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] };
connectControllerDialog.showDialog(info, 'pwd');
await connectControllerDialog.isInitialized;
const validateResult = await connectControllerDialog.validate();
@@ -41,36 +41,36 @@ describe('ConnectControllerDialog', function (): void {
it('validate replaces http with https', async function (): Promise<void> {
await validateConnectControllerDialog(
{ id: uuid(), url: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
{ id: uuid(), endpoint: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30081');
});
it('validate appends https if missing', async function (): Promise<void> {
await validateConnectControllerDialog({ id: uuid(), url: '127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
await validateConnectControllerDialog({ id: uuid(), endpoint: '127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30080');
});
it('validate appends default port if missing', async function (): Promise<void> {
await validateConnectControllerDialog({ id: uuid(), url: 'https://127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
await validateConnectControllerDialog({ id: uuid(), endpoint: 'https://127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30080');
});
it('validate appends both port and https if missing', async function (): Promise<void> {
await validateConnectControllerDialog({ id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
await validateConnectControllerDialog({ id: uuid(), endpoint: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30080');
});
for (const name of ['', undefined]) {
it.skip(`validate display name gets set to arc instance name for user chosen name of:${name}`, async function (): Promise<void> {
await validateConnectControllerDialog(
{ id: uuid(), url: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: name!, username: 'sa', rememberPassword: true, resources: [] },
{ id: uuid(), endpoint: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: name!, namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30081');
});
}
it.skip(`validate display name gets set to default data controller name for user chosen name of:'' and instanceName in explicably returned as undefined from the controller endpoint`, async function (): Promise<void> {
await validateConnectControllerDialog(
{ id: uuid(), url: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: '', username: 'sa', rememberPassword: true, resources: [] },
{ id: uuid(), endpoint: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: '', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30081',
undefined);
});
@@ -92,6 +92,6 @@ async function validateConnectControllerDialog(info: ControllerInfo, expectedUrl
const validateResult = await connectControllerDialog.validate();
should(validateResult).be.true('Validation should have returned true');
const model = await connectControllerDialog.waitForClose();
should(model?.controllerModel.info.url).equal(expectedUrl);
should(model?.controllerModel.info.endpoint).equal(expectedUrl);
should(model?.controllerModel.info.name).equal(expectedControllerInfoName);
}

View File

@@ -24,6 +24,20 @@ interface ExtensionGlobalMemento extends vscode.Memento {
setKeysForSync(keys: string[]): void;
}
function getDefaultControllerInfo(): ControllerInfo {
return {
id: uuid(),
endpoint: '127.0.0.1',
kubeConfigFilePath: '/path/to/.kube/config',
kubeClusterContext: 'currentCluster',
username: 'sa',
name: 'my-arc',
namespace: 'arc-ns',
rememberPassword: true,
resources: []
};
}
describe('AzureArcTreeDataProvider tests', function (): void {
let treeDataProvider: AzureArcTreeDataProvider;
beforeEach(function (): void {
@@ -58,7 +72,7 @@ describe('AzureArcTreeDataProvider tests', function (): void {
treeDataProvider['_loading'] = false;
let children = await treeDataProvider.getChildren();
should(children.length).equal(0, 'There initially shouldn\'t be any children');
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
await treeDataProvider.addOrUpdateController(controllerModel, '');
should(children.length).equal(1, 'Controller node should be added correctly');
await treeDataProvider.addOrUpdateController(controllerModel, '');
@@ -69,12 +83,12 @@ describe('AzureArcTreeDataProvider tests', function (): void {
treeDataProvider['_loading'] = false;
let children = await treeDataProvider.getChildren();
should(children.length).equal(0, 'There initially shouldn\'t be any children');
const originalInfo: ControllerInfo = { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] };
const originalInfo: ControllerInfo = getDefaultControllerInfo();
const controllerModel = new ControllerModel(treeDataProvider, originalInfo);
await treeDataProvider.addOrUpdateController(controllerModel, '');
should(children.length).equal(1, 'Controller node should be added correctly');
should((<ControllerTreeNode>children[0]).model.info).deepEqual(originalInfo);
const newInfo = { id: originalInfo.id, url: '1.1.1.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'new-name', username: 'admin', rememberPassword: false, resources: [] };
const newInfo: ControllerInfo = { id: originalInfo.id, endpoint: '1.1.1.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'new-name', namespace: 'new-namespace', username: 'admin', rememberPassword: false, resources: [] };
const controllerModel2 = new ControllerModel(treeDataProvider, newInfo);
await treeDataProvider.addOrUpdateController(controllerModel2, '');
should(children.length).equal(1, 'Shouldn\'t add duplicate controller node');
@@ -109,8 +123,8 @@ describe('AzureArcTreeDataProvider tests', function (): void {
mockArcApi.setup(x => x.azdata).returns(() => fakeAzdataApi);
sinon.stub(vscode.extensions, 'getExtension').returns(mockArcExtension.object);
sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').resolves([{ name: 'currentCluster', isCurrentContext: true }]);
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }, 'mypassword');
sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').returns([{ name: 'currentCluster', isCurrentContext: true }]);
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo(), 'mypassword');
await treeDataProvider.addOrUpdateController(controllerModel, '');
const controllerNode = treeDataProvider.getControllerNode(controllerModel);
const children = await treeDataProvider.getChildren(controllerNode);
@@ -123,8 +137,10 @@ describe('AzureArcTreeDataProvider tests', function (): void {
describe('removeController', function (): void {
it('removing a controller should work as expected', async function (): Promise<void> {
treeDataProvider['_loading'] = false;
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const controllerModel2 = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.2', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'cloudsa', rememberPassword: true, resources: [] });
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
const info2 = getDefaultControllerInfo();
info2.username = 'cloudsa';
const controllerModel2 = new ControllerModel(treeDataProvider, info2);
await treeDataProvider.addOrUpdateController(controllerModel, '');
await treeDataProvider.addOrUpdateController(controllerModel2, '');
const children = <ControllerTreeNode[]>(await treeDataProvider.getChildren());
@@ -141,20 +157,20 @@ describe('AzureArcTreeDataProvider tests', function (): void {
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', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
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', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
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', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
const miaaModel = new MiaaModel(controllerModel, { name: 'miaa-1', resourceType: ResourceType.sqlManagedInstances }, undefined!, treeDataProvider);
await treeDataProvider.addOrUpdateController(controllerModel, '');
const controllerNode = treeDataProvider.getControllerNode(controllerModel)!;